/***************************************************************************
                          csearchmanager.cpp  -  description
                             -------------------
    begin                : Thu May 27 2004
    copyright            : (C) 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 "csearchmanager.h"

#include <stdio.h>
#include <stdlib.h>

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

#include "dclib.h"
#include "dcobject.h"
#include "cconfig.h"
#include "core/cmanager.h"
#include "cconnectionmanager.h"
#include "cfilemanager.h"

#include "cdownloadmanager.h"

#define SEARCH_TIMEOUT				60
#define SEARCH_CLIENT_TIMEOUT			60

// no longer defined because we are using a configured value
//define SEARCH_SPAM_TIMEOUT			60

#define SEARCH_PASSIVE_CLIENT_REMOVE_TIMEOUT	5
#define SEARCH_ACTIVE_CLIENT_REMOVE_TIMEOUT	60

/** */
CSearchManager::CSearchManager()
{
	SearchState(esNONE);

	m_eSearchType  = estyNONE;
	m_eClientMode  = ecmNONE;

	m_pClientList  = 0;
	m_pHubList     = 0;
	m_nMaxClients  = 0;
	m_sCurrentHub  = 0;
	m_pParentCallback = 0;

	m_bEnableTag   = false;
	m_bHandleUserList = false;

	// set manager callback
	m_pCallback = new CCallback0<CSearchManager>( this, &CSearchManager::CallBackManager );
	CManager::Instance()->Add( m_pCallback );
	
	// set udp socket callback
	m_SearchSocket.SetCallBackFunction( new CCallback1<CSearchManager, CMessageSearchResult*>( this, &CSearchManager::CallBackSearchSocket ) );
}

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

	// disconnect listen udp socket
	m_SearchSocket.Disconnect();

	if ( CManager::Instance() )
		CManager::Instance()->Remove( m_pCallback );
	delete m_pCallback;
	m_pCallback = 0;
}

/** */
long CSearchManager::HubCount()
{
	long res = 0;

	m_Mutex.Lock();
	
	if ( m_pHubList )
	{
		res = m_pHubList->Count();
	}
	
	m_Mutex.UnLock();

	return res;	
}

/** */
eSearchError CSearchManager::StartSearch( eSearchMode mode, eSearchType type, CList<CDCMessage> * querylist, CStringList<CString> * serverlist )
{
	if ( SearchState() != esNONE )
	{
		return eseALREADYRUN;
	}

	if ( !querylist )
	{
		DPRINTF("empty query list\n");
		return eseNONE;
	}

	// check hubs
	if ( (mode != esmCONNECTEDSINGLE) &&
	     (mode != esmCONNECTEDALL) )
	{
		// get hublist
		if ( !serverlist )
		{
			if ( mode == esmPUBLIC )
			{
				// get public hublist
				serverlist = CConfig::Instance()->GetPublicHubServerList();
			}
			else if ( mode == esmBOOKMARK )
			{
				// get bookmark hublist
				serverlist = CConfig::Instance()->GetBookmarkHubServerList();
			}
		}
		
		// check hublist
		if ( !serverlist )
		{
			DPRINTF("empty hublist\n");
			return eseNONE;
		}
		
		if ( serverlist->Count() == 0 )
		{
			DPRINTF("empty hublist 0\n");
			return eseNONE;
		}
	}
	else
	{
		// get connected hub count
		if ( CConnectionManager::Instance()->GetConnectedHubCount() == 0 )
		{
			DPRINTF("no connected hub\n");
			return eseNONE;
		}

		if ( mode == esmCONNECTEDSINGLE )
		{
			if ( !serverlist || (serverlist->Count() == 0) )
			{
				DPRINTF("empty hublist\n");
				return eseNONE;
			}
		}
	}

	// start listen
	if ( CConfig::Instance()->GetMode() != ecmPASSIVE )
	{
		m_eClientMode = ecmACTIVE;

		if ( m_SearchSocket.Connect( CString(), CConfig::Instance()->GetUDPListenPort(), false ) != ecsSUCCESS )
		{
			return eseNONE;
		}
	}
	else
	{
		m_eClientMode = ecmPASSIVE;
	}

	// now we can start the search
	m_eSearchType  = type;
	m_eSearchMode  = mode;
	m_nHubIndex    = 0;
	m_nHubError    = 0;

	m_pCurrentSearchObject = 0;
	m_pSearchList          = querylist;
	m_pHubList             = serverlist;

	// set start time
	m_tStartTime = time(0);
	// reset timeout
	m_tTimeoutTime = 0;
	
	SearchState(esSEARCH);
	
	DPRINTF("start search\n");
	
	DPRINTF("M: %d T: %d QC: %ld\n",
		mode,
		type,
		querylist->Count());

	return eseNONE;
}

