/***************************************************************************
 *   Copyright (C) 2005 by xavier helluy   *
 *   xavier.helluy@web.de   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.             *
 ***************************************************************************/

#include <kapplication.h>
#include <kdebug.h>
#include <qobject.h>
#include <dcopclient.h>
#include "clucenedaemon.h"
#include <qstringlist.h>
#include <qstring.h>



CLuceneDaemon::CLuceneDaemon()
	: DCOPObject("KCLuceneSearchDaemon"), /* this is the object name DCOP will expose */
          KCLuceneSearchDaemonDCOPIface(),
	  QObject() , 
	  m_intValue(0)
	 
{
	NumberOfQueriesRunning = 0;
	MaxLimitOfNumberOfQueriesRunning = 5;
	//connect( this, SIGNAL(newQueryRequestIsValid(QString)) ,this , SLOT(dispatchNewQuery(QString)) ) ;

	//
	MyThreadsList.size();
	
	MyThreadsList.setAutoDelete(TRUE);
	QueryList.setAutoDelete(TRUE);
	m_indexMultiSearchersList.setAutoDelete(TRUE);
	m_indexSingleSearchersList.setAutoDelete(TRUE);
	
}



CLuceneDaemon::~CLuceneDaemon()
{
	MyThreadsList.clear();
	QueryList.clear();
	m_indexMultiSearchersList.clear();
	m_indexSingleSearchersList.clear();
	
}

void CLuceneDaemon::customEvent(QCustomEvent *event)
{
	//search finished
	
	QString * res = (QString *) event->data();
	kdDebug() << " CLuceneDaemon::customEvent. THE Query and its corresponding THREAD FINISHED: " << *res << endl;
	
	//the thread should have deleted all socket associated to the query *res in QueryList 
	//check that it is true
	if ( QueryList.find(*res)->isEmpty() )
	{
		kdDebug() << " CLuceneDaemon::customEvent. THE QUERY IS EMPTY GOOD" << *res << endl;
// 		QueryList.remove(*res);
		
	}
	else
	{
		kdDebug() << " CLuceneDaemon::customEvent. THEQUERY IS NOT EMPTY BAD" << *res << endl;
	
	}
	
	mutex.lock();
	//TODO DELETING SEARCHERS
	//searchers are closed and deleted as soon as no one needs them.

	MyThread2 * mT = MyThreadsList.find(*res);		
	QStringList indexesPathList = mT->m_indexesPathList;
	//increment indexList counters indexesPathList
	for ( QStringList::Iterator it = indexesPathList.begin(); it != indexesPathList.end(); ++it )
	{
		//conditional test here but it MUST exist
		QMap<QString, int>::Iterator it2 = m_indexSingleSearchersListCounter.find(*it);
		if (it2 != m_indexSingleSearchersListCounter.end() ) 
		{
			kdDebug() << "CLuceneDaemon::customEvent" << it2.data() << endl;
			it2.data() = it2.data() -1;
			kdDebug() << "CLuceneDaemon::customEvent B" << it2.data() << endl;
			if (it2.data() == 0)
			{
				kdDebug() << "CLuceneDaemon::customEventDELETE SINGLE" << it2.data() << endl;
				//fixme. I could get the index value from m_indexSingleSearchersListCounter
				lucene::search::IndexSearcher * idS = m_indexSingleSearchersList.find(*it);
				idS->close();
				kdDebug() << "CLuceneDaemon::customEventDELETE SINGLE CLOSED" << it2.data() << endl;

// 				delete idS;
				kdDebug() << "CLuceneDaemon::customEventDELETE SINGLE DELETED" << it2.data() << endl;

				m_indexSingleSearchersList.remove(*it);
				kdDebug() << "CLuceneDaemon::customEvent BDELETE SINGLE REMOVED"  << endl;
			}
		}
	}
	
	//increment multiIndexList counters indexesPathList
	const QString indexKey = indexesPathList.join("-");
	QMap<QString, int>::Iterator it2 = m_indexMultiSearchersListCounter.find(indexKey); 	
	if ( it2 !=  m_indexMultiSearchersListCounter.end())
	{
		kdDebug() << "CLuceneDaemon::customEvent" << it2.data() << endl;
		it2.data() = it2.data() -1;
		kdDebug() << "CLuceneDaemon::customEvent" << it2.data() << endl;
		if (it2.data() == 0)
		{
			kdDebug() << "CLuceneDaemon::customEvent DELETE MULTI" << it2.data() << endl;
			m_indexMultiSearchersList.remove(indexKey);
			kdDebug() << "CLuceneDaemon::customEvent DELETE MULTI FINISHED" << endl;

		}
	}

	
	
	//the removed thread is such that no more queued same-queries are existing
	/*MyThreadsList.find(*res)->terminate();
	MyThreadsList.find(*res)->wait();*/
	MyThreadsList.remove(*res);
	//finally delete the query entry
	QueryList.remove(*res);
	NumberOfQueriesRunning--;
	mutex.unlock();
	delete res;
	kdDebug() << "CLuceneDaemon::customEvent THE END" << endl;
	kdDebug() << "CLuceneDaemon::customEvent OF SLEEPING........" << endl;

}

