/***************************************************************************
                          cquerymanager.cpp  -  description
                             -------------------
    begin                : Sat Jun 8 2002
    copyright            : (C) 2002-2004 by Mathias Küster
    email                : mathen@users.berlios.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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "cquerymanager.h"

#include <stdio.h>

#ifndef WIN32
#include <unistd.h>
#endif

#include "cconnectionmanager.h"
#include "cdownloadqueue.h"
#include "cdownloadmanager.h"
#include "cfilemanager.h"
#include "cconfig.h"
#include "core/cmanager.h"
#include "csearchindex.h"
#include "core/ciconv.h"
#include "core/cmutex.h"

/* for proper case-insensitive searching */
#include "ccasefolder.h"

#define MAX_SEARCH_RESULTS 		10
#define MAX_SEARCH_RESULTS_PASSIVE	 5
#define MAX_QUERY_QUEUE_COUNT		25
#define QUERY_TIMEOUT			10

#include <set>

/** */
CQueryManager::CQueryManager()
{
	m_pQueueMutex    = new CMutex();
	m_pQueryQueue    = new CList<CQueryObject>;
	m_pQuerySendList = new CList<CQuerySendObject>;

	m_nSearchCountActive  = 0;
	m_nSearchCountPassive = 0;
	m_nSearchCountReject  = 0;
	m_nSearchCountError   = 0;
	m_nResultCount        = 0;
	m_nResultCountError   = 0;

	m_pCaseFolder = new CCaseFolder();

	m_pCallback = new CCallback0<CQueryManager>( this, &CQueryManager::Callback );
	CManager::Instance()->Add( m_pCallback );
}

/** */
CQueryManager::~CQueryManager()
{
	SetInstance(0);

	CManager::Instance()->Remove( m_pCallback );

	delete m_pCallback;
	m_pCallback = 0;
	
	delete m_pCaseFolder;
	delete m_pQueryQueue;
	delete m_pQuerySendList;
	delete m_pQueueMutex;
}

/** */
bool CQueryManager::CheckSize( CQueryObject * queryobject, struct filebaseobject * fbo )
{
	bool res = true;

	// check file size
	if ( queryobject->pMessageSearch->m_bSizeLimit )
	{
		if ( queryobject->pMessageSearch->m_eSizeType == esstATMOST )
		{
			if ( fbo->m_nSize > queryobject->pMessageSearch->m_nSize )
			{
				res = false;
			}
		}
		else
		{
			if ( fbo->m_nSize < queryobject->pMessageSearch->m_nSize )
			{
				res = false;
			}
		}
	}

	return res;
}

/** */
bool CQueryManager::CheckType( CQueryObject * queryobject, struct filebaseobject * fbo )
{
	bool res = true;

	// check file type
	switch(queryobject->pMessageSearch->m_eFileType)
	{
		case eftALL:
		case eftHASH: /* the TTH: prefix is now added/removed at the lowest level */
			break;
		case eftMP3:
		case eftARCHIVE:
		case eftDOCUMENT:
		case eftAPPLICATION:
		case eftPICTURE:
		case eftVIDEO:
		case eftFOLDER:
			if ( queryobject->pMessageSearch->m_eFileType != eFileTypes(fbo->m_eFileType) )
			{
				res = false;
			}
			break;
		default:
			res = false;
			break;
	}

	return res;
}

/** */
bool CQueryManager::AddResult( CQuerySendObject * querysendobject, CQueryObject * queryobject, 
			struct filebaseobject * fbo, CString filename, CString hash )
{
	CString s, result;
	int slot_max,slot_free;
	
	CIconv ciconv( CConfig::Instance()->GetLocalEncoding(), CConfig::Instance()->GetRemoteEncoding() );

	// get slot info
	slot_max  = CConfig::Instance()->GetMaxUpload();
	slot_free = CDownloadManager::Instance()->DLM_UsedSlots();

	// unlimit slots
	if ( slot_max == 0 )
	{
		slot_max = 99+slot_free;
	}

