/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: StubIndexAccessor.java,v 1.2 2005/06/19 15:14:34 rampil Exp $
 */
package org.logical_paradox.koike.tools.isys;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;

import org.logical_paradox.koike.core.KoikeConstant;
import org.logical_paradox.koike.core.indexer.IndexAccessException;
import org.logical_paradox.koike.core.indexer.IndexAccessor;
import org.logical_paradox.koike.core.parser.Term;
import org.logical_paradox.koike.core.search.InvertedIndex;
import org.logical_paradox.koike.core.search.KoikeIndexResultSet;
import org.logical_paradox.koike.core.search.Location;

/**
 * スタブ用のインデックスシステムに接続するアクセサ
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.2 $
 */
public class StubIndexAccessor implements IndexAccessor {
	/** プロパティファイル */
	private Properties index = new Properties();
	/** スタブインデックスシステム用インデックスファイルの配置位置 */
	public static final String STUB_INDEX_DATADIR = StubProperty.getProperty("data.stub");
	/** スタブインデックスシステムのインデックスファイル */
	public static final String STUB_INDEX_DATAFILE = STUB_INDEX_DATADIR + File.separator + "stub.index";

	/** 接続が閉じているかどうか */
	private boolean closed = false;

	/**
	 * 接続がとじているかどうか
	 * @return true:閉じている / false:開いている
	 */
	public boolean isClosed() {
		return closed;
	}

	/**
	 * 接続を閉じる
	 * 接続のクローズと同時にインデックスの内容をファイルへ出力する<br>
	 * ファイルはドキュメント番号ごとに出力される
	 */
	public void close() throws IndexAccessException {
		try {
			// プロパティファイルを出力する
			index.store(new FileOutputStream(STUB_INDEX_DATAFILE), null);
		} catch (Exception e) {
			e.printStackTrace();
			throw new IndexAccessException(e);
		}
		closed = true;
	}

	/**
	 * 現在のインデックスファイルをメモリに取り込む
	 */
	public void load() throws FileNotFoundException, IOException {
		index = new Properties();
		index.load(new FileInputStream(STUB_INDEX_DATAFILE));
	}
	/**
	 * インデックスに対して索引語を追加する
	 * ※digestは英数字(ノード番号) + ":" + 数字(ドキュメント番号)であることを想定している<br>
	 * ※Term[]は索引語の配列
	 */
	public synchronized void addTerm(String digest, Term[] term) throws IndexAccessException {
		// メッセージダイジェストの確認
		if(digest.indexOf(":") != digest.lastIndexOf(":") || digest.indexOf(":") < 0) {
			throw new IndexAccessException();
		}
		String[] tokens = digest.split(":");
		String nodeId = tokens[0];							// ノードID
		int docno = Integer.parseInt(tokens[1]);			// ドキュメント番号

		// 位置情報を連結して追加する
		for(int i = 0; i < term.length; i++) {
			// 更新しようとしている索引語の位置情報を先に取得しておく
			String locations = (String)index.get(term[i].getTerm());
			if(locations == null) {
				locations = "";
			}
			StringBuffer sb = new StringBuffer(locations);

			Term t = term[i];
			for(Iterator it = t.iterator(); it.hasNext();) {
				Integer pos = (Integer)it.next();
				if(sb.length() > 0) {
					sb.append(',');
				}
				sb.append(nodeId + ":" + docno + ":" + pos);
			}

			// 索引語1つ分の位置情報が完成したのでマップに登録する
			index.put(t.getTerm(), sb.toString());
		}
	}
	/**
	 * インデックスシステムから索引語を削除する
	 * @param 削除対象の索引語
	 * @return 削除された索引語の位置情報
	 */
	public Object removeTerm(String key) throws IndexAccessException {
		return index.remove(key);
	}

	/**
	 * 指定された索引語の位置情報を，検索システムからの回答として返す
	 * @param key 索引語
	 * @return インデックスシステムとしての回答(null:索引語が見つからなかった)
	 */
	public KoikeIndexResultSet getValue(String key) throws IndexAccessException {
		String value = (String)index.get(key);

		if(value == null) {
			// そもそも索引語自体が登録されていない
			return null;
		}

		KoikeIndexResultSet rs = new KoikeIndexResultSet();
		// 索引語の位置情報はカンマ区切りである
		String[] values = value.split(",");
		for(int i = 0; i < values.length; i++) {
			String[] tokens = values[i].split(":");
			String nodeId = tokens[0];														// ノードID
			int docno = Integer.parseInt(tokens[1]);										// 文書番号
			int pos = Integer.parseInt(tokens[2]);											// 位置情報
			int blkno = docno / KoikeConstant.INVERTED_INDEX_BLK_SIZE;						// ブロック番号
			long documents = 1 << docno - (blkno * KoikeConstant.INVERTED_INDEX_BLK_SIZE);	// document availability

			InvertedIndex iidx = rs.getInvertedIndex(nodeId, blkno);
			if(iidx != null) {
				documents = documents | iidx.getDocumentAvailability();
			} else {
				iidx = new InvertedIndex(nodeId, blkno, 0);
			}
			// document availabilityを再設定する
			iidx.setDocumentAvailability(documents);
			// 転置インデックスに対して位置情報を追加する
			iidx.addLocation(new Location(nodeId, docno, pos));

			rs.addInvertedIndex(iidx);
		}

		// 検索結果を返す
		return rs;
	}
}