/** */
void CSearchManager::StopSearch()
{
	if ( SearchState() != esNONE )
	{
		SearchState(esSTOP);
		// disconnect listen udp socket
		m_SearchSocket.Disconnect();
	}
}

/** */
int CSearchManager::CallBackManager()
{
	// no search running
	if ( SearchState() == esNONE )
	{
		return 0;
	}
	
	m_SearchSocket.Receive();

	// check for search timeout
	if ( SearchState() == esTIMEOUT )
	{
//		DPRINTF("timeout\n");
		if ( (time(0)-m_tTimeoutTime) >= SEARCH_TIMEOUT )
		{
			StopSearch();
		}
	}

	// check for stop search
	if ( SearchState() == esSTOP )
	{
		DPRINTF("stop\n");
		// disconnect all clients
		DisconnectClients();
		
		// remove all clients
		if ( RemoveClients() )
		{
			delete m_pClientList;
			m_pClientList = 0;
			
			DPRINTF("end\n");
			// no more clients, reset search
			SearchState(esNONE);
			// disconnect listen udp socket
			m_SearchSocket.Disconnect();
			
			m_eSearchType = estyNONE;
			
			return 0;
		}
	}

	// update all clients
	UpdateClients();

	if ( SearchState() == esSEARCH )
	{
		switch (m_eSearchMode)
		{
			case esmCONNECTEDSINGLE:
			case esmCONNECTEDALL:
				if ( (time(0) - m_tTimeoutTime) >= (CConfig::Instance()->GetAutoSearchInterval()) )
				{
					if ( DoSearch(0) == false )
					{
						SearchState(esTIMEOUT);
					}
					
					m_tTimeoutTime = time(0);
				}
				break;

			case esmPUBLIC:
			case esmBOOKMARK:
				bool b1,b2;
				// add new clients
				b1 = AddClients();
				// remove disconnected clients
				b2 = RemoveClients();
				
				if ( (b1 == false) && b2 )
				{
					// search done
					SearchState(esTIMEOUT);
					m_tTimeoutTime = time(0);
				}
				break;
			default:
				break;
		}
	}

	return 0;
}

/** */
bool CSearchManager::AddClients()
{
	if ( m_pHubList == 0 )
	{
		return false;
	}

	if ( m_pHubList->Count() == m_nHubIndex )
	{
		return false;
	}
	
	m_Mutex.Lock();
	
	if ( m_pClientList == 0 )
	{
		m_pClientList = new CList<CSearchClient>();
	}
	
	while( m_pClientList->Count() < m_nMaxClients )
	{
		if ( AddClient() == false )
		{
			break;
		}
	}
	
	m_Mutex.UnLock();
	
	return true;
}

