/*
 * SearchCore
 * 検索システムのコアの部分
 * 入力された検索語をインデックスから検索して，該当部分を配列にして返す
 */
package org.logical_paradox.fts;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

import org.logical_paradox.fts.summary.*;

public class SearchCore {
	protected final IndexAccessor accessor;

	// コンストラクタ
	public SearchCore( IndexAccessor axsr ) {
		accessor = axsr;
	}

	// 指定されたクエリーに対応するキーを取得する
	private IndexFoundLocation getKey(String query) throws IndexAccessException {
		InvertedIndexKey key = new InvertedIndexKey(query);
		KeywordLocationArray locations = (KeywordLocationArray)accessor.getValue(key);
		if( locations == null ) {
			System.out.println("指定された索引語は，辞書になかった:" + query);
			return null;
		}

		for(Iterator it = locations.iterator(); it.hasNext();) {
			key.setIndexTermOnDocument(((KeywordLocation)it.next())._dicno, true );
		}
// TODO デバッグ表示
System.out.println("key=" + key.toString() + "," + key.get64bitFormat(key.document));
		// 索引キーと，その出現位置を返す
		return new IndexFoundLocation(key, locations.toArray());
	}

	// 指定されたクエリーに基づいた検索結果を配列で返す
	// version 1.0では，クエリーは検索語とし，AND検索とする
	public SearchResult[] search( String[] searchWords ) throws IndexAccessException {
		long founddic = -1;
		IndexFoundLocation[][] indexFoundLocations = new IndexFoundLocation[searchWords.length][];
		Vector result = new Vector();

		try {
			// 全ての索引語が存在する文書番号をまず最初に割り出す
			for( int cnt = 0; cnt < searchWords.length; cnt++ ) {						// A v1.1 2003-12-13
				String[] queries = parseQuery( new String( searchWords[cnt].trim().getBytes(), "Shift_JIS" ) );		// 検索語はUnicodeに変換して処理する

				indexFoundLocations[cnt] = new IndexFoundLocation[queries.length];
				for( int i = 0; i < queries.length; i++ ) {
					IndexFoundLocation foundLocation = getKey( queries[i] );
					if( foundLocation != null ) {
						founddic &= foundLocation.key.getDocument();					// AND検索たる所以
						indexFoundLocations[cnt][i] = foundLocation;
					} else {
						founddic = 0;									// 指定されたクエリーで索引語が発見できなかったので，該当なし(ANDなので)
					}
				}

				if( founddic == 0L ) {
					// 1件もヒットしなかった場合
					return null;
				}

				// 検索結果を集計する
				// この段階で，founddicには全てが存在する(かもしれない)文書番号が格納されている
				// 索引語が連続していれば，あったとみなす
				ParsedKeyLocation pk = parseKeyLocations( indexFoundLocations[cnt], founddic );		// C v1.1 2003-12-13
				founddic &= pk.docno;																// A v1.1 2003-12-13
				if( pk.isEmpty() == false ) {
					result.addElement( pk );
				}
			}

			// 指定された検索語の数と，展開された位置情報の数が一致していなければ．
			// 何らかの検索語が文書中に存在しなかったことになる．
			if( result == null || result.size() != searchWords.length ) {
				return null;
			}

			// サマリー作成処理
			return getSummaryGenerator().getSummary( (ParsedKeyLocation[])result.toArray( new ParsedKeyLocation[0] ), founddic );
		} catch( Exception uee ) {
			uee.printStackTrace();
			throw new IndexAccessException( uee.getMessage() );
		}
	}

