/*
 *  Copyright (C) 2006-2013  Ronald Blankendaal
 *  
 *  Many thanks to Manuel J. Gallego for his work on MobyGames querying
 *  for TincoreADB. This file is based on his code.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package org.dbgl.util.searchengine;

import java.io.IOException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.dbgl.model.SearchEngineImageInformation;
import org.dbgl.model.WebProfile;
import org.dbgl.model.SearchEngineImageInformation.SearchEngineImageType;
import org.dbgl.swtdesigner.SWTImageManager;


public class MobyGamesSearchEngine extends WebSearchEngine {

	private static final String IMG_ALT_MARKER = "\"alt\":";
	private static final String IMG_SRC_MARKER = "\"src\":";
	private static final String HTML_MULTIPLE_RESULT_MARKER_START = "<div class='specs'>Showing ";
	private static final String HTML_GAME_START = "<div class='list-info";
	private static final String HTML_GAME_TITLE_START = "<h3 class='list'>";

	private final static String MOBY_GAMES_HOST_NAME = "www.mobygames.com";
	private final static int RESULTS_PER_PAGE = 25;


	private MobyGamesSearchEngine() {}

    private static class SearchEngineHolder {
        private static WebSearchEngine instance = new MobyGamesSearchEngine();
    }

    public static WebSearchEngine getInstance() {
        return SearchEngineHolder.instance;
    }

    public String getIcon() {
		return SWTImageManager.IMG_MOBYGAMES;
	}
    
    public String getName() {
		return "MobyGames";
	}
    
    public String getSimpleName() {
		return "mobygames";
	}

	public List<WebProfile> getEntries(final String title, String[] platforms) throws IOException {
		int pageIdx = 0;
		int pages = 1;
		Set<WebProfile> allEntries = new LinkedHashSet<WebProfile>();

		while (pageIdx < pages) {
			String content = getResponseContent(HTTP_PROTOCOL + MOBY_GAMES_HOST_NAME + "/search/quick?ajax=1&sFilter=1&p=-1&sG=on&q="
					+ URLEncoder.encode(title.replaceAll("/", " "), "UTF-8") + "&offset=" + (pageIdx * RESULTS_PER_PAGE), "UTF-8");
			if (pageIdx == 0)
				pages = getPages(content);
			allEntries.addAll(extractEntries(content));
			pageIdx++;
		}

		return filterEntries(platforms, allEntries);
	}

	private static int getPages(String htmlChunk) {
		int i = htmlChunk.indexOf("\"name\":\"Games (");
		int j = htmlChunk.indexOf(")\"", i);
		if (i == -1 || j == -1)
			return 0;
		int entries = Integer.parseInt(htmlChunk.substring(i + 15, j));
		return (entries + RESULTS_PER_PAGE - 1) / RESULTS_PER_PAGE;
	}

	private static List<WebProfile> extractEntries(String html) {
		List<WebProfile> allEntries = new ArrayList<WebProfile>();
		html = html.replaceAll("\\\\\"", "\"");
		int gameMatchEntryIndex = html.indexOf(HTML_MULTIPLE_RESULT_MARKER_START);
		if (gameMatchEntryIndex != -1)
			gameMatchEntryIndex = html.indexOf(HTML_GAME_START, gameMatchEntryIndex);

		while (gameMatchEntryIndex != -1) {

			int gameTitleIdx = html.indexOf(HTML_GAME_TITLE_START, gameMatchEntryIndex);
			String gameTitle = unescapeHtml(extractNextContent(html, gameTitleIdx + HTML_GAME_TITLE_START.length(), HTML_ANCHOR_OPEN, HTML_ANCHOR_CLOSE));
			String url = extractNextHrefContent(html, gameTitleIdx + HTML_GAME_TITLE_START.length());

			gameMatchEntryIndex = html.indexOf("<span style=\"white-space: nowrap\">", gameMatchEntryIndex);
			int gameEndMatchEntryIndex = html.indexOf(HTML_DIV_CLOSE, gameMatchEntryIndex);
			String details = html.substring(gameMatchEntryIndex, gameEndMatchEntryIndex);
			int platformIdx = details.indexOf(HTML_SPAN_OPEN);

			while (platformIdx != -1) {
				String platform = extractNextContent(details, platformIdx, HTML_SPAN_OPEN, HTML_SPAN_CLOSE);
				if (platform.indexOf(HTML_ANCHOR_OPEN) != -1) {
					platform = extractNextContent(details, platformIdx, HTML_ANCHOR_OPEN, HTML_ANCHOR_CLOSE);
					url = extractNextHrefContent(details, platformIdx);
				} else {
					int yrIdx = platform.indexOf(" (");
					if (yrIdx != -1) {
						platform = platform.substring(0, yrIdx);
					}
				}
				url = absoluteUrl(MOBY_GAMES_HOST_NAME, url);
				String year = extractNextContent(details, platformIdx, HTML_EM_OPEN, HTML_EM_CLOSE);

				WebProfile gameEntry = new WebProfile();
				gameEntry.setTitle(gameTitle);
				gameEntry.setUrl(url);
				gameEntry.setPlatform(platform);
				gameEntry.setPublisherName("");
				gameEntry.setYear(year);
				allEntries.add(gameEntry);
				platformIdx = details.indexOf(HTML_SPAN_OPEN, platformIdx + 1);
			}

			gameMatchEntryIndex = html.indexOf(HTML_GAME_START, gameEndMatchEntryIndex + HTML_DIV_CLOSE.length());
		}
		return allEntries;
	}

	public WebProfile getEntryDetailedInformation(final WebProfile entry) throws UnknownHostException, IOException {
		WebProfile result = new WebProfile();

		result.setTitle(entry.getTitle());
		result.setPlatform(entry.getPlatform());
		result.setYear(entry.getYear());
		result.setUrl(entry.getUrl());

		String responseEntry = getResponseContent(entry.getUrl(), "UTF-8");

		result.setDeveloperName(extractCategory(responseEntry, ">Developed by</div>"));
		result.setPublisherName(extractCategory(responseEntry, ">Published by</div>"));
		result.setGenre(extractCategory(responseEntry, ">Genre</div>"));
		result.setNotes(extractDescription(responseEntry));
		result.setRank(extractRank(responseEntry, result.getPlatform()));
		result.setCoreGameCoverUrl(extractCoreGameCoverUrl(responseEntry));

		if (StringUtils.isEmpty(result.getDeveloperName()))
			result.setDeveloperName(extractCredits(responseEntry, "<div class='detail credit-list'>"));

		return result;
	}

	private String extractCredits(String htmlChunk, String marker) {
		int startIndex = htmlChunk.indexOf(marker);
		if (startIndex != -1) {
			int endIndex = htmlChunk.indexOf("<h2>Screenshots</h2>", startIndex + marker.length());
			if (endIndex != -1) {
				String credits = htmlChunk.substring(startIndex + marker.length(), endIndex);
				startIndex = idxNextHrefContent(credits, 0);
				String result = extractNextContent(credits, startIndex, HTML_ANCHOR_OPEN, HTML_ANCHOR_CLOSE);
				if (idxNextHrefContent(credits, startIndex + result.length()) == -1)
					return unescapeHtml(result);
			}
		}
		return StringUtils.EMPTY;
	}

	public SearchEngineImageInformation[] getEntryImages(final WebProfile entry, int coverArtMax, int screenshotsMax, boolean forceAllRegionsCoverArt) throws IOException {
		List<SearchEngineImageInformation> result = new ArrayList<SearchEngineImageInformation>();
		if (coverArtMax > 0) {
			result.addAll(getEntryCoverArtInformation(entry, coverArtMax, forceAllRegionsCoverArt));
		}
		if (screenshotsMax > 0) {
			result.addAll(getEntryScreenshotInformation(entry, screenshotsMax));
		}
		entry.setWebImages(result.toArray(new SearchEngineImageInformation[0]));
		return entry.getWebImages();
	}

	private static String extractCoreGameCoverUrl(String htmlChunk) {
		int primIndex = htmlChunk.indexOf("<div class='game-nav'>");
		int secIndex = htmlChunk.indexOf("<div class='text-center'><a href", primIndex);
		if (primIndex == -1 || secIndex == -1) return null;
		return extractNextHrefContent(htmlChunk, secIndex + 25);
	}

	private static String extractCategory(final String htmlChunk, final String marker) {
		int startIndex = htmlChunk.indexOf(marker);
		if (startIndex != -1) {
			int endIndex = htmlChunk.indexOf(HTML_DIV_CLOSE, startIndex + marker.length());
			return unescapeHtml(removeAllTags(htmlChunk.substring(startIndex + marker.length(), endIndex + HTML_DIV_CLOSE.length())));
		} else {
			return "";
		}
	}

	private static String extractDescription(final String htmlChunk) {
		int endIndex = htmlChunk.indexOf("<a class=\"edit\" href=");
		int startIndex = htmlChunk.lastIndexOf(HTML_DIV_CLOSE, endIndex);
		return unescapeHtml(removeAllTags(htmlChunk.substring(startIndex + HTML_DIV_CLOSE.length(), endIndex)));
	}

	private static Integer extractRank(final String htmlChunk, final String platform) {
		int primIndex = htmlChunk.indexOf("<div class='scores moby-scores'>");
		int secIndex = htmlChunk.indexOf("<div class='platform'>" + platform + "</div>", primIndex);
		int terIndex = htmlChunk.indexOf("<div class='score'>", secIndex);
		if (primIndex == -1 || secIndex == -1 || terIndex == -1) return 0;
		try {
			String rank = extractNextContent(htmlChunk, terIndex, HTML_DIV_OPEN, HTML_DIV_CLOSE);
			return (int)(Double.parseDouble(rank) * 20.0);
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	private List<SearchEngineImageInformation> getEntryScreenshotInformation(final WebProfile entry, final int max) throws IOException {
		List<SearchEngineImageInformation> result = new ArrayList<SearchEngineImageInformation>();
		String htmlChunk = getResponseContent(entry.getScreenshotsUrl(), "UTF-8");
		int startIndex = htmlChunk.indexOf("var screenshots");

		if (startIndex != -1) {
			int endIndex = htmlChunk.indexOf("</script>", startIndex);

			if (endIndex != -1) {
				htmlChunk = htmlChunk.substring(startIndex, endIndex);
				startIndex = htmlChunk.indexOf("\"thumbs\":");
				int found = 0;

				while ((found < max) && (startIndex != -1)) {
					int srcIndex = htmlChunk.indexOf(IMG_SRC_MARKER, startIndex);
					if (srcIndex == -1) break;
					String imgUrl = extractNextDoubleQuotedContent(htmlChunk, srcIndex + IMG_SRC_MARKER.length());
					imgUrl = absoluteUrl(MOBY_GAMES_HOST_NAME, imgUrl.replaceAll("/images/shots/s/", "/images/shots/l/"));
					srcIndex = htmlChunk.indexOf(IMG_ALT_MARKER, startIndex);
					String imgDescription = extractNextDoubleQuotedContent(htmlChunk, srcIndex + IMG_ALT_MARKER.length()).replace(HTML_BR_UNCLOSED, " ").replace(HTML_BR_CLOSED, " ").replace(HTML_BR_CLOSED_ALT, " ");;
					if (imgDescription.startsWith(entry.getTitle())) imgDescription = imgDescription.substring(entry.getTitle().length()).trim();
					if (imgDescription.startsWith(entry.getPlatform())) imgDescription = imgDescription.substring(entry.getPlatform().length()).trim();
					result.add(new SearchEngineImageInformation(SearchEngineImageType.Screenshot, imgUrl, imgDescription));
					startIndex = htmlChunk.indexOf("},{", startIndex + 1);
					found++;
				}
			}
		}
		return result;
	}

	private List<SearchEngineImageInformation> getEntryCoverArtInformation(final WebProfile entry, final int max, final boolean forceAllRegionsCoverArt) throws IOException {
		List<SearchEngineImageInformation> result = new ArrayList<SearchEngineImageInformation>();
		int maxAmountCoverArtInRegion = max;
				
		String htmlChunk = getResponseContent(entry.getCoverArtUrl(), "UTF-8");
		int startIndex = htmlChunk.indexOf("<div class='game-info'>");
		
		if (startIndex != -1) {
			if (entry.getCoreGameCoverUrl() != null && !forceAllRegionsCoverArt) {
				startIndex = htmlChunk.indexOf(entry.getCoreGameCoverUrl(), startIndex);
				int endIndex = htmlChunk.indexOf("</div></div>", startIndex);
				maxAmountCoverArtInRegion = Math.min(StringUtils.countMatches(htmlChunk.substring(startIndex, endIndex), "<div class='thumb'") + 1, maxAmountCoverArtInRegion);
			}

			startIndex = htmlChunk.indexOf("var screenshots");

			if (startIndex != -1) {
				int endIndex = htmlChunk.indexOf("</script>", startIndex);

				if (endIndex != -1) {
					htmlChunk = htmlChunk.substring(startIndex, endIndex);
					startIndex = htmlChunk.indexOf("\"thumbs\":");

					if (entry.getCoreGameCoverUrl() != null && !forceAllRegionsCoverArt) {
						String imgId = StringUtils.chop(entry.getCoreGameCoverUrl().substring(entry.getCoreGameCoverUrl().lastIndexOf(',') + 1));
						startIndex = htmlChunk.indexOf("\"shotId\":\"" + imgId + "\"");
						startIndex = htmlChunk.lastIndexOf('{', startIndex);
					}

					int found = 0;
	
					while ((found < maxAmountCoverArtInRegion) && (startIndex != -1)) {
						int srcIndex = htmlChunk.indexOf(IMG_SRC_MARKER, startIndex);
						if (srcIndex == -1) break;
						String imgUrl = extractNextDoubleQuotedContent(htmlChunk, srcIndex + IMG_SRC_MARKER.length());
						imgUrl = absoluteUrl(MOBY_GAMES_HOST_NAME, imgUrl.replaceAll("/images/covers/small/", "/images/covers/large/"));
						srcIndex = htmlChunk.indexOf(IMG_ALT_MARKER, startIndex);
						String imgDescription = extractNextDoubleQuotedContent(htmlChunk, srcIndex + IMG_ALT_MARKER.length()).replace(HTML_BR_UNCLOSED, " ").replace(HTML_BR_CLOSED, " ").replace(HTML_BR_CLOSED_ALT, " ");
						if (imgDescription.startsWith(entry.getTitle())) imgDescription = imgDescription.substring(entry.getTitle().length()).trim();
						if (imgDescription.startsWith(entry.getPlatform())) imgDescription = imgDescription.substring(entry.getPlatform().length()).trim();
						result.add(new SearchEngineImageInformation(SearchEngineImageType.CoverArt, imgUrl, imgDescription));
						startIndex = htmlChunk.indexOf("},{", startIndex + 1);
						found++;
					}
				}
			}
		}
		return result;
	}
}
