/***************************************************************************
 *   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 "kwestdaemon.h"
#include <qstringlist.h>
#include <qstring.h>

//mapping integral constants to types page 29
template <int v>
struct Int2Type
{
	enum { value = v};
};

KwestDaemon::KwestDaemon()
	: DCOPObject("KwestSearchDaemon"), /* this is the object name DCOP will expose */
          KwestSearchDaemonDCOPIface(),
#ifdef HAS_CLUCENE
	  kclucenedaemon (),
#endif
	  QObject() , 
	  m_intValue(0)
	 
{
#ifdef HAS_CLUCENE
	  m_knownSearchEngineList.append(SEARCH_AND_QUERY_FORM_URL_CLUCENE);
#endif
#ifdef HAS_LIBBEAGLE
	  m_knownSearchEngineList.append(SEARCH_AND_QUERY_FORM_URL_BEAGLE);
#endif
	NumberOfQueriesRunning = 0;
	MaxLimitOfNumberOfQueriesRunning = 5;
	//connect( this, SIGNAL(newQueryRequestIsValid(QString)) ,this , SLOT(dispatchNewQuery(QString)) ) ;

	//
	SearchThreadList.size();
	
	SearchThreadList.setAutoDelete(TRUE);
	QueryList.setAutoDelete(TRUE);
	
	
	
}



KwestDaemon::~KwestDaemon()
{
	SearchThreadList.clear();
	QueryList.clear();
	
}

void KwestDaemon::customEvent(QCustomEvent *event)
{
	//search finished
	
	QString * res = (QString *) event->data();
	kdDebug() << " KwestDaemon::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() << " KwestDaemon::customEvent. THE QUERY IS EMPTY GOOD" << *res << endl;
// 		QueryList.remove(*res);
		
	}
	else
	{
		kdDebug() << " KwestDaemon::customEvent. THEQUERY IS NOT EMPTY BAD" << *res << endl;
	
	}
	
#ifdef HAS_CLUCENE
	mutex.lock();
	SearchThread * mT = SearchThreadList.find(*res);
	if (typeid (mT) == typeid (KCLuceneThread))
// 		kclucenedaemon.deleteSearchers ((KCLuceneThread *) mT, res);
 		kclucenedaemon.deleteSearchers ( ((KCLuceneThread *)mT)->m_indexesPathList, res);
#endif
	

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

}

int  KwestDaemon::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
	// -2 : asked for beagle backend in query and beagled is not 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() << "KwestDaemon::askForNewQuery(). Socket path:" << SocketPath 
			<< " m_socketPath:" << m_socketPath << endl;
	
	
	//
	QString newQuery; 
	bool hasSearchEngine = parseSearchEnginesAndAssignQueries(query);
	if (!hasSearchEngine) 
	{
		kdDebug() << "KwestDaemon::askForNewQuery().: No searchengine found ! " << endl; 
		return -2124;
	}
	
	//so we have found a search engine 
	newQuery = m_requestedQuery;
	
#ifdef HAS_CLUCENE
	// if the searchengine is clucene
	if ( (m_requestedSearchEngine == QString(SEARCH_AND_QUERY_FORM_URL_CLUCENE)))
	{
		if (! kclucenedaemon.initCLuceneIndexes (indexesPathList))
			return -1233;
		
		
		// some magic here: should go somewehere else in the isValid test probably
		if (newQuery.contains("any:"))
		{
			newQuery.replace("any:" , "anystring:",TRUE);
		}
	}