int  CLuceneDaemon::askForNewQuery( const QString query ,const  QString SocketPath , 
			      QStringList indexesPathList , const int /*MaxResults*/,
			     const bool /*html*/ , const bool peer2peer)

{
	// 1  : new query has been accepted with Id one
	// 2...Max=5 ; new query has been accepted with Id 2...
	// -1  : bad query syntax
	// 0 : too many queries running
	// -2000 : you are asking for a query you already have asked for and being processed!!!
	
// 	int IsQueryAccepted = -100;
	DCOPClient *dc = this->callingDcopClient();
	
	if (!dc) return -1111;
	
	QCString appid = dc->senderId();	
	m_socketPath = SocketPath;
	//m_indexesPathList = indexesPathList;
	kdDebug() << "CLuceneDaemon::askForNewQuery(). Socket path:" << SocketPath 
			<< " m_socketPath:" << m_socketPath << endl;
	
	
	// sort list of indexes
	//first look if list already is in use then if not check if it is valid.
	//if (IndexVal)
	QStringList subList;
	bool receivedListIsGood =  initIndexes(indexesPathList);
	bool subListIsGood = FALSE;
	if (! receivedListIsGood)
	{
		kdDebug() << "CLuceneDaemon::askForNewQuery() DAEMON BAD INDEX FILES TRYING TO GET SUBSPACE" << endl;
		subList = getValidSubList(indexesPathList);
		subListIsGood = initIndexes(subList);
	}
	//try to use some of the given indexes
	if ( (!receivedListIsGood) && (! subListIsGood) )
	{
		kdDebug() << "CLuceneDaemon::askForNewQuery() NO SUBSPACE FOUND " << endl;
		return -1233;
	}
	else if ( (!receivedListIsGood) && (subListIsGood) )
	{
		kdDebug() << "CLuceneDaemon::askForNewQuery() SUBSPACE FOUND " << endl;
		indexesPathList = subList;
	}
	
	
	//QDictIterator<ClientList> queryIt(QueryList );
	mutex.lock();
	
	//TODO shortcut check if anystring enabled, still not as good as a single entry
	QString newQuery = query;
	if (newQuery.contains("any:"))
	{
		newQuery.replace("any:" , "anystring:",TRUE);
	}

	if ( QueryList.find(newQuery)) //returns 0 if 
	{
		//query already exists !:
		//check if the client is another one
		if ( QueryList[newQuery]->find(SocketPath) )
		{
			// a SocketPath can only process 1 QUERY 
			//query already exists and client already exist TOO
			kdDebug() << "CLuceneDaemon::askForNewQuery() DAEMON: the same QUERY AND CLIENT already exists" << endl;
			kdDebug() << "CLuceneDaemon::askForNewQuery() I will IGNORE YOUR NEW QUERY" << endl;
			mutex.unlock();
			return -2000;
		}
		else
		{
			//create a queued item
			//FIXME: I have to use a QString cast here. So redefine all socketpath to be QString ?
			
			kdDebug() << "CLuceneDaemon::askForNewQuery() DAEMON: I CREATE A QUEUE ITEM !" << endl;
			QueryList[newQuery]->insert( SocketPath , new int(1)); // int value is egal
			mutex.unlock();
			return 1;
		}
	}
	else
	{
		//query not yet existing 
		if ( this->NumberOfQueriesRunning > MaxLimitOfNumberOfQueriesRunning )
		{
			//too many queries
			kdDebug() << "CLuceneDaemon::askForNewQuery() DAEMON: Too many queries running" << endl;
			mutex.unlock();
			return -7;
		}
		else if ( ! isQueryValid(newQuery) )
		{
			kdDebug() << "CLuceneDaemon::askForNewQuery() DAEMON :ERROR QUERY IS BAD" << endl;
			//bad query syntax
			mutex.unlock();
			return -3;
		} 
		else
		{
			//new query accepted
			//but check no Thread still referes to a query having beennamed the same
			if ( MyThreadsList.find(newQuery) )
			{
				//FIXME: ?????????? a rare case but not solved !!!!
				//Problem: the thread finished old queries being the same but as not yet been 
				//deleted. Wait a bit or delete it yourself here.
				//	safeDeleteThread(newQuery);
				mutex.unlock();
				kdError() << "CLuceneDaemon::askForNewQuery() FIX THIS ERROR " << endl;
				return -1111;
			}
			else
			{
				mutex.unlock();
				kdDebug() << "CLuceneDaemon::askForNewQuery() DAEMON GOOD QUERY " << endl;
				//start a new query, but maybe no indexes given / exists
				//FIXME: check for indexes here ?
				startQuery( newQuery  , indexesPathList , SocketPath , peer2peer);
				kdDebug() << "CLuceneDaemon::askForNewQuery() AFTER STARTQUERY  " << endl;

				return 1;
			}
		}
	}
	
};

