/**
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2004 satoshi akabane(akabane@logical-paradox.org)
 *
 */
package org.logical_paradox.phalanx.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Properties;

import javax.xml.bind.JAXBException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.logical_paradox.koike.core.KoikeConfig;
import org.logical_paradox.koike.core.KoikeConfigException;
import org.logical_paradox.koike.core.search.Element;
import org.logical_paradox.koike.core.search.SearchCondition;
import org.logical_paradox.koike.core.search.SearchEngine;
import org.logical_paradox.koike.core.search.SearchResult;
import org.logical_paradox.phalanx.SearchException;
import org.logical_paradox.phalanx.SearchLogic;
import org.logical_paradox.phalanx.binding.condition.AndType;
import org.logical_paradox.phalanx.binding.condition.Condition;
import org.logical_paradox.phalanx.binding.condition.OrType;
import org.logical_paradox.phalanx.binding.resultset.ObjectFactory;
import org.logical_paradox.phalanx.binding.resultset.RecordType;
import org.logical_paradox.phalanx.binding.resultset.ResultSet;
import org.logical_paradox.phalanx.binding.resultset.SiteType;

/**
 * N-gram索引を使用する全文検索ロジック
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.15 $
 */
public class NgramFTSSearchLogic implements SearchLogic {
	/** ロガー */
	private static final Log log = LogFactory.getLog(NgramFTSSearchLogic.class);

	/** 'koike'用コンフィグファイル */
	public static final String KOIKE_CONFIG_FILENAME = "koike.conf";

	/** サーチエンジン */
	private SearchEngine searchEngine;

	/**
	 * 検索を実行する
	 * @param condition 検索条件
	 * @param configDir コンフィグファイルを格納したディレクトリ
	 * @return 検索結果セット
	 * @throws SearchException 検索に失敗した際に発生する例外
	 */
	public ResultSet search(Condition condition, String configDir) throws SearchException, JAXBException {
		// 全文検索アプリケーションの準備
		// 今はとりあえずAPサーバー上で全文検索システムを稼動させる
		try {
			if(searchEngine == null) {
				loadSearchEngine(configDir);
			}
			SearchResult sr = searchEngine.search(createSearchCondition(condition));
			return createResultSet(sr);
		} catch(Exception e) {
			log.error(e);
			StringWriter strw = new StringWriter();
			e.printStackTrace(new PrintWriter(strw));
			log.error(strw.getBuffer().toString());

			throw new SearchException(e.getMessage());
		}
	}
	/**
	 * 検索結果を変換して返す
	 * @param sr 検索エンジンからの回答
	 * @return phalanxとしての回答
	 * @throws JAXBException 検索結果変化処理中に発生した例外
	 */
	protected ResultSet createResultSet(SearchResult sr) throws JAXBException {
		ObjectFactory factory = new ObjectFactory();
		ResultSet rs = factory.createResultSet();

		if(sr == null || sr.getResultSet().length == 0) {
			rs.setHits(0);
			return rs;
		}
		rs.setHits(sr.getHits());
		org.logical_paradox.koike.core.search.ResultSet[] results = sr.getResultSet();
		for(int i = 0; i < results.length; i++) {
			RecordType record = factory.createRecordType();
			SiteType site = factory.createSiteType();
			site.setName(results[i].getSiteName());
			site.setUrl(results[i].getUrl());
			site.setSummary(results[i].getSummary());
			site.setTitle(results[i].getDocumentTitle());
			record.setSite(site);
			record.setDocumentType("html");
			record.setPriority(5);
			record.setDisabled(false);

			rs.getRecord().add(record);
		}

		return rs;
	}
	/**
	 * 検索ロジックの識別子を返す
	 * @return 識別子
	 */
	public String getLogicIdentifier() {
		return "FULL TEXT SEARCH LOGIC USING N-GRAM TERMS";
	}
	/**
	 * 検索条件を変換する
	 * @param condition フロントエンドシステムから送信されてきた検索条件
	 * @return 検索エンジンに対して渡す検索条件
	 */
	protected SearchCondition createSearchCondition(Condition condition) {
		// koikeの件数は0オリジンであるため，-1する
		int startIndex = condition.getStartindex().intValue() -1;
		int endIndex = condition.getEndindex().intValue() -1;
		int dispnum = endIndex - startIndex + 1;

		SearchCondition searchCondition = new SearchCondition();
		searchCondition.setStartp(startIndex < 0 ? 0 : startIndex);
		searchCondition.setDispnum(dispnum < 0 ? 0 : dispnum);

		// AND条件の取り込み
		for(Iterator it = condition.getAnd().iterator(); it.hasNext();) {
			AndType and = (AndType)it.next();
			searchCondition.addChildNode(buildAndType(new Element(Element.TYPE_AND), and));
		}
		// OR条件の取り込み
		for(Iterator it = condition.getOr().iterator(); it.hasNext();) {
			OrType or = (OrType)it.next();
			searchCondition.addChildNode(buildOrType(new Element(Element.TYPE_OR), or));
		}
		return searchCondition;
	}
	/**
	 * AND条件を取り込む
	 * @param element 生成中の検索条件オブジェクト
	 * @param and AND条件
	 * @return 生成された要素
	 */
	protected Element buildAndType(Element element, AndType and) {
		// AND条件の取り込み
		for(Iterator it = and.getAnd().iterator(); it.hasNext();) {
			element.addChildNode(buildAndType(new Element(Element.TYPE_AND), (AndType)it.next()));
		}
		// OR条件の取り込み
		for(Iterator it = and.getOr().iterator(); it.hasNext();) {
			element.addChildNode(buildOrType(new Element(Element.TYPE_OR), (OrType)it.next()));
		}
		// TERMの取り込み
		for(Iterator it = and.getTerm().iterator(); it.hasNext();) {
			String token = (String)it.next();
			element.addChildNode(new Element(Element.TYPE_TERM, token));
		}

		return element;
	}
	/**
	 * OR条件を取り込む
	 * @param element 生成中の検索条件オブジェクト
	 * @param or OR条件
	 * @return 生成された要素
	 */
	protected Element buildOrType(Element element, OrType or) {
		// AND条件の取り込み
		for(Iterator it = or.getAnd().iterator(); it.hasNext();) {
			element.addChildNode(buildAndType(new Element(Element.TYPE_AND), (AndType)it.next()));
		}
		// OR条件の取り込み
		for(Iterator it = or.getOr().iterator(); it.hasNext();) {
			element.addChildNode(buildOrType(new Element(Element.TYPE_OR), (OrType)it.next()));
		}
		// TERMの取り込み
		for(Iterator it = or.getTerm().iterator(); it.hasNext();) {
			try {
				element.addChildNode(new Element(Element.TYPE_TERM, new String(((String)it.next()).getBytes("utf-8"))));
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}

		return element;
	}
	/**
	 * サーチエンジンをロードする．
	 * @param configDir コンフィグファイルを格納したディレクトリ
	 */
	protected void loadSearchEngine(String configDir) throws FileNotFoundException, IOException, ClassNotFoundException, SecurityException, NoSuchMethodException, KoikeConfigException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
		// koikeコンフィグレーションのロード
		Properties pr = new Properties();
		pr.load(new FileInputStream(configDir + File.separator + KOIKE_CONFIG_FILENAME));
		KoikeConfig config = new KoikeConfig(pr);

		searchEngine = config.getSearchEngine();
	}
}