#endif
	
	//QDictIterator<ClientList> queryIt(QueryList );
	mutex.lock();
	
	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() << "KwestDaemon::askForNewQuery() DAEMON: the same QUERY AND CLIENT already exists" << endl;
			kdDebug() << "KwestDaemon::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() << "KwestDaemon::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() << "KwestDaemon::askForNewQuery() DAEMON: Too many queries running" << endl;
			mutex.unlock();
			return -7;
		}
		else if ( ! isQueryValid(newQuery) )
		{
			kdDebug() << "KwestDaemon::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 ( SearchThreadList.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() << "KwestDaemon::askForNewQuery() FIX THIS ERROR " << endl;
				return -1111;
			}
			else
			{
				mutex.unlock();
				kdDebug() << "KwestDaemon::askForNewQuery() DAEMON GOOD QUERY " << endl;
				//start a new query, but maybe no indexes given / exists
				//FIXME: check for indexes here ?
				try {
					startQuery( newQuery  , indexesPathList , SocketPath , peer2peer);
				} catch (int i) {
					if (i == -2) // beagled not running
						return -2;
				}
				kdDebug() << "KwestDaemon::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 KwestDaemon::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 KwestDaemon::isSearchEngineSupported(const QString & requestedSearchEngine)
{
	QStringList::Iterator it = m_knownSearchEngineList.begin();
	bool found = 0;
	while ( (!found) && (it!=m_knownSearchEngineList.end() ) )
	{
		if (requestedSearchEngine == *it )
		{
			//found a matching search engine
			found = 1;
		}
		it++;
	}
	return found;
}
QString KwestDaemon::isRequestStartingWithSupportedSearchEngine(const QString & requestedSearchEngine)
{
kdDebug() << "KwestDaemon::isRequestStartingWithSupportedSearchEngine called: " << endl;
	QString SearchEngineString;
	QStringList::Iterator it = m_knownSearchEngineList.begin();
	bool found = 0;
	while ( (!found) && (it!=m_knownSearchEngineList.end() ) )
	{
		if (requestedSearchEngine.startsWith(*it))
		{
			//found a matching search engine
			found = 1;
			SearchEngineString = *it;
		}
		it++;
	}
	return SearchEngineString;
}

// get lists of search engines and 
// so far only one single searchengine per request supported
bool KwestDaemon::parseSearchEnginesAndAssignQueries(const QString & requestString)
{
	kdDebug() << "KwestDaemon::parseSearchEnginesAndAssignQueries called: " << endl;
	QString SearchEngineString = isRequestStartingWithSupportedSearchEngine(requestString);
	if (!SearchEngineString.isNull())
	{
		m_requestedSearchEngine = SearchEngineString;
		m_requestedQuery = requestString.mid(SearchEngineString.length(),requestString.length());
		kdDebug() << "KwestDaemon::parseSearchEnginesAndAssignQueries FOUND SEARCHENGINE : " << m_requestedSearchEngine << " and query is : " << m_requestedQuery << endl;
		return 1;
	}
	else
	{
		return -1;
	}
}

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

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

        // dispatch queries here according to searchengine
        // TODO: one should create a clucene/beagle query class so that one can identify a query based on its type

#ifdef HAS_CLUCENE
	//only start a clucene thread if search engine is clucene
	if (m_requestedSearchEngine == QString(SEARCH_AND_QUERY_FORM_URL_CLUCENE))
	{
		kclucenedaemon.prepareIndexForQuery (indexesPathList);
		kdDebug() << "KwestDaemon::startQuery after Mutex    : " << endl;
	
		SearchThreadList.insert( newQuery , 
			     	new KCLuceneThread(
					this , 
					newQuery ,
					SocketPath , 
					&QueryList , 
					kclucenedaemon,
					indexesPathList ,           //FIXME: REENTRANT !!
					peer2peer)
			    );
		SearchThreadList[newQuery]->start();
	}
#endif
	
#ifdef HAS_LIBBEAGLE
	if (m_requestedSearchEngine == QString(SEARCH_AND_QUERY_FORM_URL_BEAGLE))
	{
		SearchThreadList.insert (newQuery,
				    new BeagleThread(
					    this,
					    newQuery,
					    SocketPath,
					    &QueryList,
					    peer2peer)
			    );
		SearchThreadList[newQuery]->start();
	}
#endif
	
	mutex.unlock();
	
	//unused return statment
	return 0;
}


#include "kwestdaemon.moc"	
