/*------------------------------------------------------------------------------
* Copyright (C) 2003-2006 Ben van Klinken and the CLucene Team
* 
* Distributable under the terms of either the Apache License (Version 2.0) or 
* the GNU Lesser General Public License, as specified in the COPYING file.
------------------------------------------------------------------------------*/

#include <CLucene/StdHeader.h>
#include <CLucene/util/Misc.h>
#include "ChainedFilter.h"

/*
	Discussion - brian@unixpoet.com
	
	From ChainedFilter.java:

	...
	
	// First AND operation takes place against a completely false
	// bitset and will always return zero results. Thanks to
	// Daniel Armbrust for pointing this out and suggesting workaround.

	if (logic[0] == AND)
	{
		result = (BitSet) chain[i].bits(reader).clone();
		++i;
	}
	
	...
	
	The observation is correct and it was buggy. The problem is that the same
	issue remains for the ANDNOT logic op but with the inverse result: all bits
	set to 1. The result of the other ops, i.e. OR, AND, XOR for the first filter
	ends up just copying the bitset of the first filter (explicitly in the case of the AND).
	
	Why not do the same for the NAND? This will have the side effect of rendering the first op
	in the logic array superflous - not a big problem.
	
	The only "problem" is that we will return different results then the Java 
	Lucene code - though I prefer CLucene to be a correct implementation and only maintain 
	API compat rather than full 100% compat with Lucene.	
*/
CL_NS_DEF(search)
CL_NS_USE(index)
CL_NS_USE(util)
CL_NS_USE(document)


ChainedFilter::ChainedFilter( Filter ** filters, int _op )
{
	this->filters = filters;
	this->logic   = _op;
	this->logicArray = NULL;
}


ChainedFilter::ChainedFilter( Filter** filters, int* _array )
{
	this->filters = filters;
	this->logic   = -1;
	this->logicArray = _array;
}


ChainedFilter::~ChainedFilter(void)
{
	
}


ChainedFilter::ChainedFilter( const ChainedFilter& copy ) :
	filters( copy.filters ),
	logic( copy.logic ),
	logicArray( copy.logicArray )
{
}


Filter* ChainedFilter::clone() const {
	return _CLNEW ChainedFilter(*this );
}

TCHAR* ChainedFilter::getLogicString(int logic){
	if ( logic == ChainedFilter::OR )
		return _T("OR");
	else if ( logic == ChainedFilter::AND )
		return _T("AND");
	else if ( logic == ChainedFilter::ANDNOT )
		return _T("ANDNOT");
	else if ( logic == ChainedFilter::XOR )
		return _T("XOR");
	else if ( logic >= ChainedFilter::USER ){
		return _T("USER");
	}
	return _T("");
}

TCHAR* ChainedFilter::toString()
{
	
	Filter** filter = filters;
	
	StringBuffer buf(_T("ChainedFilter: ["));
	int* la = logicArray;
	while(*filter )
	{
		if ( filter != filters )
			buf.appendChar(' ');
		buf.append(getLogicString(logic==-1?*la:logic));
		buf.appendChar(' ');
		
		TCHAR* filterstr = (*filter)->toString();
		buf.append(filterstr);
		_CLDELETE_ARRAY( filterstr );
		
		filter++;
		if ( logic == -1 )
			la++;
	}
	
	buf.appendChar(']');
	
	return buf.toString();
}


/** Returns a BitSet with true for documents which should be permitted in
search results, and false for those that should not. */
BitSet* ChainedFilter::bits( IndexReader* reader )
{
	if( logic != -1 )
		return bits( reader, logic );
	else if( logicArray != NULL )
		return bits( reader, logicArray );
	else
		return bits( reader, DEFAULT );
}


BitSet* ChainedFilter::bits( IndexReader* reader, int logic )
{
	BitSet* bts = NULL;
	
	Filter** filter = filters;
	
	// see discussion at top of file
	if( *filter ) {
		BitSet* tmp = (*filter)->bits( reader );
		if ( (*filter)->shouldDeleteBitSet(tmp) ) //if we are supposed to delete this BitSet, then 
			bts = tmp; //we can safely call it our own
		else if ( tmp == NULL ){
			int32_t len = reader->maxDoc();
			bts = _CLNEW BitSet( len ); //bitset returned null, which means match _all_
			for (int32_t i=0;i<len;i++ )
				bts->set(i);
		}else{
			bts = tmp->clone(); //else it is probably cached, so we need to copy it before using it.
		}
		filter++;
	}
	else
		bts = _CLNEW BitSet( reader->maxDoc() );
	
	while( *filter ) {
		doChain( bts, reader, logic, *filter );
		filter++;
	}
	
	return bts;
}


BitSet* ChainedFilter::bits( IndexReader* reader, int* _logicArray )
{
	BitSet* bts = NULL;
	
	Filter** filter = filters;
	int* logic = _logicArray;
	
	// see discussion at top of file
	if( *filter ) {
		BitSet* tmp = (*filter)->bits( reader );
		if ( (*filter)->shouldDeleteBitSet(tmp) ) //if we are supposed to delete this BitSet, then 
			bts = tmp; //we can safely call it our own
		else if ( tmp == NULL ){
			int32_t len = reader->maxDoc();
			bts = _CLNEW BitSet( len ); //bitset returned null, which means match _all_
			for (int32_t i=0;i<len;i++ )
				bts->set(i); //todo: this could mean that we can skip certain types of filters
		}
		else
		{
			bts = tmp->clone(); //else it is probably cached, so we need to copy it before using it.
		}
		filter++;
		logic++;
	}
	else
		bts = _CLNEW BitSet( reader->maxDoc() );
	
	while( *filter ) {
		doChain( bts, reader, *logic, *filter );
		filter++;
		logic++;
	}
	
	return bts;
}

void ChainedFilter::doUserChain( CL_NS(util)::BitSet* chain, CL_NS(util)::BitSet* filter, int logic ){
	_CLTHROWA(CL_ERR_Runtime,"User chain logic not implemented by superclass");
}

BitSet* ChainedFilter::doChain( BitSet* resultset, IndexReader* reader, int logic, Filter* filter )
{
	BitSet* filterbits = filter->bits( reader );
	int32_t maxDoc = reader->maxDoc();
	int32_t i=0;
	if ( logic >= ChainedFilter::USER ){
		doUserChain(resultset,filterbits,logic);
	}else{
		switch( logic ) 
		{
		case OR:
			for( i=0; i < maxDoc; i++ )
				resultset->set( i, resultset->get(i) || (filterbits==NULL || filterbits->get(i) ) );
			break;
		case AND:
			for( i=0; i < maxDoc; i++ )
				resultset->set( i, resultset->get(i) && (filterbits==NULL || filterbits->get(i) ) );
			break;
		case ANDNOT:
			for( i=0; i < maxDoc; i++ )
				resultset->set( i, !(resultset->get(i) && (filterbits==NULL || filterbits->get(i) ) ) );
			break;
		case XOR:
			for( i=0; i < maxDoc; i++ )
				resultset->set( i, resultset->get(i) ^ (filterbits==NULL || filterbits->get(i) ) );
			break;
		default:
			doChain( resultset, reader, DEFAULT, filter );
		}
	}
	
	if ( filter->shouldDeleteBitSet(filterbits) )
		_CLDELETE( filterbits );
	
	return resultset;
}

CL_NS_END