/** */
bool CSearchManager::AddClient()
{
	CSearchClient * client;

	// check for hublist done
	if ( m_pHubList->Next(&m_sCurrentHub) == 0 )
	{
		DPRINTF("no new hubserver\n");
		return false;
	}
	
	m_nHubIndex++;
	
	DPRINTF("add client: '%s'\n",m_sCurrentHub->Data());

	if (m_sCurrentHub != 0)
	{
		CMessageLog * msglog = new CMessageLog();
		msglog->sMessage += "Add client on ";
		msglog->sMessage += *m_sCurrentHub;
		//DPRINTF("%s\n", msglog->sMessage.Data());
		if ( !SendObject(msglog) )
		{
			delete msglog;
		}
	}
	//else
	//{
	//	DPRINTF("m_sCurrentHub == 0\n");
	//}

	client = new CSearchClient();
	
	// set default values for search
	client->m_tSearchTimeout = time(0);

	// client settings
	client->SetNick(CConfig::Instance()->GetSearchNick());
	client->SetComment(CConfig::Instance()->GetDescription(!m_bEnableTag));
	client->SetConnectionType(CConfig::Instance()->GetSpeed());
	client->SetEMail(CConfig::Instance()->GetEMail());
	client->SetShareSize(CFileManager::Instance()->GetShareSize());
	client->SetMode(CConfig::Instance()->GetMode());
	client->SetHandleUserList(m_bHandleUserList);
	client->SetHandleSearch(false);
	client->SetHandleMyinfo(false);
	client->SetHandleForceMove(false);
	client->SetHandleTransfer(false);
	client->SetCallBackFunction( new CCallback2<CSearchManager, CClient, CDCMessage*>( this, &CSearchManager::CallBackClient ) );

//	SendDebug( "Start search on: [" + CString::number(m_nCurrentHub) + "] " + server );

	// add client to client list
	m_pClientList->Add(client);

	// final connect
	client->Connect(*m_sCurrentHub);
	
	return true;
}

/** */
void CSearchManager::UpdateClients()
{
	m_Mutex.Lock();

	// sanity check
	if ( m_pClientList != 0 )
	{
		CSearchClient * client = 0;

		// update all clients from the client list
		while ( ( (client = m_pClientList->Next(client)) != NULL) )
		{
			client->Thread();
			
			// connection timeout
			if ( (client->m_bSearchRemove == false) &&
			     (client->m_bSearchEnable == false) )
			{
				if ( (time(0) - client->m_tSearchTimeout) >= SEARCH_CLIENT_TIMEOUT )
				{
					DPRINTF("remove client timeout\n");
					
					// remove client, connection timeout
					client->m_bSearchRemove = true;
					client->m_tSearchTimeout = 0;
				}
			}
			// send search
			else if ( client->m_bSearchEnable )
			{
				if ( (time(0) - client->m_tSearchTimeout) >= (CConfig::Instance()->GetAutoSearchInterval()) )
				{
					if ( DoSearch(client) == false )
					{
						// no more searches
						client->m_bSearchEnable = false;
						client->m_bSearchRemove = true;
					}
					
					// update timeout
					client->m_tSearchTimeout = time(0);
					
					DPRINTF("search enabled\n");
				} 
			}
		}
	}
	
	m_Mutex.UnLock();
}

/** */
bool CSearchManager::RemoveClients()
{
	bool res;
	
	m_Mutex.Lock();

	res = false;

	// sanity check
	if ( m_pClientList != 0 )
	{
		CSearchClient * client = 0;

		// remove clients from the client list
		while ( ( (client = m_pClientList->Next(client)) != NULL) )
		{
			if ( client->m_bSearchRemove )
			{
				if ( client->m_tSearchTimeout == 0 )
				{
					client->SetCallBackFunction(0);
				
					m_pClientList->Remove(client);
					delete client;
				
					client = 0;
				}
				// remove timeout
				else
				{
					if ( ((m_eClientMode == ecmACTIVE) &&
					      ((time(0) - client->m_tSearchTimeout) >= SEARCH_ACTIVE_CLIENT_REMOVE_TIMEOUT)) ||
					     ((m_eClientMode == ecmPASSIVE) &&
					      ((time(0) - client->m_tSearchTimeout) >= SEARCH_PASSIVE_CLIENT_REMOVE_TIMEOUT)) )
					{
						client->m_tSearchTimeout = 0;
						client->Disconnect(true);
					}
				}
			}
		}
		
		// ready on empty client list
		if ( m_pClientList->Count() == 0 )
		{
			res = true;
		}
	}
	else
	{
		res = true;
	}
		
	m_Mutex.UnLock();
	
	return res;
}