	// check for extra slots
	if ( slot_free > slot_max )
	{
		slot_free = 0;
	}
	else
	{
		slot_free = slot_max-slot_free;
	}

	// create the searchresult
	s += ciconv.encode(filename);
	s.Swap('/','\\');
	result  = "$SR ";
	result += ciconv.encode(queryobject->sNick);
	result += ' ';
	
	// folder results do not have size
	if ( fbo->m_eFileType != eftFOLDER )
	{
		result += s;
		result += "\x5";
		result += CString::number(fbo->m_nSize);
		result += ' ';
	}
	else
	{
		s = s.Replace("\\\\", "\\");
		if ( s.NotEmpty() && (s.Data()[0] == '\\')  )
		{
			s = s.Mid( 1, s.Length() - 1 );
		}
		result += s;
		result += ' ';
	}
	
	result += CString::number(slot_free);
	result += '/';
	result += CString::number(slot_max);
	result += "\x5";
	
	// add hash or hubname to searchresult
	if ( hash.IsEmpty() )
	{
		result += ciconv.encode(queryobject->sHubName);
	}
	else
	{
		result += "TTH:";
		result += hash;
	}
	result += " (";

	result += queryobject->sHubHost;
	result += ')';

	// result found ! send it ...
	if ( queryobject->pMessageSearch->m_bLocal )
	{
		result += "\x5";
		result += ciconv.encode(queryobject->pMessageSearch->m_sSource);
		result += '|';
		querysendobject->m_pList->push_back(result);
//		printf("ADD L RESULT: '%s'\n",result.Data());
	}
	else
	{
		result += '|';
		querysendobject->m_pList->push_back(result);
//		printf("ADD G RESULT: '%s'\n",result.Data());
	}

	return true;
}

/** */
void CQueryManager::SendResults()
{
	CQuerySendObject * querysendobject = 0;

	while ( (querysendobject=m_pQuerySendList->Next(querysendobject)) != 0 )
	{
		if ( querysendobject->m_pSocket )
		{
			eConnectState e;
			
			e = querysendobject->m_pSocket->Connect( querysendobject->m_sSource, querysendobject->m_nPort, true );

			if ( e == ecsSUCCESS )
			{
				for ( std::list<CString>::const_iterator it = querysendobject->m_pList->begin(); it != querysendobject->m_pList->end(); ++it )
				{
					if ( querysendobject->m_pSocket->Write( (const unsigned char*)it->Data(), it->Length(), 2 ) <= 0 )
					{
						m_nResultCountError++;
						
						break;
					}
					else
					{
						m_nResultCount++;
					}
				}

				querysendobject->m_pSocket->Disconnect();
				m_pQuerySendList->Del(querysendobject);
			}
			else if ( e == ecsERROR )
			{
				m_pQuerySendList->Del(querysendobject);
				
				m_nResultCountError++;
			}

			break;
		}
		else
		{
			for ( std::list<CString>::const_iterator it = querysendobject->m_pList->begin(); it != querysendobject->m_pList->end(); ++it )
			{
				if ( CConnectionManager::Instance()->SendStringToConnectedServers(*it,querysendobject->m_sSource,false) == 0 )
				{
					m_nResultCountError++;
					
					break;
				}
				else
				{
					m_nResultCount++;
				}
			}

			m_pQuerySendList->Del(querysendobject);
			break;
		}
	}
}

