/***************************************************************************
                          cconnectionmanager.cpp  -  description
                             -------------------
    begin                : Don Mai 16 2002
    copyright            : (C) 2002-2005 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 "cconnectionmanager.h"

#include "cclient.h"
#include "cconfig.h"
#include "core/cmanager.h"
#include "cdownloadmanager.h"
#include "dcobject.h"
#include "cfilemanager.h"
#include "core/ccallback.h"
#include "core/cnetaddr.h"

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

#include "dclib-ssl-use.h"

#include <stdio.h>

/** */
CConnectionManager::CConnectionManager()
{
	m_pClientListMutex = new CMutex();
	m_pClientList = new CList<CClient>();

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

	m_bUpdateMyinfo   = false;
}

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

	CManager::Instance()->Remove( m_pCallback );
	delete m_pCallback;
	m_pCallback = 0;
	
	m_Mutex.Lock();
	
	// this was the cause of the
	// pthread_mutex_destroy: Device or resource busy
	// message on valknut shutdown
	//m_pClientList->Lock();

	delete m_pClientList;
	m_pClientList = 0;

	delete m_pClientListMutex;
	m_pClientListMutex = 0;

	m_Mutex.UnLock();
}

/** thread callbackfunction */
int CConnectionManager::Callback()
{
	CClient * client;

	m_Mutex.Lock();

	// update all clients
	if ( m_pClientList )
	{
		client = 0;
		
		while( (client=m_pClientList->Next(client)) != 0 )
		{
			client->Thread();
		}
		
		// update myinfo from all clients
		if ( m_bUpdateMyinfo )
		{
			client = 0;
		
			while( (client=m_pClientList->Next(client)) != 0 )
			{
				if ( client->IsHandshake() )
					continue;
					
				UpdateMyInfo(client);
			}
		
			m_bUpdateMyinfo = false;
		}
	}

	m_Mutex.UnLock();

	return 0;
}

/** */
void CConnectionManager::ConnectClient( CString hubname, CString server )
{
	DCMessageConnectClient *msg = new DCMessageConnectClient();

	msg->m_sHubName = hubname;
	msg->m_sHubHost = server;

	if ( DC_CallBack(msg) == -1 )
	{
		delete msg;

		Connect( hubname, server, NULL );
	}
}

/** */
void CConnectionManager::Connect( CString hubname, CString server, CClient * client, bool sslconnect )
{
	if ( server.IsEmpty() )
	{
		return;
	}

	if ( hubname.IsEmpty() )
	{
		hubname = server;
	}

	if ( !client )
	{
		m_Mutex.Lock();

		client = (CClient*)GetHub(hubname,server);

		m_Mutex.UnLock();

		if ( client )
		{
			return;
		}

		client = new CClient();
	}

	client->SetHubName(hubname);

	AddHub(client);

	client->SetNick(CConfig::Instance()->GetNick( hubname, server ));
	client->SetConnectionType(CConfig::Instance()->GetSpeed());
	client->SetShareSize(CFileManager::Instance()->GetShareSize());
	client->SetMode(CConfig::Instance()->GetMode());
	client->SetAwayMode(CConfig::Instance()->GetAwayMode());
						
	client->SetComment(CConfig::Instance()->GetDescription(false, hubname, server));
	
	// set ssl mode, if profile exist use profile settings
	DCConfigHubProfile pConfigHubProfile;

	if ( CConfig::Instance()->GetBookmarkHubProfile( hubname, server, &pConfigHubProfile ) )
	{
		if ( pConfigHubProfile.m_bEMail )
			client->SetEMail(pConfigHubProfile.m_sEMail);
		else
			client->SetEMail(CConfig::Instance()->GetEMail());
		client->SetSSLMode(pConfigHubProfile.m_bSSL);
	}
	else
	{
		client->SetEMail(CConfig::Instance()->GetEMail());
		client->SetSSLMode(sslconnect);
	}
	
	client->Connect(server);
}

/** */
void CConnectionManager::AddHub( CClient * client )
{
	m_Mutex.Lock();

	if ( m_pClientList )
	{
		m_pClientListMutex->Lock();
		m_pClientList->Add(client);
		m_pClientListMutex->UnLock();
	}

	m_Mutex.UnLock();
}

/** */
void CConnectionManager::RemoveHub( CClient * client )
{
	m_Mutex.Lock();

	if ( m_pClientList )
	{
		m_pClientListMutex->Lock();
		client->SetCallBackFunction(0);
		m_pClientList->Remove(client);
		m_pClientListMutex->UnLock();
	}

	m_Mutex.UnLock();
}