/** */
void CSearchManager::DisconnectClients()
{
	m_Mutex.Lock();

	// sanity check
	if ( m_pClientList != 0 )
	{
		CSearchClient * client = 0;

		// disconnect all clients
		while ( ( (client = m_pClientList->Next(client)) != NULL) )
		{
			// disconnect client
			if ( (client->GetConnectionState() != estNONE) &&
			     (client->GetConnectionState() != estDISCONNECTING) )
			{
				client->Disconnect(true);
			}
		}
	}
	
	m_Mutex.UnLock();
}

/** send search to the client */
bool CSearchManager::DoSearch( CSearchClient * HubSearchClient )
{
	bool res = false;

	if ( HubSearchClient != 0 )
	{
		// active search, get current search object
		m_pCurrentSearchObject = HubSearchClient->m_pCurrentSearchObject;
	}
	
	while ( (res == false) && ((m_pCurrentSearchObject=m_pSearchList->Next(m_pCurrentSearchObject)) != 0) )
	{
		switch ( m_pCurrentSearchObject->m_eType )
		{
			case DC_MESSAGE_SEARCH_USER:
			{
				CMessageSearchUser * msg = (CMessageSearchUser*)m_pCurrentSearchObject;

				if ( !HubSearchClient )
				{
					CList<DCHubObject> list;
					DCHubObject * HubObject;
					CString s;
					
					if ( m_eSearchMode == esmCONNECTEDSINGLE )
					{
						m_sCurrentHub = 0;
						if ( m_pHubList->Next(&m_sCurrentHub) != 0 )
							s = *m_sCurrentHub;
					}

					if ( CConnectionManager::Instance()->IsUserOnline( msg->m_sNick, s, CString(), &list ) )
					{
						HubObject = 0;

						while ( (HubObject=list.Next(HubObject)) != 0 )
						{
							CMessageSearchResultUser * MessageSearchResultUser = new CMessageSearchResultUser();

							MessageSearchResultUser->m_sHubName = HubObject->m_sHubName;
							MessageSearchResultUser->m_sNick    = msg->m_sNick;

							if ( !SendObject(MessageSearchResultUser) )
								delete MessageSearchResultUser;
						}

						list.Clear();
					}
				}
				else
				{
					if ( HubSearchClient->UserList()->IsUserOnline( msg->m_sNick ) )
					{
						CMessageSearchResultUser * MessageSearchResultUser = new CMessageSearchResultUser();

						MessageSearchResultUser->m_sHubName = HubSearchClient->GetHubName();
						MessageSearchResultUser->m_sNick    = msg->m_sNick;

						if ( !SendObject(MessageSearchResultUser) )
							delete MessageSearchResultUser;
					}
				}
				
				break;
			}

			case DC_MESSAGE_SEARCH_FILE:
			{
				CMessageSearchFile * msg = (CMessageSearchFile*)m_pCurrentSearchObject;

				if ( m_eSearchType == estyMULTI )
					msg->m_bMulti = true;
				else
					msg->m_bMulti = false;
				
				DPRINTF("send search\n");
				
				if ( HubSearchClient )
				{
					HubSearchClient->SendSearch(msg);
				}
				else
				{
					if ( m_eSearchMode == esmCONNECTEDSINGLE )
					{
						m_sCurrentHub = 0;
						
						if ( m_pHubList->Next(&m_sCurrentHub) != 0 )
						{
							CConnectionManager::Instance()->SendSearchToConnectedServers(msg,*m_sCurrentHub);
						}
					}
					else
					{
						if ( SearchType() == estyEXTERNAL )
						{
							m_CallbackMutex.Lock();
							
							if ( m_pParentCallback != 0 )
							{
								CMessageLog * log = new CMessageLog();
								log->sMessage  = "Auto search for \"";
								log->sMessage += msg->m_sString;
								log->sMessage += "\" on all connected hubs";
								
								if ( m_pParentCallback->notify( log ) != 0 )
								{
									delete log;
								}
							}
							
							m_CallbackMutex.UnLock();
						}
						CConnectionManager::Instance()->SendSearchToConnectedServers(msg);
					}
				}
				
				res = true;

				break;
			}

			default:
			{
				break;
			}
		}

	}

	if ( HubSearchClient != 0 )
	{
		// active search, set last search object
		HubSearchClient->m_pCurrentSearchObject = m_pCurrentSearchObject;
	}
	
	// check for last search
	if ( !m_pCurrentSearchObject )
		res = false;
	else if ( !m_pSearchList->Next(m_pCurrentSearchObject) )
		res = false;
	
	return res;
}