/** */
void CQueryManager::HandleQuery( CQueryObject * queryobject )
{
	CString search;
	CString filename;
	std::set<unsigned long> * results = 0;
	struct filebaseobject FileBaseObject;
	CQuerySendObject * querysendobject;
	CString s,s1;
	long i;
	bool dummy = false;
	unsigned int resultcount;
	unsigned int maxresults;
	
	/* CFileManager now handles everything */
	std::list<CString> words;

	// no passive to passive results, otherwise set max results
	if ( queryobject->pMessageSearch->m_bLocal )
	{
		if ( CConfig::Instance() && (CConfig::Instance()->GetMode() == ecmPASSIVE) )
		{
			return;
		}
		
		maxresults = MAX_SEARCH_RESULTS_PASSIVE;
	}
	else
	{
		maxresults = MAX_SEARCH_RESULTS;
	}
	
	// get searchstring
	if ( queryobject->pMessageSearch->m_eFileType == eftHASH )
	{
		search = queryobject->pMessageSearch->m_sString.ToUpper();
	}
	else
	{
		if ( m_pCaseFolder->Fold( queryobject->pMessageSearch->m_sString, search ) == false )
		{
			// failed, fallback
			search = queryobject->pMessageSearch->m_sString.ToLower();
		}
	}

	// sanity check
	if ( search.IsEmpty() )
	{
		return;
	}

	// check for dummy search
	if ( search == "." )
	{
		dummy = true;
	}

	if ( !dummy )
	{
		if ( queryobject->pMessageSearch->m_eFileType == eftHASH )
		{
			results = CFileManager::Instance()->SearchHash( search );
		}
		else
		{
			// replace Windows dir separators with ours
			s = search;
			s.Swap('\\',DIRSEPARATOR);
			s += ' ';
			
			while( (i=s.Find(' ')) != -1 )
			{
				// get substring
				s1 = s.Left(i);
				// remove substring from string
				s  = s.Mid(i+1,s.Length()-i-1);

				i++;

				/* sanity check */
				if ( s1.IsEmpty() )
				{
					continue;
				}
				else
				{
					words.push_back(s1);
				}
			}
			
			if ( queryobject->pMessageSearch->m_bSizeLimit && !((queryobject->pMessageSearch->m_eSizeType == esstATLEAST) && (queryobject->pMessageSearch->m_nSize == 0)) )
			{
				if ( queryobject->pMessageSearch->m_eSizeType == esstATLEAST )
				{
					if ( queryobject->pMessageSearch->m_eFileType == eftALL )
					{
						results = CFileManager::Instance()->SearchAtLeast( maxresults, &words, queryobject->pMessageSearch->m_nSize );
					}
					else
					{
						results = CFileManager::Instance()->SearchAtLeast( maxresults, &words, queryobject->pMessageSearch->m_nSize, queryobject->pMessageSearch->m_eFileType );
					}
				}
				else
				{
					if ( queryobject->pMessageSearch->m_eFileType == eftALL )
					{
						results = CFileManager::Instance()->SearchAtMost( maxresults, &words, queryobject->pMessageSearch->m_nSize );
					}
					else
					{
						results = CFileManager::Instance()->SearchAtMost( maxresults, &words, queryobject->pMessageSearch->m_nSize, queryobject->pMessageSearch->m_eFileType );
					}
				}
			}
			else
			{
				if ( queryobject->pMessageSearch->m_eFileType == eftALL )
				{
					results = CFileManager::Instance()->Search( maxresults, &words );
				}
				else
				{
					results = CFileManager::Instance()->Search( maxresults, &words, queryobject->pMessageSearch->m_eFileType );
				}
			}
		}
	}

	if ( ((results != 0) && (results->size() > 0)) || dummy )
	{
		// results found ...
		resultcount = 0;

		// create resultobject
		querysendobject = new CQuerySendObject();

		// init passive or active search result
		if ( queryobject->pMessageSearch->m_bLocal == false )
		{
			querysendobject->m_pSocket = new CSocket(estUDP);
			querysendobject->m_sSource = queryobject->pMessageSearch->m_sSource;
			querysendobject->m_nPort   = queryobject->pMessageSearch->m_nPort;
		}
		else
		{
			querysendobject->m_sSource = queryobject->sHubName;
		}

		// real query
		if ( dummy == false )
		{
			std::set<unsigned long>::const_iterator results_it = results->begin();
			
			while( (results_it != results->end()) && (resultcount < maxresults) )
			{
				// get the fileobject for this resultid
				if ( CFileManager::Instance()->GetFileBaseObject( *results_it, &FileBaseObject, filename ) )
				{
					/* CFileManager now handles type and size of non-TTH searches */
					if ( queryobject->pMessageSearch->m_eFileType == eftHASH )
					{
						// check if filesize correct
						if ( CheckSize( queryobject, &FileBaseObject ) == false )
						{
							++results_it;
							continue;
						}

						// check if filetype correct
						if ( CheckType( queryobject, &FileBaseObject ) == false )
						{
							++results_it;
							continue;
						}
					}

					CString hash;
					
					hash = CFileManager::Instance()->GetHash(FileBaseObject.m_nHashIndex);
					
					// add result
					if ( AddResult( querysendobject, queryobject, &FileBaseObject, filename, hash ) == false )
					{
						break;
					}

					resultcount++;
				}
				
				++results_it;
			}
		}
		else
		{
			// add dummy result
			struct filebaseobject fbo;
			fbo.m_nSize = 0;
			AddResult( querysendobject, queryobject, &fbo, "." );
		}

		if ( querysendobject->m_pList->empty() )
		{
			delete querysendobject;
		}
		else
		{
			m_pQuerySendList->Add(querysendobject);
		}
	}
	
	if ( results )
	{
		results->clear();
		delete results;
	}
}

