package dareka.processor.impl;

import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import dareka.common.Logger;
import dareka.common.regex.JavaMatcher;
import dareka.common.regex.JavaPattern;
import dareka.extensions.Rewriter;
import dareka.processor.HttpResponseHeader;

public class SearchRewriter implements Rewriter {
	// Rewriter interface
	static final Pattern SUPPORTED_PATTERN = Pattern
			.compile("^http://(?:www|nine)\\.nicovideo\\.jp/(?:search|tag)/([-0-9A-Za-z%+\\.]+)(\\?.*)?$");

	static final Pattern TAG_SEARCH_PATTERN = Pattern
			.compile("http://(?:www|nine)\\.nicovideo\\.jp/tag/");

	public Pattern getRewriterSupportedURLAsPattern() {
		return SUPPORTED_PATTERN;
	}
	
	private String[] writeItem(VideoDescriptor video, String filename) {
        String altid = Cache.videoDescriptorToAltId(video);
		boolean isLow = video.isLow();
		String smid = video.getId();
		String title = Cache.getTitleFromFilename(filename);
        if (title == null) {
            title = "nicocache-unknown-title";
        }
		String background = isLow ? "#fff7f7" : "#f7ffff";
		Cache cache = new Cache(video);
		String extension = cache.getPostfix();
		
		// 音楽抽出用のリンク
		String mp3 = "";
		if (extension.equals(Cache.FLV) || 
			(extension.equals(Cache.MP4) /* && new File("MP4Box.exe").exists() */) ||
			(extension.equals(Cache.SWF) /* && new File("swfextract.exe").exists() */)
			) {
			mp3 = "<a href=\"/cache/" + altid + "/audio\">音声</a>・";
		}

		String thumbBlock =
			"<p class=\"font12\" style=\"font-weight:bold;\">" + smid + "</p>\r\n" +
			"<a href=\"watch/" + smid + "\"><img alt=\"" + title + "\" " +
			"src=\"http://tn-skr.smilevideo.jp/smile?i=" + smid.substring(2) +
			"\" width=\"130\" height=\"100\" style=\"border:solid 2px #333\"></a>\r\n";
		String descBlock =
			"<p class=\"font12\"><span>"+
			"<a class=\"cacheResultTitle\" href=\"watch/" + smid + "\">" + title + "</a></span><br>\r\n" +
			"<span>キャッシュ " + extension +" ...</span></p>\r\n" +
			"<span></span><p class=\"font12\">\n" +
				"<a href=\"/cache/" + altid + "/movie\">保存</a>・" +
				mp3 + 
				"<a href=\"/cache/" + smid + ".xml\">コメ</a>・" +
				"<a href=\"/cache/rm?" + altid + "\" onclick=\"return confirm('消しますよ？');\">削除</a>\r\n" +
			"</p>\r\n";

		String[] ret = { thumbBlock, descBlock, background };
		return ret;
	}

	// 該当するリクエストの際に呼ばれる
	public String onMatch(Matcher match, HttpResponseHeader responseHeader, String content)  
			throws IOException {
		if (!Boolean.getBoolean("useSearchExtension") ||
			 Boolean.getBoolean("disableSearchRewriter") ||
				(TAG_SEARCH_PATTERN.matcher(match.group(0)).lookingAt() &&
						!Boolean.getBoolean("insertSearchResultToTagPage"))) {
			return content;
		}
		// 1ページ目なら表示
		if (match.group(2) != null && match.group(2).contains("page=")) {
			return content;
		}
		
		String query = Cache.tidyTitle(URLDecoder.decode(match.group(1), "UTF-8"));
		Map<VideoDescriptor, File> matched = searchVideo(query, false, true);
		if (matched.size() == 0) {
			return content;
		}
		
// 検索結果埋め込み用パターン
		JavaPattern pattern;
		String blockString;
		int index = -1;
		while (true) {
			String confName = "SearchExtConf";
			if (index >= 0)
				confName += index;
			index++;
			JavaPattern[] jp = EasyRewriter.getMatch(confName);
			String[] r = EasyRewriter.getReplace(confName);
			JavaPattern require = EasyRewriter.getRequire(confName);
			boolean notRequire = EasyRewriter.getNotRequire(confName);
			if (jp != null) {
				pattern = jp[0]; 
				blockString = r[0];
				if (require == null || (require.matcher(content).find() ^ notRequire)) {
					break;
				}

			} else {
				Logger.info("no SearcExtConf match. do nothing...");
				return content;
			}
		}
		
		StringBuilder sb = new StringBuilder();
		int resultMax = Integer.getInteger("searchResultMax", Integer.MAX_VALUE);
		int resultCount = 0;
		String buf = blockString;
		for (Map.Entry<VideoDescriptor, File> e : matched.entrySet()) {
			if (resultCount >= resultMax) {
				break;
			}
			String[] item = writeItem(e.getKey(), e.getValue().getName());
			assert item != null && item.length == 3;
			buf = buf.replaceFirst("<thumbBlock>", Matcher.quoteReplacement(item[0]))
					 .replaceFirst("<descBlock>" , Matcher.quoteReplacement(item[1]))
					 .replaceFirst("<colorBlock>", Matcher.quoteReplacement(item[2]));
			if (!buf.contains("<thumbBlock>")) {
				// <thumbBlock>が無くなったら書き出してリセット
				sb.append(buf); buf = blockString;
			}
			resultCount++;
		}
		if (resultCount == 0) {
			return content;
		} else if (buf != blockString) {
			sb.append(buf); // 残りがあれば書き出す
		}
		// 埋め込む
		JavaMatcher jm = pattern.matcher(content);
		if (jm.find()) {
			String resultMes = resultCount + "件";
			if (resultCount >= resultMax) {
				resultMes = "検索結果が多すぎます。先頭" + resultMax + "件を表示します";
			}
			content = jm.replaceFirst("$0" + Matcher.quoteReplacement(
				"<div><a href=\"javascript:void(0)\" onclick=\"var ele=document.getElementsByName('cacheResult');for(var i=0; i<ele.length; i++){ele[i].style.display=(ele[i].style.display!='' ? '' : 'none');}\">" +
				"<b>NicoCacheのキャッシュからの検索結果  （" + resultMes + "）</b></a></div>" + sb));
		}
		return content;
	}
	