	/**
	 * サマリージェネレータを返す．
	 * 必要であれば，SearchCoreのこのメソッドをオーバーライドとして，別のタイプの
	 * 要約生成ジェネレータを返すようにコードを記述する
	 * 
	 * @return 生成されたサマリージェネレータ
	 */
	protected SummaryGenerator getSummaryGenerator() throws Exception {
		String summaryGeneratorClass = System.getProperty("FTS_SUMMARY_GENERATOR_PROPERTY");
		if(summaryGeneratorClass == null || summaryGeneratorClass.trim().length() == 0) {
			summaryGeneratorClass = FullTextSearchProperty.FTS_SUMMARY_GENERATOR_NAME;
		}

		// サマリージェネレータの生成
		return (SummaryGenerator)Class.forName(summaryGeneratorClass).newInstance();
	}
	// 検索結果を解析する
	protected ParsedKeyLocation parseKeyLocations( IndexFoundLocation[] indexFoundLocations, long founddic ) {
		InvertedIndexKey dicKey = new InvertedIndexKey( "", founddic );
		InvertedIndexKey foundKey = new InvertedIndexKey( "" );

		// そのままだと連続性確認作業がしにくいので，構造を変更する
		// hashtable[文書No.][索引語が発見された文書上の位置(先頭からのオフセット)] = 索引語
		// という構造にする．
		Hashtable dics = new Hashtable();
		for( int i = 0; i < indexFoundLocations.length; i++ ) {
			KeywordLocation[] locations = indexFoundLocations[i].locations;
			for( int j = 0; j < locations.length; j++ ) {
				Hashtable locationsOfDic = (Hashtable)dics.get( new Integer(locations[j]._dicno) );
				if( locationsOfDic == null ) {
					locationsOfDic = new Hashtable();
				}
				locationsOfDic.put( new Integer(locations[j]._pos), indexFoundLocations[i].key.getKey() );
				dics.put( new Integer(locations[j]._dicno), locationsOfDic );
			}
		}

		// 索引キーの1番目の発見数だけループする
		int terms = indexFoundLocations.length -1;		// 連続して配置されていなければならない索引語の数(最初の1語は基準になるので除外する)
		ParsedKeyLocation pk = new ParsedKeyLocation();		// A v1.2 2003-12-23

		for( int pos = 0; pos < indexFoundLocations[0].locations.length; pos++ ) {
			if( dicKey.isTermFoundInDocument( indexFoundLocations[0].locations[pos]._dicno ) == false ) {
				// 指定された索引キーが登録されている文書が対象外の場合は無視
				continue;
			}

			// 1番目の索引語が発見された位置を特定する
			int termOffset = indexFoundLocations[0].locations[pos]._pos;		// 出現位置
			int termDocNo = indexFoundLocations[0].locations[pos]._dicno;		// 文書No.
			Hashtable docset = (Hashtable)dics.get( new Integer( termDocNo ) );	// 指定された文書No.に対応する位置情報セットを取得する
			if( docset == null ) {
				// 文書セットに指定された文書No.の位置情報がない場合は次へ
				continue;
			}

			// 索引語が連続しているかどうかを調べる
			// ループは1から開始する．0は最初の索引語なので，連続性確認には必要ない
			boolean rc = true;
			for( int k = 1; k < terms; k++ ) {
				String term = (String)docset.get( new Integer( termOffset + k ) );		// 1番目の索引語から数えてk番目の索引語を取得する
// System.err.println( "termDocNo=" + termDocNo + " offset=" + termOffset + " k=" + k + ":" + term );
				if( term == null || term.equals( indexFoundLocations[k].key.getKey() ) == false ) {
					// 索引語が連続していないので強制終了
					rc = false;
					break;
				}
			}

			if( rc == true ) {
				// 全ての索引語が連続して配置されていた場合
				// 最初の索引語が発見された文書No.と位置を保存する
				pk.add( new KeywordLocation(termDocNo,termOffset) );
				foundKey.setIndexTermOnDocument(termDocNo, true); 
			}
		}

		pk.docno = foundKey.getDocument();
		return pk;
	}

	protected String[] parseQuery( String txt ) {
		int loopCnt = txt.length() - FullTextSearchProperty.GRAM_LENGTH +1;		// C v1.1 2003-12-13
		//----- A v1.1 2003-12-13 bgn
		int gramLength = FullTextSearchProperty.GRAM_LENGTH;
		if( loopCnt <= 0 ) {
			gramLength += (loopCnt-1);
			loopCnt = 1;			// 何か文字列が指定された場合，必ず1つは索引語になる
		}
		//----- A v1.1 2003-12-13 end
		String[] terms = new String[ loopCnt ];
		for( int cnt = 0; cnt < loopCnt; cnt++ ) {
			terms[cnt] = txt.substring( cnt, cnt + gramLength );		// C v1.1 2003-12-13
		}

		return terms;
	}


	// サーチコアを閉じる
	public void close() throws IndexAccessException {
		if( accessor.isClosed() == false ) {
			accessor.close();
		}
	}

	public class IndexFoundLocation {
		public final InvertedIndexKey key;
		public final KeywordLocation[] locations;

		public IndexFoundLocation( InvertedIndexKey k, KeywordLocation[] l ) {
			key = k;
			locations = l;
		}
	}
}

// end of SearchCore.java