/** */
long CConnectionManager::GetConnectedHubCount( bool admin )
{
	long i;
	CClient * client;

	if ( !m_pClientList )
	{
		return 0;
	}

	m_pClientListMutex->Lock();

	i = 0;
	client = 0;

	while( (client=m_pClientList->Next(client)) != 0 )
	{
		if ( client->IsHandshake() )
			continue;

		if ( admin && (client->UserList()->IsAdmin(client->GetNick())) )
			continue;

		i++;
	}

	m_pClientListMutex->UnLock();

	return i;
}

/** */
long CConnectionManager::GetConnectedHubPasswordCount( )
{
	long i;
	CClient * client;

	if ( !m_pClientList )
	{
		return 0;
	}

	m_pClientListMutex->Lock();

	i = 0;
	client = 0;

	while( (client=m_pClientList->Next(client)) != 0 )
	{
		if ( client->IsHandshake() )
			continue;	
	
		if ( (client->GetUsedPassword() == false) )
			continue;

		i++;
	}

	m_pClientListMutex->UnLock();

	return i;
}

/** */
CClient * CConnectionManager::GetHub( CString hubname, CString hubhost )
{
	CClient* client = 0;

	if ( !m_pClientList )
	{
		return client;
	}

	m_pClientListMutex->Lock();

	client = GetHubObject(hubname,hubhost);

	m_pClientListMutex->UnLock();

	return client;
}

/** */
CClient * CConnectionManager::GetHubObject( CString hubname, CString hubhost )
{
	CString o_host,s;
	unsigned int i;
	unsigned int o_port;
	CClient* client = 0, * client_candidate = 0;

	if ( !m_pClientList )
	{
		return client;
	}

	CNetAddr::ParseHost( hubhost, o_host, o_port );

	// set default port
	if ( o_port == 0 )
		o_port = 411;

	o_host = o_host.ToUpper();

	while( (client=m_pClientList->Next(client)) != 0 )
	{
		// compare hubname
		if ( client->GetHubName() == hubname )
			client_candidate = client;

		// compare hubhost
		CNetAddr::ParseHost( client->GetHost().ToUpper(), s, i );

		if ( i == 0 )
			i = 411;

		if ( s == o_host )
		{
			// host and port match
			if ( i == o_port )
				break;
			// host and hubname match, we can connect to the same hub on different ports
			if ( client->GetHubName() == hubname )
				break;
		}

		// compare peer name
		CNetAddr::ParseHost( client->GetHost(true).ToUpper(), s, i );

		if ( i == 0 )
			i = 411;

		if ( s == o_host )
		{
			// host and port match
			if ( i == o_port )
				break;
			// host and hubname match, we can connect to the same hub on different ports
			if ( client->GetHubName() == hubname )
				break;
		}
	}

	if ( client == 0 )
	{
		// no client found
		client = client_candidate; 
	}
	
	return client;
}

/** */
std::map<CString, CString> * CConnectionManager::GetConnectedHubServerMap()
{
	if ( !m_pClientList )
	{
		return 0;
	}

	m_pClientListMutex->Lock();

 	std::map<CString, CString> * hostmap = new std::map<CString, CString>();

	if ( m_pClientList->Count() > 0 )
	{
		CClient * client = 0;
		while( (client=m_pClientList->Next(client)) != 0 )
		{
			if ( client->IsHandshake() )
			{
				continue;
			}

			(*hostmap)[client->GetHubName()] = client->GetHost();
		}
	}

	m_pClientListMutex->UnLock();

	return hostmap;
}

/** send message to all connected servers */
int CConnectionManager::SendStringToConnectedServers( CString s, CString hubname, bool encode )
{
	int i;
	CClient* client;

	if ( !m_pClientList )
	{
		return 0;
	}

	m_pClientListMutex->Lock();

	i = 0;

	if ( s.NotEmpty() )
	{
		client = 0;

		while( (client=m_pClientList->Next(client)) != 0 )
		{
			if ( client->IsHandshake() )
			{
				continue;
			}

			if ( hubname.NotEmpty() )
			{
				if ( hubname == client->GetHubName() )
				{
					client->SendString(s, encode);
					i++;
					break;
				}
				else
				{
					continue;
				}
			}
			else
			{
				client->SendString(s, encode);
			}

			i++;
		}
	}

	m_pClientListMutex->UnLock();

	return i;
}