	/**
	 * キャッシュを検索してマッチした結果のid2Fileマップを生成して返す。
	 * 通常検索の場合は、互換性維持の為にフォルダを含めないファイル名を対象に
	 * 単語単位(空白文字区切り・大文字小文字無視)のOR検索を行う。
	 * 正規表現検索の場合は、フォルダを含めたパスを対象に部分一致検索を行う。
	 * 
	 * @param query 検索文字列
	 * @param regex 正規表現検索の場合はtrue、通常検索の場合はfalse
	 * @param desc マップの格納順を降順にする場合はtrue、昇順ならfalse
	 * @return マッチしたキャッシュのソート済みid2Fileマップ
	 * @since NicoCache_nl+110110mod
	 */
    @Deprecated
    public static SortedMap<String, File> search(String query, boolean regex, boolean desc) {
        SortedMap<String, File> matched = new TreeMap<String, File>(
                Cache.getSmidComparator(desc));
        for (Map.Entry<VideoDescriptor, File> e : searchVideo(query, regex, desc).entrySet()) {
            VideoDescriptor video = e.getKey();
            matched.put(CacheManager.videoDescriptorToAltId(video), e.getValue());
        }
        return matched;
    }
    
	/**
	 * キャッシュを検索してマッチした結果のvideo2Fileマップを生成して返す。
	 * 通常検索の場合は、互換性維持の為にフォルダを含めないファイル名を対象に
	 * 単語単位(空白文字区切り・大文字小文字無視)のOR検索を行う。
	 * 正規表現検索の場合は、フォルダを含めたパスを対象に部分一致検索を行う。
	 * 
	 * @param query 検索文字列
	 * @param regex 正規表現検索の場合はtrue、通常検索の場合はfalse
	 * @param desc マップの格納順を降順にする場合はtrue、昇順ならfalse
	 * @return マッチしたキャッシュのソート済みvideo2Fileマップ
	 */
	public static SortedMap<VideoDescriptor, File> searchVideo(String query, boolean regex, boolean desc) {
		SortedMap<VideoDescriptor, File> matched = new TreeMap<VideoDescriptor, File>(
				Cache.getVideoDescriptorComparator(desc));
		Map<VideoDescriptor, File> caches = Cache.getVideo2File();
		if (caches.size() == 0) {
			return matched;
		}
		if ("low".equals(query)) { // low で検索したときのみ特殊処理
			for (Map.Entry<VideoDescriptor, File> e : caches.entrySet()) {
				if (e.getKey().isLow()) {
					matched.put(e.getKey(), e.getValue());
				}
			}
		} else if (regex) {
			try {
				Pattern queryPattern = Pattern.compile(query);
				for (Map.Entry<VideoDescriptor, File> e : caches.entrySet()) {
					if (isMatch(queryPattern, e.getValue())) {
						matched.put(e.getKey(), e.getValue());
					}
				}
			} catch (PatternSyntaxException e) {
				Logger.debugWithThread(e);
				Logger.warning("regex search error: " + query);
			}
		} else {
			String[] queryWords = query.toLowerCase().split("\\s+");
			for (Map.Entry<VideoDescriptor, File> e : caches.entrySet()) {
				if (isMatch(queryWords, e.getValue())) {
					matched.put(e.getKey(), e.getValue());
				}
			}
		}
		return matched;
	}
	
	// 単語OR検索
	private static boolean isMatch(String[] queryWords, File cacheFile) {
		String title = cacheFile.getName();
		String titleLow = Cache.tidyTitle(title.toLowerCase());
		for (String word : queryWords) {
			if (word.startsWith("-")) {
				// NOT
				if (titleLow.contains(word.substring(1)))
					return false;
			} else if (!titleLow.contains(word)) {
				// AND
				return false;
			}
		}
		return true;
	}
	
	// 正規表現検索
	private static boolean isMatch(Pattern queryPattern, File cacheFile) {
		String path = Cache.getPathFromFile(cacheFile);
		if (path != null) {
			return queryPattern.matcher(path).find();
		}
		return false;
	}
}