//needs to be notified when the results have been read.
//a real cache engine could be implemented, but it is not important
//and ahistory_cache will be implemented by a datamining software.
bool CLuceneDaemon::isQueryValid(const QString & Query)
{
	kdDebug() << "QUERYTEST: " << Query << endl;
	bool IsQueryValid = FALSE;
	if ( Query.isEmpty() ) 
		IsQueryValid = FALSE;//FIXME: return Message Error  to the ioslave
	else if ( Query.startsWith("*") ) 
	{ 
		IsQueryValid = FALSE;//FIXME: return Message Error  to the ioslave
	}
	else if ( Query.startsWith("?") ) 
	{ 
		IsQueryValid = FALSE;//FIXME: return Message Error to the ioslave
	}
	else 
	{ 
		IsQueryValid = TRUE; 
	}
	
	//check here if the query will not crash CLucene
	// * ?
	
	return IsQueryValid;
}


bool CLuceneDaemon::isIndexesListValid(QStringList & /*indexesPathList*/)
{
	return TRUE;
}


int CLuceneDaemon::initIndexes(QStringList indexesPathList)
{
	//sort PathList
	indexesPathList.sort();
	
	//check the whole list is already registered as such
	//if single Index check on ItemList
	if ( indexesPathList.count()==1 )
	{
		kdDebug() << "DAEMON: INIT SINGLE INDEX STARTED" << endl;
		//sset a bool constant to Single ???
		if (! initSingleIndex( indexesPathList ))
			return 0;
		else
			return 1;
	}
	else
	{
		kdDebug() <<  "DAEMON: MULTI-INDEX STARTED" << endl;
		if (! initMultiIndex(indexesPathList) ) 
			return 0;
		else
			return 1;
	}
	
}