/** send a search string (extracted from message) to connected servers */
int CConnectionManager::SendSearchToConnectedServers( CMessageSearchFile *sf, CString hubhost )
{
	int i;
	CClient* client;

	if ( !m_pClientList )
	{
		return 0;
	}

	m_pClientListMutex->Lock();

	i = 0;

	if ( hubhost.NotEmpty() )
	{
		client = GetHubObject( CString(), hubhost );
		
		if ( client )
		{
			if ( client->IsHandshake() == false )
			{
				client->SendSearch(sf);
				i++;
			}
		}
		else
		{
			printf("CConnectionManager::SendSearchToConnectedServers hub not found\n");
		}
	}
	else
	{
		client = 0;

		while( (client=m_pClientList->Next(client)) != 0 )
		{
			if ( client->IsHandshake() )
			{
				continue;
			}

			client->SendSearch(sf);

			i++;
		}
	}

	m_pClientListMutex->UnLock();

	return i;
}

/** send myinfo to all connected servers */
int CConnectionManager::SendMyInfoToConnectedServers()
{
	m_bUpdateMyinfo = true;
	
	return 0;
}

/** */
void CConnectionManager::UpdateMyInfo( CClient* client )
{
	CString description,speed,email;
	eUserAwayMode awaymode;
	DCConfigHubProfile pConfigHubProfile;

	if ( !m_pClientList ||
	     !CFileManager::Instance() ||
	     !CConfig::Instance() )
	{
		return;
	}

	speed       = CConfig::Instance()->GetSpeed();
	awaymode    = CConfig::Instance()->GetAwayMode();

	// get description
	description = CConfig::Instance()->GetDescription(false,client->GetHubName(),client->GetHost());
		
	// get hub profile
	if ( CConfig::Instance()->GetBookmarkHubProfile( client->GetHubName(), client->GetHost(), &pConfigHubProfile ) )
	{
		if ( pConfigHubProfile.m_bEMail )
			email = pConfigHubProfile.m_sEMail;
		else
			email = CConfig::Instance()->GetEMail();
	}
	else
	{
		email = CConfig::Instance()->GetEMail();
	}
		
	// update internal values
	client->SetComment(description);
	client->SetConnectionType(speed);
	client->SetEMail(email);
	client->SetShareSize(CFileManager::Instance()->GetShareSize());
	client->SetAwayMode(awaymode);
	client->SetMode(CConfig::Instance()->GetMode());
	
#if DCLIB_HAS_SSL
	client->SetMyInfoTLSFlag( CConfig::Instance()->GetTransferCert().NotEmpty() && CConfig::Instance()->GetTransferKey().NotEmpty() );
#endif
	
	return;
}

/** */
int CConnectionManager::SendConnectionRequest( CString nick, CString hubname, CString hubhost )
{
	int res;
	CClient* client;

	if ( !m_pClientList )
	{
		return 0;
	}

	m_pClientListMutex->Lock();

	res = -3;

	client = GetHubObject(hubname,hubhost);

	if ( client )
	{
		if ( client->IsHandshake() )
		{
			res = -2;
		}
		else if ( client->UserList()->IsUserOnline( nick ) == false )
		{
			res = -1;
		}
		else if ( client->GetMode() == ecmPASSIVE )
		{
			// send connection request
			if ( client->SendRevConnectToMe( client->GetNick(), nick ) == 0 )
			{
				res = 0;
			}
			else
			{
				res = -4;
			}
		}
		else if ( client->GetMode() == ecmACTIVE )
		{
#if DCLIB_HAS_SSL == 1
			CMessageMyInfo usermyinfo;
			bool crypto = ( client->IsTLSInMyInfo() && client->UserList()->GetUserMyInfo(nick,&usermyinfo) && usermyinfo.m_bTLSFlag );
#else
			bool crypto = false;
#endif
			CString s = client->GetExternalIP( true, crypto );

			if ( s.NotEmpty() )
			{
				// send connection request
				CDownloadManager::Instance()->DLM_AddTransferRequest( nick, CString(), hubname, client->GetHost() );

				if ( client->SendConnectToMe( nick, s, crypto ) == 0 )
				{
					res = 0;
				}
				else
				{
					res = -4;
				}
			}
			else
			{
				res = -4;
			}
		}
	}
	m_pClientListMutex->UnLock();

	return res;
}

/** */
bool CConnectionManager::IsUserOnline( CString nick, CString hubname, CString hubhost, CList<DCHubObject> * list )
{
	bool res;
	CClient* client;
	DCHubObject * HubObject;

	if ( !m_pClientList )
	{
		return false;
	}

	m_pClientListMutex->Lock();

	res = false;
	client = 0;

	hubhost = hubhost.ToUpper();

	while( (client=m_pClientList->Next(client)) != 0 )
	{
		if ( client->IsHandshake() == false )
		{
			if ( hubname.IsEmpty() )
			{
				if ( client->UserList()->IsUserOnline( nick ) )
				{
					if ( list )
					{
						HubObject = new DCHubObject();
						HubObject->m_sHubName = client->GetHubName();
						HubObject->m_sHubHost = client->GetHost();
						list->Add(HubObject);
					}
					res = true;
				}
			}
			if ( (client->GetHubName() == hubname) ||
			     (client->GetHost().ToUpper() == hubhost) ||
			     (client->GetHost(true) == hubhost) )
			{
				if ( client->UserList()->IsUserOnline( nick ) )
					res = true;
				break;
			}
		}
	}
	m_pClientListMutex->UnLock();

	return res;
}