/** */
int CQueryManager::Callback()
{
	CQueryObject * queryobject;

	m_pQueueMutex->Lock();

	// get searches from the queue
	while ( (queryobject = m_pQueryQueue->Next(0)) != 0 )
	{
		// remove it from the list
		m_pQueryQueue->Remove(queryobject);

		// query timeout in seconds
		if ( (time(0)-queryobject->tTimeout) > QUERY_TIMEOUT )
		{
			// remove it
			delete queryobject;
			queryobject = 0;
			// reject statistic
			m_nSearchCountReject++;
		}
		else
		{
			break;
		}
	}

	m_pQueueMutex->UnLock();

	// sanity check / object not rejected
	if (queryobject)
	{
		// handle query
		HandleQuery(queryobject);
		// remove object
		delete queryobject;
	}

	// send any results from the result queue
	SendResults();

	return 0;
}

/** store all searches from the hub in a queue */
bool CQueryManager::SearchQuery( CString hubname, CString hubhost, CString nick, CMessageSearchFile * msg )
{
	bool res = false;
	CQueryObject * queryobject;

	// sanity check
	if ( msg == 0)
	{
		return res;
	}

	m_pQueueMutex->Lock();

	// active/passive search statistic
	if ( msg->m_bLocal == false )
	{
		m_nSearchCountActive++;
	}
	else
	{
		m_nSearchCountPassive++;
	}

	// queue overflow ?
	if ( m_pQueryQueue->Count() < MAX_QUERY_QUEUE_COUNT )
	{
		// sanity check
		if ( (hubname.NotEmpty()) && (hubhost.NotEmpty()) )
		{
			// create query object and add it to the queue
			queryobject = new CQueryObject();

			queryobject->sHubName = hubname;
			queryobject->sHubHost = hubhost;
			queryobject->sNick    = nick;
			queryobject->tTimeout = time(0);
			queryobject->pMessageSearch = new CMessageSearchFile();
			*queryobject->pMessageSearch = *msg;
			m_pQueryQueue->Add(queryobject);

			res = true;
		}
		else
		{
			// error statistic
			m_nSearchCountError++;
		}
	}
	else
	{
		// reject statistic
		m_nSearchCountReject++;
	}

	m_pQueueMutex->UnLock();

	return res;
}

/** query mananger stats */
ulonglong CQueryManager::GetStat( enum eSearchStat e )
{
	ulonglong res = 0;
	
	switch(e)
	{
		case essCOUNTACTIVE:
			res = m_nSearchCountActive;
			break;
		case essCOUNTPASSIVE:
			res = m_nSearchCountPassive;
			break;
		case essCOUNTREJECT:
			res = m_nSearchCountReject;
			break;
		case essCOUNTERROR:
			res = m_nSearchCountError;
			break;
		case essRESULTCOUNT:
			res = m_nResultCount;
			break;
		case essRESULTCOUNTERROR:
			res = m_nResultCountError;
			break;
		default:
			break;
	}
	
	return res;
}