/** */
bool CSearchManager::SendObject( CDCMessage * dcmessage )
{
	bool res = false;

	if ( SearchType() == estyEXTERNAL )
	{
		if ( dcmessage->m_eType == DC_MESSAGE_SEARCHRESULT )
		{
			/* always returns false, ignore that */
			CDownloadManager::Instance()->DLM_HandleSearch((CMessageSearchResult *)dcmessage);
		}
		
		/* otherwise CSearchSocket will print out "CallBack failed (state)..." */
		delete dcmessage;
		res = true;
	}
	else
	{
		m_CallbackMutex.Lock();
		
		if ( m_pParentCallback != 0 )
		{
			if ( m_pParentCallback->notify( dcmessage ) == 0 )
				res = true;
		}
		
		m_CallbackMutex.UnLock();
	}

	return res;
}

/** */
bool CSearchManager::HandleSearch( CDCMessage * dcmessage )
{
	return SendObject(dcmessage);
}

/** */
int CSearchManager::CallBackSearchSocket( CMessageSearchResult * result )
{
	int err = -1;
	
	if ( SendObject(result) )
	{
		err = 0;
	}

	return err;
}

/** */
int CSearchManager::CallBackClient( CClient * Client, CDCMessage * cmsg )
{
	CSearchClient * c = (CSearchClient*)Client;
	
	if ( (c == 0) || (cmsg == 0) )
	{
		return -1;
	}

	switch ( cmsg->m_eType )
	{
		case DC_MESSAGE_CONNECTION_STATE:
		{
			CMessageConnectionState *msg = (CMessageConnectionState*) cmsg;

			switch(msg->m_eState)
			{
				case estCONNECTED:
				{
					break;
				}

				case estSOCKETERROR:
				{
					DPRINTF("socket error\n");
					m_nHubError++;
					break;
				}

				case estDISCONNECTED:
				{
					DPRINTF("disconnect\n");
					c->m_bSearchRemove = true;
					c->m_tSearchTimeout = 0;
				}

				default:
				{
					break;
				}
			}

			break;
		}

		case DC_MESSAGE_VALIDATEDENIDE:
		{
			//SendDebug( "Validate denide " + c->GetIP() + CString(":") + CString::number(c->GetPort()) );
			c->Disconnect(true);
			break;
		}

		case DC_MESSAGE_HUBISFULL:
		{
			//SendDebug( "Hub is full on " + c->GetIP() + CString(":") + CString::number(c->GetPort()) );
			c->Disconnect(true);
			break;
		}

		case DC_MESSAGE_FORCEMOVE:
		{
			//SendDebug( "Force move on " + c->GetIP() + CString(":") + CString::number(c->GetPort()) );
			c->Disconnect(true);
			break;
		}

		case DC_MESSAGE_MYINFO:
		{
			CMessageMyInfo * MessageMyInfo = (CMessageMyInfo *)cmsg;
			
			if ( MessageMyInfo->m_sNick == CConfig::Instance()->GetSearchNick() )
			{
				DPRINTF("enable search\n");
				c->m_bSearchEnable  = true;
				c->m_tSearchTimeout = 0;
			}
			
			break;
		}
		
		default:
			break;
	}

	delete cmsg;

	return 0;
}