/** */
bool CConnectionManager::SetUserTransferInfo( CString hubname, CString hubhost, CString nick, CDCMessage * msg )
{
	bool res = false;
	CClient* client;

	if ( !m_pClientList )
	{
		return false;
	}

	m_pClientListMutex->Lock();

	if ( (client = GetHubObject( hubname, hubhost )) != 0 )
	{
		if ( client->IsHandshake() == false )
		{
			res = client->SetUserTransferInfo(nick,msg);
		}
		else
		{
			printf("CConnectionManager::SetUserTransferInfo: hub offline\n");
		}
	}
	else
	{
		printf("CConnectionManager::SetUserTransferInfo: can't find hub\n");
	}
	
	m_pClientListMutex->UnLock();

	return res;
}

/** */
eHubState CConnectionManager::IsHubOnline( CString hubname, CString hubhost )
{
	eHubState res;
	CClient* client;

	if ( !m_pClientList )
	{
		return ehsNONE;
	}

	m_pClientListMutex->Lock();

	res = ehsNONE;

	if ( (client = GetHubObject( hubname, hubhost )) != 0 )
	{
		if ( client->IsHandshake() == false )
		{
			res = ehsONLINE;
		}
		else
		{
			res = ehsOFFLINE;
		}
	}
	m_pClientListMutex->UnLock();

	return res;
}

/** */
CString CConnectionManager::GetNick( CString hubname, CString hubhost )
{
	CString res;
	CClient* client;

	if ( !m_pClientList )
	{
		return res;
	}

	m_pClientListMutex->Lock();

	if ( (client = GetHubObject( hubname, hubhost )) != 0 )
	{
		res = client->GetNick();
	}

	m_pClientListMutex->UnLock();

	return res;
}


/** */
bool CConnectionManager::IsAdmin( CString hubname, CString hubhost, CString nick )
{
	bool res;
	CClient* client;

	if ( !m_pClientList )
	{
		return false;
	}

	m_pClientListMutex->Lock();

	res = false;

	if ( (client = GetHubObject(hubname,hubhost)) != 0 )
	{
		if ( client->IsHandshake() == false )
		{
			if ( nick.IsEmpty() )
				res = client->UserList()->IsAdmin( client->GetNick() );
			else
				res = client->UserList()->IsAdmin(nick);
		}
	}

	m_pClientListMutex->UnLock();

	return res;
}

/** */
bool CConnectionManager::GetUserMyInfo( CString hubname, CString hubhost, CString nick, CMessageMyInfo * p )
{
	bool res;
	CClient* client;

	if ( !m_pClientList )
	{
		return false;
	}

	m_pClientListMutex->Lock();

	res = false;

	if ( (client = GetHubObject(hubname,hubhost)) != 0 )
	{
		if ( client->IsHandshake() == false )
		{
			res = client->UserList()->GetUserMyInfo(nick,p);
		}
	}

	m_pClientListMutex->UnLock();

	return res;
}

/** TODO: remove function ! (used in ctransferview) */
CString CConnectionManager::GetHubHost( CString hubname )
{
	CString res;
	CClient* client;

	if ( !m_pClientList )
	{
		return res;
	}

	m_pClientListMutex->Lock();

	client = 0;

	while( (client=m_pClientList->Next(client)) != 0 )
	{
		if ( client->GetHubName() == hubname )
		{
			res = client->GetIP();
			res += ':';
			res += CString::number(client->GetPort());
			break;
		}
	}
	m_pClientListMutex->UnLock();

	return res;
}

/** */
bool CConnectionManager::GetHubDetails( const CString & id, CString & name, CString & host, CString & ip )
{
	bool res = false;
	
	if ( !m_pClientList )
	{
		return res;
	}
	
	m_pClientListMutex->Lock();
	
	CClient * client = 0;
	
	while ( (client = m_pClientList->Next(client)) != 0 )
	{
		if ( client->GetHubName() == id || client->GetResolvedIP() == id || client->GetHost() == id )
		{
			name = client->GetHubName();
			host = client->GetHost();
			ip   = client->GetResolvedIP();
			res  = true;
			break;
		}
	}
	
	m_pClientListMutex->UnLock();
	
	return res;
}