int CLuceneDaemon::initSingleIndex( QStringList indexesPathList )
{
	//should only one single item
	if ( m_indexSingleSearchersList.find( *(indexesPathList.begin())) )
	{
		kdDebug() << "DAEMON: INIT SINGLE-INDEXE: INDEX ALRERADY registered" << endl;
		return 1;
	}
	else if ( ! isIndexesPathListValid(indexesPathList) )
	{
		kdDebug() << "DAEMON: INIT MULTIINDEXEXES: NEW INDEX LIST IS NOT VALID" << endl;
		kdDebug() <<"DAEMON: INIT MULTIINDEXEXES: TRY TO GET SOME" << endl;
		
		return  0;
	}
	else
	{
		kdDebug() << "DAEMON: SINGLE INIT INDEX: NEW INDEX LIST IS VALID. GOOD" << endl;
		
		InitAllSingleSearchers(indexesPathList);
		//dann init MultiSearch
		return  1;

	}

}

int CLuceneDaemon::initMultiIndex( QStringList  indexesPathList)
{
	QString indexKey = indexesPathList.join("-");
	if (m_indexMultiSearchersList.find(indexKey) )
	{
		kdDebug() << "CLuceneDaemon::initMultiIndex()DAEMON: INIT MULTI-INDEXEXES: INDEX ALRERADY registered" << endl; 
		//don't increment multiIndexCounter here because 
		//it is not sure that the query is accepted
		//do it when a newQuery starts
		
		//FIXME : what should I return ? 1 is SUCCESS.
		return 1;
	}
	else if ( ! isIndexesPathListValid(indexesPathList) )
	{
		kdDebug() << "CLuceneDaemon::initMultiIndex() DAEMON: INIT MULTIINDEXEXES: NEW INDEX LIST IS NOT VALID" << endl; 
		return  0;
	}
	else
	{
		kdDebug() << "CLuceneDaemon::initMultiIndex() DAEMON: MULTIINIT INDEXEXES: NEW INDEX LIST IS VALID. GOOD" << endl; 
		// register each item first
		
		InitAllSingleSearchers(indexesPathList);
		InitAllMultiSearchers(indexesPathList);
		return  1;
	}

}



int CLuceneDaemon::startQuery( QString  newQuery  , QStringList indexesPathList , QString SocketPath , bool  peer2peer)
{
	kdDebug() << "CLuceneDaemon::startQuery query: " << newQuery << endl;
	kdDebug() << "CLuceneDaemon::startQuery indexes: " << indexesPathList << endl;
	kdDebug() << "CLuceneDaemon::startQuery socket : " << SocketPath << endl;
	kdDebug() << "CLuceneDaemon::startQuery p2p    : " << peer2peer << endl;

	NumberOfQueriesRunning++;
	
	mutex.lock();
	
	QueryList.insert(newQuery, new ClientList());
	QueryList[newQuery]->insert(SocketPath , new int(1));
	kdDebug() << "CLuceneDaemon::startQuery after querylist......" << indexesPathList.count() << endl;

	//increment indexList counters indexesPathList
	
	kdDebug() << "CLuceneDaemon::startQuery after querylist......" << indexesPathList.count() << endl;

	for ( QStringList::Iterator it = indexesPathList.begin(); it != indexesPathList.end(); ++it )
	{
		kdDebug() << "CLuceneDaemon::startQuery *it    : " << endl;

		kdDebug() << "CLuceneDaemon::startQuery *it    : " << *it << endl;

		//conditional test here but it MUST exist
		QMap<QString, int>::Iterator it2 = m_indexSingleSearchersListCounter.find(*it);
		if (it2 != m_indexSingleSearchersListCounter.end() ) 
		{
			kdDebug() << "CLuceneDaemon::startQueryA" << it2.data() << endl;
			it2.data() = it2.data() +1;
			kdDebug() << "CLuceneDaemon::startQueryB" << it2.data() << endl;
		}
	}
	
	kdDebug() << "CLuceneDaemon::startQuery after AB    : " << endl;

	//increment multiIndexList counters indexesPathList
	const QString indexKey = indexesPathList.join("-");
	QMap<QString, int>::Iterator it2 = m_indexMultiSearchersListCounter.find(indexKey); 	
	if ( it2 !=  m_indexMultiSearchersListCounter.end())
	{
		kdDebug() << "CLuceneDaemon::startQueryA" << it2.data() << endl;
		it2.data() = it2.data() +1;
		kdDebug() << "CLuceneDaemon::startQueryB" << it2.data() << endl;
	}
	mutex.unlock();
	kdDebug() << "CLuceneDaemon::startQuery after Mutex    : " << endl;

	
	MyThreadsList.insert( newQuery , 
			     	new MyThread2(
					this , 
					newQuery ,
					SocketPath , 
					&QueryList , 
					m_indexSingleSearchersList, //FIXME: REENTRANT !!
					m_indexMultiSearchersList , //FIXME: REENTRANT !!
					indexesPathList ,           //FIXME: REENTRANT !!
					peer2peer)
			    );
	MyThreadsList[newQuery]->start();
	
	//unused return statment
	return 0;
}

