/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: InvertedIndex.java,v 1.5 2006/02/26 12:22:58 akabane Exp $
 */
package org.logical_paradox.koike.core.search;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.logical_paradox.koike.core.KoikeConstant;

/**
 * 転置インデックス
 * 本当は000110000..などの具合に0,1で索引語の存在情報を表現したいが<br>
 * 21億documentsを表現するのに21億bitが必要なので、64bit(long)のブロックの<br>
 * 配列として表現している。必要のない部分については格納しないことで<br>
 * 容量を節約している。
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.5 $
 */
public class InvertedIndex {
	/** ノードID */
	private final String nodeId;
	/** ブロック番号 */
	private final int blockno;
	/** 転置インデックス */
	private long documents;
	/** ロケーションマップ(キー:docno 値:位置情報の配列) */
	private HashMap<Integer, ArrayList<Location>> locationMap = new HashMap<Integer, ArrayList<Location>>();

	/**
	 * コンストラクタ
	 * @param nodeId_ ノードID
	 * @param blockno_ ブロック番号
	 * @param documents_ 転置インデックス(各bit 0:ある 1:ない), MSB=最大 LSB=最小
	 */
	public InvertedIndex(String nodeId_, int blockno_, long documents_) {
		nodeId = nodeId_;
		blockno = blockno_;
		documents = documents_;
	}
	/**
	 * コピーコンストラクタ
	 * @param iidx 元のデータ
	 */
	public InvertedIndex(InvertedIndex iidx) {
		nodeId = new String(iidx.getNodeId());
		blockno = iidx.getBlockNo();
		documents = iidx.getDocumentAvailability();
		locationMap = (HashMap<Integer, ArrayList<Location>>)iidx.locationMap.clone();
	}
	/**
	 * ノードIDを返す
	 * @return
	 */
	public String getNodeId() {
		return nodeId;
	}
	/**
	 * この転置インデックスのブロック番号を返す
	 * @return ブロック番号
	 */
	public int getBlockNo() {
		return blockno;
	}
	/**
	 * 該当ノード/該当ブロックの部分転置インデックスを設定する
	 * 設定値に従って、位置情報を削除する(減少した場合のみ)
	 * @param doc 部分転置インデックス
	 */
	public void setDocumentAvailability(long doc) {
		setDocumentAvailability(doc, true);
	}
	/**
	 * 該当ノード/該当ブロックの部分転置インデックスを設定する
	 * @param doc 部分転置インデックス
	 * @param filter true:位置情報フィルタあり / false:なし
	 */
	public void setDocumentAvailability(long doc, boolean filter) {
		if(filter == true) {
			filterLocations(doc);
		}
		documents = doc;
	}
	/**
	 * 該当ノード/該当ブロックの部分転置インデックスを返す
	 * @return 部分転置インデックス
	 */
	public long getDocumentAvailability() {
		return documents;
	}
	/**
	 * 同じかどうかを比較する
	 * ノードID/ブロック番号が一致していればOkとする
	 */
	public boolean equals(Object o) {
		if(o == null || o instanceof InvertedIndex == false) {
			return false;
		}
		InvertedIndex iidx = (InvertedIndex)o;

		return iidx.blockno == blockno && iidx.nodeId.equals(nodeId);
	}
	/**
	 * ハッシュコードを返す
	 * ノードIDとブロック番号が一致していれば、同じ値を返す
	 * @return ハッシュコード
	 */
	public int hashCode() {
		return nodeId.hashCode() * blockno + (blockno/2);
	}
	/**
	 * 位置情報を追加する
	 * @param location 位置情報
	 */
	public void addLocation(Location location) {
		synchronized(locationMap) {
			Integer docno = new Integer(location.getDocno());
			ArrayList<Location> locations = locationMap.get(docno);
			if(locations == null) {
				locations = new ArrayList<Location>();
			}
			locations.add(location);
			locationMap.put(docno, locations);
		}
	}
	/**
	 * 位置情報を破棄する
	 * @param location 位置情報
	 */
	public void removeLocation(Location location) {
		synchronized(locationMap) {
			int docno = location.getDocno();
			Integer docnoKey = new Integer(docno);
			ArrayList<Location> locations = locationMap.get(docnoKey);
			if(locations == null) {
				return;
			}
			// 位置情報の破棄
			locations.remove(location);

			if(locations.size() == 0) {
				// 指定文書中の位置情報が全くなくなってしまった場合，転置情報を再設定する
				long filter = ~(1L << (docno - (blockno * KoikeConstant.INVERTED_INDEX_BLK_SIZE)));
				documents = documents & filter;
				locationMap.remove(docnoKey);
			} else {
				locationMap.put(docnoKey, locations);
			}
		}
	}
	/**
	 * 位置情報を返す
	 * @param docno 文書番号
	 * @param offset 先頭からのオフセット
	 * @return 位置情報(null:なし)
	 */
	public Location getLocation(int docno, int offset) {
		synchronized(locationMap) {
			ArrayList locations = (ArrayList)locationMap.get(new Integer(docno));
			if(locations == null || locations.size() == 0) {
				return null;
			}
			Location key = new Location(nodeId, docno, offset);
			int index = locations.indexOf(key);
			if(index < 0) {
				return null;
			} else {
				return(Location)locations.get(index);
			}
		}
	}
	/**
	 * 位置情報を持っているかどうかを返す
	 * @return true:もっている / false:もっていない
	 */
	public boolean hasLocations() {
		return locationMap.size() > 0;
	}
	/**
	 * 指定ドキュメント中の位置情報を持っているかどうかを返す
	 * @param docno 文書番号
	 * @return true:もっている / false:もっていない
	 */
	public boolean hasLocationsOf(int docno) {
		ArrayList locations = (ArrayList)locationMap.get(new Integer(docno));
		return locations != null && locations.size() > 0;
	}
	/**
	 * 位置情報のイテレータを返す
	 * @return 位置情報のイテレータ
	 */
	public Iterator iteratorLocations() {
		return new LocationIterator(locationMap);
	}
	/**
	 * 指定された部分転置位置情報に従って，必要なくなった位置情報を当該インデックスから除去する
	 * ブロック番号やノードIDなどは変更なしとの前提で
	 * @param documents 部分転置位置情報
	 */
	protected void filterLocations(long documents) {
		long filter = 1;
		int docno = blockno * KoikeConstant.INVERTED_INDEX_BLK_SIZE;

		// どのbitがたっているかを見る
		for(int i = 0; i < KoikeConstant.INVERTED_INDEX_BLK_SIZE; i++, filter <<= 1, docno++) {
			if((documents & filter) == 0) {
				// 該当文書中に索引語が存在しないので、位置情報を除去する
				synchronized(locationMap) {
					Integer key = new Integer(docno);
					ArrayList locations = (ArrayList)locationMap.get(key);
					if(locations != null) {
						// 全消去
						locations.clear();
						locationMap.remove(key);
					}
				}
			}
		}
	}
	/**
	 * インスタンスをコピーする
	 */
	protected Object clone() throws CloneNotSupportedException {
		return new InvertedIndex(this);
	}

	/**
	 * 位置情報を返すイテレータ
	 * @author satoshi akabane@logical-paradox.org
	 * @version $Revision: 1.5 $
	 */
	class LocationIterator implements Iterator {
		/** 位置情報のリスト */
		private final ArrayList values = new ArrayList();
		/** 位置情報リストのイテレータ */
		private final Iterator listIterator;

		/**
		 * コンストラクタ
		 * @param m 位置情報のマップ
		 */
		protected LocationIterator(Map m) {
			synchronized(m) {
				// 指定されたマップに格納されている全ての値を抽出する
				for(Iterator it = m.values().iterator(); it.hasNext();) {
					List locations = (List)it.next();
					if(locations != null) {
						values.addAll(locations);
					}
				}
			}
			listIterator = values.iterator();
		}
		/**
		 * コレクションから値を削除する
		 */
		public void remove() {
			listIterator.remove();
		}
		/**
		 * 次の値が存在するかどうかを返す
		 * @return true:ある / false:ない
		 */
		public boolean hasNext() {
			return listIterator.hasNext();
		}
		/**
		 * イテレータが参照している次の要素を返す
		 * @return 次の要素
		 */
		public Object next() {
			return listIterator.next();
		}
	}
}
