/*
 * LOGICAL-PARADOX.ORG
 * Copyright (C)2005 satoshi akabane(akabane@logical-paradox.org)
 * $Id: AndLogicalOperator.java,v 1.3 2006/01/31 17:10:01 rampil Exp $
 */
package org.logical_paradox.koike.rss.search.lo;

import java.util.HashMap;
import java.util.Iterator;

import org.logical_paradox.koike.core.search.InvertedIndex;
import org.logical_paradox.koike.core.search.KoikeIndexResultSet;

/**
 * AND演算子
 * @author satoshi akabane@logical-paradox.org
 * @version $Revision: 1.3 $
 */
public class AndLogicalOperator implements LogicalOperator {
	/**
	 * インデックスシステムの回答セットに対して論理演算を適用し，結果セットを返す
	 * @param resultset 演算対象
	 * @return 演算結果の配列(それぞれの回答セットに対して論理演算を適用した結果を配列で返す)
	 */
	public KoikeIndexResultSet[] execute(KoikeIndexResultSet[] resultset) throws LogicalOperationException {
		if(resultset == null || resultset.length == 0) {
			// 引数なしの場合
			return null;
		} else if(resultset.length == 1) {
			// 引数が1件のみの場合(=回答)
			return resultset;
		}
		// まず、最初の索引語の結果を整理する
		HashMap<String, InvertedIndex> map = new HashMap<String, InvertedIndex>();
		for(Iterator it = resultset[0].iteratorInvertedIndexes(); it.hasNext();) {
			InvertedIndex iidx = (InvertedIndex)it.next();
			// 編集用コピーを作成する
			InvertedIndex newIidx = new InvertedIndex(iidx);
			map.put(iidx.getNodeId() + ":" + iidx.getBlockNo(), newIidx);
		}

		KoikeIndexResultSet[] results = new KoikeIndexResultSet[resultset.length];

		KoikeIndexResultSet resultSet = new KoikeIndexResultSet();

		// 他の索引語について，AND演算をする
		for(int i = 1; i < resultset.length; i++) {
			KoikeIndexResultSet rs = resultset[i];
			try {
				results[i] = (KoikeIndexResultSet)rs.clone();
			} catch (CloneNotSupportedException e) {
				throw new LogicalOperationException(e);
			}
			boolean found = false;

			for(Iterator it = rs.iteratorInvertedIndexes(); it.hasNext();) {
				InvertedIndex iidx = (InvertedIndex)it.next();
				// 索引語が第1索引語として指定されているかどうかを調べる
				InvertedIndex invertedIndex = (InvertedIndex)map.get(iidx.getNodeId() + ":" + iidx.getBlockNo());
				if(invertedIndex != null && (invertedIndex.getDocumentAvailability() & iidx.getDocumentAvailability()) > 0) {
					// 第1索引語と同じものがあったので，ANDをとる
					found = true;
					long documents = invertedIndex.getDocumentAvailability() & iidx.getDocumentAvailability();
					// documentのavailabilityを再設定すると，必要ない位置情報も自動的に除去される
					invertedIndex.setDocumentAvailability(documents);
					resultSet.addInvertedIndex(invertedIndex);
				} else {
					// ANDをとって0になってしまうものは、マップから除去しておく
					if(invertedIndex != null) {
						map.remove(invertedIndex.getNodeId() + ":" + invertedIndex.getBlockNo());
						resultSet.removeInvertedIndex(invertedIndex);
					}
					map.remove(iidx.getNodeId() + ":" + iidx.getBlockNo());
					// この索引語の結果セットの転置インデックス自体も除去しておく
					results[i].removeInvertedIndex(iidx);
				}
			}
			if(found == false) {
				// 当該索引語について，第1索引語と全く一致しなかった(=AND条件でfalse)
				map = null;
				return null;
			}
		}

		// ANDフィルタをかける
		for(Iterator it = map.values().iterator(); it.hasNext();) {
			InvertedIndex iidx = (InvertedIndex)it.next();

			// 全ての索引語に対してANDフィルタをかける(ただし第1索引語以外)
			for(int i = 1; i < results.length; i++) {
				InvertedIndex target = results[i].getInvertedIndex(iidx);
				if(target != null) {
					results[i].getInvertedIndex(iidx).setDocumentAvailability(iidx.getDocumentAvailability());
				} else {
					resultSet.removeInvertedIndex(target);
				}
			}
		}
		// 最後に第1索引語の設定
		results[0] = resultSet;

		return results;
	}

}