QStringList CLuceneDaemon::getValidSubList(QStringList indexesPathList)
{
	QStringList newList;
	for ( QStringList::Iterator it = indexesPathList.begin(); it != indexesPathList.end(); ++it )
	{
		
		kdDebug() <<  "DAEMON: INDEXLISTITEM " << *it <<endl;
		//const char* str = (*it).latin1();//FIXME:
		char* p_str = (char*) (const char*) (*it).latin1();
		if (lucene::index::IndexReader::indexExists( p_str ) ) //str directory path to the index
		{
			newList.append(*it);
		}
	}
	return newList;
	

}

bool CLuceneDaemon::isIndexesPathListValid(QStringList indexesPathList)
{
	for ( QStringList::Iterator it = indexesPathList.begin(); it != indexesPathList.end(); ++it )
	{
		
		kdDebug() <<  "DAEMON: INDEXLISTITEM " << *it <<endl;
		//const char* str = (*it).latin1();//FIXME:
		char* p_str = (char*) (const char*) (*it).latin1();
		if (! lucene::index::IndexReader::indexExists( p_str ) ) //str directory path to the index
		{
			kdDebug() <<  "DAEMON: INDEXLISTITEM " << *it << "DOES NOT EXIST"<< endl;
			return FALSE;
		}
		
		//static FSDirectory& getDirectory(const TCHAR* file, const bool create); return Directory
		
		
	}
	return TRUE;
}

bool CLuceneDaemon::InitAllSingleSearchers(QStringList & indexesPathList)
{
	for ( QStringList::Iterator it = indexesPathList.begin(); it != indexesPathList.end(); ++it )
	{
		if (!  m_indexSingleSearchersList.find(*it) ) 
		{
			kdDebug() <<  "DAEMON: INDEXLISTITEM " << *it << "DOES NOT EXIST"<<endl;
			char* p_str = (char*) (const char*) (*it).latin1(); //FIXME:
			//init IndexReader Thread Safe
			m_indexSingleSearchersList.insert( *it , new lucene::search::IndexSearcher(p_str ) );
			//init counter	
			m_indexSingleSearchersListCounter.insert( *it ,0 );
		}
		
	}
	return TRUE;
}

bool CLuceneDaemon::InitAllMultiSearchers(QStringList & indexesPathList)
{
	const QString indexKey = indexesPathList.join("-");
	if (m_indexMultiSearchersList.find(indexKey))
	{
		kdDebug() <<"DAEMON: MULTIINDEX   ALREADY REGISTERED" << endl;
		return TRUE;
	}
	else
	{
		kdDebug() << "DAEMON: MULTIINDEX   NOW BEING REGISTERED\n" << endl;
		m_indexMultiSearchersList.insert(indexKey , new QStringList(indexesPathList) );
		//multiSearch array will be build on thread creation
		m_indexMultiSearchersListCounter.insert(indexKey , 0);
		kdDebug() << "DAEMON: MULTIINDEX   NOW  REGISTERED" << endl;

	}
		return TRUE;
}



#include "clucenedaemon.moc"	
