// -*- Mode: c++-mode; c-basic-offset: 2; indent-tabs-mode: t; tab-width: 2; -*-
//
// Copyright (C) 2003-2004 Grzegorz Jaskiewicz 	<gj at pointblue.com.pl>
// Copyright (C) 2003 Zack Rusin 		<zack@kde.org>
//
// gaduaccount.cpp
//
// 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 Street, Fifth Floor, Boston, MA
// 02110-1301, USA

#include "gaduaccount.h"
#include "gaducontact.h"
#include "gaduprotocol.h"
#include "gaduawayui.h"
#include "gaduaway.h"
#include "gadupubdir.h"
#include "gadudcc.h"
#include "gadudcctransaction.h"

#include "kopetemetacontact.h"
#include "kopetecontactlist.h"
#include "kopetegroup.h"
#include "kopetepassword.h"
#include "kopeteuiglobal.h"
#include "kopeteglobal.h"

#include <kpassdlg.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tdepopupmenu.h>
#include <tdemessagebox.h>
#include <knotifyclient.h>
#include <tdetempfile.h>
#include <tdeio/netaccess.h>

#include <tqapplication.h>
#include <tqdialog.h>
#include <tqtimer.h>
#include <tqtextcodec.h>
#include <tqptrlist.h>
#include <tqtextstream.h>
#include <tqhostaddress.h>

#include <netinet/in.h>

class GaduAccountPrivate {

public:
	GaduAccountPrivate() {}

	GaduSession*	session_;
	GaduDCC*	gaduDcc_;

	TQTimer*		pingTimer_;
	
	TQTextCodec*	textcodec_;
	KFileDialog*	saveListDialog;
	KFileDialog*	loadListDialog;

	TDEActionMenu*	actionMenu_;
	TDEAction*	searchAction;
	TDEAction*	listputAction;
	TDEAction*	listToFileAction;
	TDEAction*	listFromFileAction;
	TDEAction*	friendsModeAction;
	bool		connectWithSSL;

	int		currentServer;
	unsigned int	serverIP;

	TQString		lastDescription;
	bool		forFriends;
	bool		ignoreAnons;
        
	TQTimer*         exportTimer_;
	bool		exportUserlist;
	
	TDEConfigGroup*			config;
	Kopete::OnlineStatus		status;
	TQValueList<unsigned int>	servers;
	KGaduLoginParams		loginInfo;
};

// 10s is enough ;p
#define USERLISTEXPORT_TIMEOUT (10*1000)

// FIXME: use dynamic cache please, i consider this as broken resolution of this problem
static const char* const servers_ip[] = {
	"217.17.41.85",
	"217.17.41.83",
	"217.17.41.84",
	"217.17.41.86",
	"217.17.41.87",
	"217.17.41.88",
	"217.17.41.92",
	"217.17.41.93",
	"217.17.45.133",
	"217.17.45.143",
	"217.17.45.144"
};

#define NUM_SERVERS (sizeof(servers_ip)/sizeof(char*))

 GaduAccount::GaduAccount( Kopete::Protocol* parent, const TQString& accountID,const char* name )
: Kopete::PasswordedAccount( parent, accountID, 0, name )
{
	TQHostAddress ip;
	p = new GaduAccountPrivate;

	p->pingTimer_ = NULL;
	p->saveListDialog = NULL;
	p->loadListDialog = NULL;
	p->forFriends = false;

	p->textcodec_ = TQTextCodec::codecForName( "CP1250" );
	p->session_ = new GaduSession( this, "GaduSession" );

	TDEGlobal::config()->setGroup( "Gadu" );

	setMyself( new GaduContact( accountId().toInt(), accountId(), this, Kopete::ContactList::self()->myself() ) );

	p->status = GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL );
	p->lastDescription = TQString();

	for ( unsigned int i = 0; i < NUM_SERVERS ; i++ ) {
		ip.setAddress( TQString( servers_ip[i] ) );
		p->servers.append( htonl( ip.toIPv4Address() ) );
		kdDebug( 14100 ) << "adding IP: " <<  p->servers[ i ] << " to cache" << endl;
	}
	p->currentServer = -1;
	p->serverIP = 0;

	// initialize KGaduLogin structure to default values
	p->loginInfo.uin		= accountId().toInt();
	p->loginInfo.useTls		= false;
	p->loginInfo.status		= GG_STATUS_AVAIL;
	p->loginInfo.server		= 0;
	p->loginInfo.client_port	= 0;
	p->loginInfo.client_addr	= 0;

	p->pingTimer_ = new TQTimer( this );
	p->exportTimer_ = new TQTimer( this );

	p->exportUserlist = false;
	p->gaduDcc_ = NULL;

	p->config = configGroup();

	p->ignoreAnons = ignoreAnons();
	p->forFriends = loadFriendsMode();

	initConnections();
	initActions();

	TQString nick = p->config->readEntry( TQString::fromAscii( "nickName" ) );
	if ( !nick.isNull() ) {
		myself()->setProperty( Kopete::Global::Properties::self()->nickName(), nick );
	}
	else {
		myself()->setProperty( Kopete::Global::Properties::self()->nickName(), accountId() );
		p->config->writeEntry( TQString::fromAscii( "nickName" ), accountId() );
	}
}

GaduAccount::~GaduAccount()
{
	delete p;
}

void
GaduAccount::initActions()
{
	p->searchAction		= new TDEAction( i18n( "&Search for Friends" ), "", 0,
							this, TQT_SLOT( slotSearch() ), this, "actionSearch" );
	p->listputAction		= new TDEAction( i18n( "Export Contacts to Server" ), "", 0,
							this, TQT_SLOT( slotExportContactsList() ), this, "actionListput" );
	p->listToFileAction	= new TDEAction( i18n( "Export Contacts to File..." ), "", 0,
							this, TQT_SLOT( slotExportContactsListToFile() ), this, "actionListputFile" );
	p->listFromFileAction	= new TDEAction( i18n( "Import Contacts From File..." ), "", 0,
							this, TQT_SLOT( slotImportContactsFromFile() ), this, "actionListgetFile" );
	p->friendsModeAction	= new TDEToggleAction( i18n( "Only for Friends" ), "", 0,
							this, TQT_SLOT( slotFriendsMode() ), this,
							"actionFriendsMode" );

	static_cast<TDEToggleAction*>(p->friendsModeAction)->setChecked( p->forFriends );
}

void
GaduAccount::initConnections()
{
	TQObject::connect( p->session_, TQT_SIGNAL( error( const TQString&, const TQString& ) ),
				TQT_SLOT( error( const TQString&, const TQString& ) ) );
	TQObject::connect( p->session_, TQT_SIGNAL( messageReceived( KGaduMessage* ) ),
				TQT_SLOT( messageReceived( KGaduMessage* ) )  );
	TQObject::connect( p->session_, TQT_SIGNAL( contactStatusChanged( KGaduNotify* ) ),
				TQT_SLOT( contactStatusChanged( KGaduNotify* ) ) );
	TQObject::connect( p->session_, TQT_SIGNAL( connectionFailed( gg_failure_t )),
				TQT_SLOT( connectionFailed( gg_failure_t ) ) );
	TQObject::connect( p->session_, TQT_SIGNAL( connectionSucceed( ) ),
				TQT_SLOT( connectionSucceed( ) ) );
	TQObject::connect( p->session_, TQT_SIGNAL( disconnect( Kopete::Account::DisconnectReason ) ),
				TQT_SLOT( slotSessionDisconnect( Kopete::Account::DisconnectReason ) ) );
	TQObject::connect( p->session_, TQT_SIGNAL( ackReceived( unsigned int ) ),
				TQT_SLOT( ackReceived( unsigned int ) ) );
	TQObject::connect( p->session_, TQT_SIGNAL( pubDirSearchResult( const SearchResult&, unsigned int  ) ),
				TQT_SLOT( slotSearchResult( const SearchResult&, unsigned int ) ) );
	TQObject::connect( p->session_, TQT_SIGNAL( userListExported() ),
				TQT_SLOT( userListExportDone() ) );
	TQObject::connect( p->session_, TQT_SIGNAL( userListRecieved( const TQString& ) ),
				TQT_SLOT( userlist( const TQString& ) ) );
	TQObject::connect( p->session_, TQT_SIGNAL( incomingCtcp( unsigned int ) ),
				TQT_SLOT( slotIncomingDcc( unsigned int ) ) );

	TQObject::connect( p->pingTimer_, TQT_SIGNAL( timeout() ),
				TQT_SLOT( pingServer() ) );

	TQObject::connect( p->exportTimer_, TQT_SIGNAL( timeout() ),
				TQT_SLOT( slotUserlistSynch() ) );
}

void
GaduAccount::setAway( bool isAway, const TQString& awayMessage )
{
	unsigned int currentStatus;

	if ( isAway ) {
		currentStatus = ( awayMessage.isEmpty() ) ? GG_STATUS_BUSY : GG_STATUS_BUSY_DESCR;
	}
	else{
		currentStatus = ( awayMessage.isEmpty() ) ? GG_STATUS_AVAIL : GG_STATUS_AVAIL_DESCR;
	}
	changeStatus( GaduProtocol::protocol()->convertStatus( currentStatus ), awayMessage );
}


TDEActionMenu*
GaduAccount::actionMenu()
{
	kdDebug(14100) << "actionMenu() " << endl;

	p->actionMenu_ = new TDEActionMenu( accountId(), myself()->onlineStatus().iconFor( this ), this );
	p->actionMenu_->popupMenu()->insertTitle( myself()->onlineStatus().iconFor( myself() ), i18n( "%1 <%2> " ).
	    arg( myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString(), accountId() ) );

	if ( p->session_->isConnected() ) {
		p->searchAction->setEnabled( TRUE );
		p->listputAction->setEnabled( TRUE );
		p->friendsModeAction->setEnabled( TRUE );
	}
	else {
		p->searchAction->setEnabled( FALSE );
		p->listputAction->setEnabled( FALSE );
		p->friendsModeAction->setEnabled( FALSE );
	}

	if ( contacts().count() > 1 ) {
		if ( p->saveListDialog ) {
			p->listToFileAction->setEnabled( FALSE );
		}
		else {
			p->listToFileAction->setEnabled( TRUE );
		}

		p->listToFileAction->setEnabled( TRUE );
	}
	else {
		p->listToFileAction->setEnabled( FALSE );
	}

	if ( p->loadListDialog ) {
		p->listFromFileAction->setEnabled( FALSE );
	}
	else {
		p->listFromFileAction->setEnabled( TRUE );
	}
	p->actionMenu_->insert( new TDEAction( i18n( "Go O&nline" ),
				GaduProtocol::protocol()->convertStatus( GG_STATUS_AVAIL ).iconFor( this ),
				0, this, TQT_SLOT( slotGoOnline() ), this, "actionGaduConnect" ) );

	p->actionMenu_->insert( new TDEAction( i18n( "Set &Busy" ),
				GaduProtocol::protocol()->convertStatus( GG_STATUS_BUSY ).iconFor( this ),
				0, this, TQT_SLOT( slotGoBusy() ), this, "actionGaduConnect" ) );

	p->actionMenu_->insert( new TDEAction( i18n( "Set &Invisible" ),
				GaduProtocol::protocol()->convertStatus( GG_STATUS_INVISIBLE ).iconFor( this ),
				0, this, TQT_SLOT( slotGoInvisible() ), this, "actionGaduConnect" ) );

	p->actionMenu_->insert( new TDEAction( i18n( "Go &Offline" ),
				GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ).iconFor( this ),
				0, this, TQT_SLOT( slotGoOffline() ), this, "actionGaduConnect" ) );

	p->actionMenu_->insert( new TDEAction( i18n( "Set &Description..." ), "application-vnd.tde.info",
			0, this, TQT_SLOT( slotDescription() ), this, "actionGaduDescription" ) );

	p->actionMenu_->insert( p->friendsModeAction );

	p->actionMenu_->popupMenu()->insertSeparator();

	p->actionMenu_->insert( p->searchAction );

	p->actionMenu_->popupMenu()->insertSeparator();

	p->actionMenu_->insert( p->listputAction );

	p->actionMenu_->popupMenu()->insertSeparator();

	p->actionMenu_->insert( p->listToFileAction );
	p->actionMenu_->insert( p->listFromFileAction );

	return p->actionMenu_;
}

void
GaduAccount::connectWithPassword(const TQString& password)
{
	if (password.isEmpty()) {
		return;
	}
	if (isConnected ())
		return;
	// FIXME: add status description to this mechainsm, this is a hack now. libkopete design issue.
	changeStatus( initialStatus(), p->lastDescription );
}

void
GaduAccount::disconnect()
{
	disconnect( Manual );
}

void
GaduAccount::disconnect( DisconnectReason reason )
{
	slotGoOffline();
	p->connectWithSSL = true;
	Kopete::Account::disconnected( reason );
}

void
GaduAccount::setOnlineStatus( const Kopete::OnlineStatus& status , const TQString &reason )
{
	kdDebug(14100) << k_funcinfo << "Called" << endl;
	changeStatus( status, reason);
}

void
GaduAccount::slotUserlistSynch()
{
	if ( !p->exportUserlist ) {
		return;
	}
	p->exportUserlist = false;
	kdDebug(14100) << "userlist changed, exporting" << endl;
	slotExportContactsList();
}

void
GaduAccount::userlistChanged()
{
	p->exportUserlist = true;
	p->exportTimer_->changeInterval( USERLISTEXPORT_TIMEOUT );
}

bool
GaduAccount::createContact( const TQString& contactId, Kopete::MetaContact* parentContact )
{
	kdDebug(14100) << "createContact " << contactId << endl;

	uin_t uinNumber = contactId.toUInt();
	GaduContact* newContact = new GaduContact( uinNumber, parentContact->displayName(),this, parentContact );
	newContact->setParentIdentity( accountId() );
	addNotify( uinNumber );

	userlistChanged();
	
	return true;
}

void
GaduAccount::changeStatus( const Kopete::OnlineStatus& status, const TQString& descr )
{
	unsigned int ns;
	
	kdDebug(14100) << "##### change status #####" << endl;
	kdDebug(14100) << "### Status = " << p->session_->isConnected() << endl;
	kdDebug(14100) << "### Status description = \"" << descr << "\"" << endl;

	// if change to not available, log off
	if ( GG_S_NA( status.internalStatus() ) ) {
		if ( !p->session_->isConnected() ) {
			return;//already logged off
		}
		else {
			 if ( status.internalStatus() == GG_STATUS_NOT_AVAIL_DESCR ) {
				if ( p->session_->changeStatusDescription( status.internalStatus(), descr, p->forFriends ) != 0 ) {
					return;
				}
			}
		}
		p->session_->logoff();
		dccOff();
	}
	else {
		// if status is for no desc, but we get some desc, than convert it to status with desc
		if (!descr.isEmpty() && !GaduProtocol::protocol()->statusWithDescription( status.internalStatus() ) ) {
			// and rerun us again. This won't cause any recursive call, as both conversions are static
			ns = GaduProtocol::protocol()->statusToWithDescription( status );
			changeStatus( GaduProtocol::protocol()->convertStatus( ns ), descr );
			return;
		}

		// well, if it's empty but we want to set status with desc, change it too
                if (descr.isEmpty() && GaduProtocol::protocol()->statusWithDescription( status.internalStatus() ) ) {
			ns = GaduProtocol::protocol()->statusToWithoutDescription( status );
			changeStatus( GaduProtocol::protocol()->convertStatus( ns ), descr );
			return;
		}
				
		if ( !p->session_->isConnected() ) {
			if ( password().cachedValue().isEmpty() ) {
				// FIXME: when status string added to connect(), use it here
				p->lastDescription = descr;
				connect( status/*, descr*/ );
				return;
			}

			if ( useTls() != TLS_no ) {
				p->connectWithSSL = true;
			}
			else {
				p->connectWithSSL = false;
			}
			dccOn();
			p->serverIP = 0;
			p->currentServer = -1;
			p->status = status;
			kdDebug(14100) << "#### Connecting..., tls option "<< (int)useTls() << " " << endl;
			p->lastDescription = descr;
			slotLogin( status.internalStatus(), descr );
			return;
		}
		else {
			p->status = status;
			if ( descr.isEmpty() ) {
				if ( p->session_->changeStatus( status.internalStatus(), p->forFriends ) != 0 )
					return;
			}
			else {
				if ( p->session_->changeStatusDescription( status.internalStatus(), descr, p->forFriends ) != 0 )
					return;
			}
		}
	}

	myself()->setOnlineStatus( status );
	myself()->setProperty( GaduProtocol::protocol()->propAwayMessage, descr );

	if ( status.internalStatus() == GG_STATUS_NOT_AVAIL || status.internalStatus() == GG_STATUS_NOT_AVAIL_DESCR ) {
		if ( p->pingTimer_ ){
			p->pingTimer_->stop();
		}
	}
	p->lastDescription = descr;
}

void
GaduAccount::slotLogin( int status, const TQString& dscr )
{
	p->lastDescription	= dscr;

	myself()->setOnlineStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_CONNECTING ));
	myself()->setProperty( GaduProtocol::protocol()->propAwayMessage, dscr );

	if ( !p->session_->isConnected() ) {
		if ( password().cachedValue().isEmpty() ) {
			connectionFailed( GG_FAILURE_PASSWORD );
		}
		else {
			p->loginInfo.password		= password().cachedValue();
			p->loginInfo.useTls		= p->connectWithSSL;
			p->loginInfo.status		= status;
			p->loginInfo.statusDescr	= dscr;
			p->loginInfo.forFriends		= p->forFriends;
			p->loginInfo.server		= p->serverIP;
			if ( dccEnabled() ) {
				p->loginInfo.client_addr	= gg_dcc_ip;
				p->loginInfo.client_port	= gg_dcc_port;
			}
			else {
				p->loginInfo.client_addr	= 0;
				p->loginInfo.client_port	= 0;
			}
			p->session_->login( &p->loginInfo );
		}
	}
	else {
		p->session_->changeStatus( status );
	}
}

void
GaduAccount::slotLogoff()
{
	if ( p->session_->isConnected() || p->status == GaduProtocol::protocol()->convertStatus( GG_STATUS_CONNECTING )) {
		p->status = GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL );
		changeStatus( p->status );
		p->session_->logoff();
		dccOff();
	}
}

void
GaduAccount::slotGoOnline()
{
	changeStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_AVAIL ) );
}
void
GaduAccount::slotGoOffline()
{
	slotLogoff();
	dccOff();
}

void
GaduAccount::slotGoInvisible()
{
	changeStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_INVISIBLE ) );
}

void
GaduAccount::slotGoBusy()
{
	changeStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_BUSY ) );
}

void
GaduAccount::removeContact( const GaduContact* c )
{
	if ( isConnected() ) {
		const uin_t u = c->uin();
		p->session_->removeNotify( u );
		userlistChanged();
	}
}

void
GaduAccount::addNotify( uin_t uin )
{
	if ( p->session_->isConnected() ) {
		p->session_->addNotify( uin );
	}
}

void
GaduAccount::notify( uin_t* userlist, int count )
{
	if ( p->session_->isConnected() ) {
		p->session_->notify( userlist, count );
	}
}

void
GaduAccount::sendMessage( uin_t recipient, const Kopete::Message& msg, int msgClass )
{
	if ( p->session_->isConnected() ) {
		p->session_->sendMessage( recipient, msg, msgClass );
	}
}

void
GaduAccount::error( const TQString& title, const TQString& message )
{
	KMessageBox::error( Kopete::UI::Global::mainWidget(), title, message );
}

void
GaduAccount::messageReceived( KGaduMessage* gaduMessage )
{
	GaduContact* contact = 0;
	TQPtrList<Kopete::Contact> contactsListTmp;

	// FIXME:check for ignored users list

	if ( gaduMessage->sender_id == 0 ) {
		//system message, display them or not?
		kdDebug(14100) << "####" << " System Message " << gaduMessage->message << endl;
		return;
	}

	contact = static_cast<GaduContact*> ( contacts()[ TQString::number( gaduMessage->sender_id ) ] );

	if ( !contact ) {
		if ( p->ignoreAnons == true ) {
			return;
		}

		Kopete::MetaContact* metaContact = new Kopete::MetaContact ();
		metaContact->setTemporary ( true );
		contact = new GaduContact( gaduMessage->sender_id,
				TQString::number( gaduMessage->sender_id ), this, metaContact );
		Kopete::ContactList::self ()->addMetaContact( metaContact );
		addNotify( gaduMessage->sender_id );
	}

	contactsListTmp.append( myself() );
	Kopete::Message msg( gaduMessage->sendTime, contact, contactsListTmp, gaduMessage->message, Kopete::Message::Inbound, Kopete::Message::RichText );
	contact->messageReceived( msg );
}

void
GaduAccount::ackReceived( unsigned int recipient  )
{
	GaduContact* contact;

	contact = static_cast<GaduContact*> ( contacts()[ TQString::number( recipient ) ] );
	if ( contact ) {
		kdDebug(14100) << "####" << "Received an ACK from " << contact->uin() << endl;
		contact->messageAck();
	}
	else {
		kdDebug(14100) << "####" << "Received an ACK from an unknown user : " << recipient << endl;
	}
}

void
GaduAccount::contactStatusChanged( KGaduNotify* gaduNotify )
{
	kdDebug(14100) << "####" << " contact's status changed, uin:" << gaduNotify->contact_id <<endl;

	GaduContact* contact;

	contact = static_cast<GaduContact*>( contacts()[ TQString::number( gaduNotify->contact_id ) ] );
	if( !contact ) {
		kdDebug(14100) << "Notify not in the list " << gaduNotify->contact_id << endl;
		return;
	}

	contact->changedStatus( gaduNotify );
}

void
GaduAccount::pong()
{
	kdDebug(14100) << "####" << " Pong..." << endl;
}

void
GaduAccount::pingServer()
{
	kdDebug(14100) << "####" << " Ping..." << endl;
	p->session_->ping();
}

void
GaduAccount::connectionFailed( gg_failure_t failure )
{
	bool tryReconnect = false;
	TQString pass;


	switch (failure) {
		case GG_FAILURE_PASSWORD:
			password().setWrong();
			// user pressed CANCEL
			p->status = GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL );
			myself()->setOnlineStatus( p->status );
			disconnected( BadPassword );
			return;
		default:
			if ( p->connectWithSSL ) {
				if ( useTls() != TLS_only ) {
					slotCommandDone( TQString(), i18n( "connection using SSL was not possible, retrying without." ) );
					kdDebug( 14100 ) << "try without tls now" << endl;
					p->connectWithSSL = false;
					tryReconnect = true;
					p->currentServer = -1;
					p->serverIP = 0;
					break;
				}
			}
			else {
				if ( p->currentServer == NUM_SERVERS - 1 ) {
					p->serverIP = 0;
					p->currentServer = -1;
					kdDebug(14100) << "trying : " << "IP from hub " << endl;
				}
				else {
					p->serverIP = p->servers[ ++p->currentServer ];
					kdDebug(14100) << "trying : " << p->currentServer << " IP " << p->serverIP << endl;
					tryReconnect = true;
				}
			}
		break;
	}

	if ( tryReconnect ) {
			slotLogin( p->status.internalStatus() , p->lastDescription );
	}
	else {
		error( i18n( "unable to connect to the Gadu-Gadu server(\"%1\")." ).arg( GaduSession::failureDescription( failure ) ),
				i18n( "Connection Error" ) );
		p->status = GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL );
		myself()->setOnlineStatus( p->status );
		disconnected( InvalidHost );
	}
}

void
GaduAccount::dccOn()
{
	if ( dccEnabled() ) {
		if ( !p->gaduDcc_ ) {
			p->gaduDcc_ = new GaduDCC( this );
		}
		kdDebug( 14100 ) << " turn DCC on for " << accountId() << endl;
		p->gaduDcc_->registerAccount( this );
		p->loginInfo.client_port	= p->gaduDcc_->listeingPort();
	}
}

void
GaduAccount::dccOff()
{
	if ( p->gaduDcc_ ) {
		kdDebug( 14100 ) << "destroying dcc in gaduaccount " << endl;
		delete p->gaduDcc_;
		p->gaduDcc_ = NULL;
		p->loginInfo.client_port	= 0;
		p->loginInfo.client_addr	= 0;
	}
}

void
GaduAccount::slotIncomingDcc( unsigned int uin )
{
	GaduContact* contact;
	GaduDCCTransaction* trans;

	if ( !uin ) {
		return;
	}

	contact = static_cast<GaduContact*>( contacts()[ TQString::number( uin ) ] );

	if ( !contact ) {
	  kdDebug(14100) << "attempt to make dcc connection from unknown uin " << uin << endl;
		return;
	}

	// if incapabile to transfer files, forget about it.
	if ( contact->contactPort() < 10 ) {
	  kdDebug(14100) << "can't respond to " << uin << " request, his listeing port is too low" << endl;
	  return;
	}

	trans = new GaduDCCTransaction( p->gaduDcc_ );
	if ( trans->setupIncoming( p->loginInfo.uin, contact ) == false ) {
		delete trans;
	}

}

void
GaduAccount::connectionSucceed( )
{
	kdDebug(14100) << "#### Gadu-Gadu connected! " << endl;
	p->status =  GaduProtocol::protocol()->convertStatus( p->session_->status() );
	myself()->setOnlineStatus( p->status );
	myself()->setProperty( GaduProtocol::protocol()->propAwayMessage, p->lastDescription );
	startNotify();

	p->session_->requestContacts();
	p->pingTimer_->start( 3*60*1000 );//3 minute timeout
	pingServer();

	// check if we need to export userlist every USERLISTEXPORT_TIMEOUT ms
	p->exportTimer_->start( USERLISTEXPORT_TIMEOUT );
}

void
GaduAccount::startNotify()
{
	int i = 0;
	if ( !contacts().count() ) {
		return;
	}

	TQDictIterator<Kopete::Contact> kopeteContactsList( contacts() );

	uin_t* userlist = 0;
	userlist = new uin_t[ contacts().count() ];

	for( i=0 ; kopeteContactsList.current() ; ++kopeteContactsList ) {
		userlist[i++] = static_cast<GaduContact*> ((*kopeteContactsList))->uin();
	}

	p->session_->notify( userlist, contacts().count() );
	delete [] userlist;
}

void
GaduAccount::slotSessionDisconnect( Kopete::Account::DisconnectReason reason )
{
	uin_t status;

	kdDebug(14100) << "Disconnecting" << endl;

	if (p->pingTimer_) {
		p->pingTimer_->stop();
	}

	setAllContactsStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ) );

	status = myself()->onlineStatus().internalStatus();
	if ( status != GG_STATUS_NOT_AVAIL || status != GG_STATUS_NOT_AVAIL_DESCR ) {
		myself()->setOnlineStatus( GaduProtocol::protocol()->convertStatus( GG_STATUS_NOT_AVAIL ) );
	}
	GaduAccount::disconnect( reason );
}

void
GaduAccount::userlist( const TQString& contactsListString )
{
	kdDebug(14100)<<"### Got userlist - gadu account"<<endl;

	GaduContactsList contactsList( contactsListString );
	TQString contactName;
	TQStringList groups;
	GaduContact* contact;
	Kopete::MetaContact* metaContact;
	unsigned int i;

	// don't export any new changes that were just imported :-)
	p->exportTimer_->stop();
	
	for ( i = 0; i != contactsList.size() ; i++ ) {
		kdDebug(14100) << "uin " << contactsList[i].uin << endl;

		if ( contactsList[i].uin.isNull() ) {
			kdDebug(14100) << "no Uin, strange.. "<<endl;
			continue;
		}

		if ( contacts()[ contactsList[i].uin ] ) {
			kdDebug(14100) << "UIN already exists in contacts "<< contactsList[i].uin << endl;
		}
		else {
			contactName = GaduContact::findBestContactName( &contactsList[i] );
			bool s = addContact( contactsList[i].uin, contactName, 0L, Kopete::Account::DontChangeKABC);
			if ( s == false ) {
				kdDebug(14100) << "There was a problem adding UIN "<< contactsList[i].uin << "to users list" << endl;
				continue;
			}
		}
		contact = static_cast<GaduContact*>( contacts()[ contactsList[i].uin ] );
		if ( contact == NULL ) {
			kdDebug(14100) << "oops, no Kopete::Contact in contacts()[] for some reason, for \"" << contactsList[i].uin << "\"" << endl;
			continue;
		}

		// update/add infor for contact
		contact->setContactDetails( &contactsList[i] );

		if ( !( contactsList[i].group.isEmpty() ) ) {
			// FIXME: libkopete bug i guess, by default contact goes to top level group
			// if user desrired to see contact somewhere else, remove it from top level one
			metaContact = contact->metaContact();
			metaContact->removeFromGroup( Kopete::Group::topLevel() );
			// put him in all desired groups:
			groups = TQStringList::split( ",", contactsList[i].group );
			for ( TQStringList::Iterator groupsIterator = groups.begin(); groupsIterator != groups.end(); ++groupsIterator ) {
				metaContact->addToGroup( Kopete::ContactList::self ()->findGroup ( *groupsIterator) );
			}
		}
	}
	// start to check if we need to export userlist 
	p->exportUserlist = false;	
	p->exportTimer_->start( USERLISTEXPORT_TIMEOUT );
}

void
GaduAccount::userListExportDone()
{
	slotCommandDone( TQString(), i18n( "Contacts exported to the server.") );
}

void
GaduAccount::slotFriendsMode()
{
	p->forFriends = !p->forFriends;
	kdDebug( 14100 ) << "for friends mode: " << p->forFriends << " desc" << p->lastDescription << endl;
	// now change status, it will changing it with p->forFriends flag
	changeStatus( p->status, p->lastDescription );

	saveFriendsMode( p->forFriends );

}

// FIXME: make loading and saving nonblocking (at the moment KFileDialog stops plugin/kopete)

void
GaduAccount::slotExportContactsListToFile()
{
	KTempFile tempFile;
	tempFile.setAutoDelete( true );

	if ( p->saveListDialog ) {
		kdDebug( 14100 ) << " save contacts to file: alread waiting for input " << endl ;
		return;
	}

	p->saveListDialog = new KFileDialog( "::kopete-gadu" + accountId(), TQString(),
					Kopete::UI::Global::mainWidget(), "gadu-list-save", false );
	p->saveListDialog->setCaption(
	    i18n("Save Contacts List for Account %1 As").arg(
	    myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString() ) );

	if ( p->saveListDialog->exec() == TQDialog::Accepted ) {
		TQCString list = p->textcodec_->fromUnicode( userlist()->asString() );

		if ( tempFile.status() ) {
			// say cheese, can't create file.....
			error( i18n( "Unable to create temporary file." ), i18n( "Save Contacts List Failed" ) );
		}
		else {
			TQTextStream* tempStream = tempFile.textStream();
			(*tempStream) << list.data();
			tempFile.close();

			bool res = TDEIO::NetAccess::upload(
								tempFile.name() ,
								p->saveListDialog->selectedURL() ,
								Kopete::UI::Global::mainWidget()
								);
			if ( !res ) {
				// say it failed
				error( TDEIO::NetAccess::lastErrorString(), i18n( "Save Contacts List Failed" ) );
			}
		}

	}
	delete p->saveListDialog;
	p->saveListDialog = NULL;
}

void
GaduAccount::slotImportContactsFromFile()
{
	KURL url;
	TQCString list;
	TQString oname;

	if ( p->loadListDialog ) {
		kdDebug( 14100 ) << "load contacts from file: alread waiting for input " << endl ;
		return;
	}

	p->loadListDialog = new KFileDialog( "::kopete-gadu" + accountId(), TQString(),
					Kopete::UI::Global::mainWidget(), "gadu-list-load", true );
	p->loadListDialog->setCaption(
	    i18n("Load Contacts List for Account %1 As").arg(
	    myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString() ) );

	if ( p->loadListDialog->exec() == TQDialog::Accepted ) {
		url = p->loadListDialog->selectedURL();
		kdDebug(14100) << "a:" << url << "\nb:" << oname << endl;
		if ( TDEIO::NetAccess::download( url, oname, Kopete::UI::Global::mainWidget() ) ) {
			TQFile tempFile( oname );
			if ( tempFile.open( IO_ReadOnly ) ) {
				list = tempFile.readAll();
				tempFile.close();
				TDEIO::NetAccess::removeTempFile( oname );
				// and store it
				kdDebug( 14100 ) << "loaded list:" << endl;
				kdDebug( 14100 ) << list << endl;
				kdDebug( 14100 ) << " --------------- " << endl;
				userlist( p->textcodec_->toUnicode( list ) );
			}
			else {
				error( tempFile.errorString(),
					i18n( "Contacts List Load Has Failed" ) );
			}
		}
		else {
			// say, it failed misourably
			error( TDEIO::NetAccess::lastErrorString(),
				i18n( "Contacts List Load Has Failed" ) );
		}

	}
	delete p->loadListDialog;
	p->loadListDialog = NULL;
}

unsigned int
GaduAccount::getPersonalInformation()
{
	return p->session_->getPersonalInformation();
}

bool 
GaduAccount::publishPersonalInformation( ResLine& d )
{
	return p->session_->publishPersonalInformation( d );
}

void
GaduAccount::slotExportContactsList()
{
	p->session_->exportContactsOnServer( userlist() );
}

GaduContactsList*
GaduAccount::userlist()
{
	GaduContact* contact;
	GaduContactsList* contactsList = new GaduContactsList();
	int i;

	if ( !contacts().count() ) {
		return contactsList;
	}

	TQDictIterator<Kopete::Contact> contactsIterator( contacts() );

	for( i=0 ; contactsIterator.current() ; ++contactsIterator ) {
		contact = static_cast<GaduContact*>( *contactsIterator );
		if ( contact->uin() != static_cast<GaduContact*>( myself() )->uin() ) {
			contactsList->addContact( *contact->contactDetails() );
		}
	}

	return contactsList;
}

void
GaduAccount::slotSearch( int uin )
{
	new GaduPublicDir( this, uin );
}

void
GaduAccount::slotChangePassword()
{
}

void
GaduAccount::slotCommandDone( const TQString& /*title*/, const TQString& what )
{
	//FIXME: any chance to have my own title in event popup ?
	KNotifyClient::userEvent( 0, what,
			KNotifyClient::PassivePopup, KNotifyClient::Notification  );
}

void
GaduAccount::slotCommandError(const TQString& title, const TQString& what )
{
	error( title, what );
}

void
GaduAccount::slotDescription()
{
	GaduAway* away = new GaduAway( this );

	if( away->exec() == TQDialog::Accepted ) {
		changeStatus( GaduProtocol::protocol()->convertStatus( away->status() ),
					away->awayText() );
	}
	delete away;
}

unsigned int
GaduAccount::pubDirSearch( ResLine& query, int ageFrom, int ageTo, bool onlyAlive )
{
	return p->session_->pubDirSearch( query, ageFrom, ageTo, onlyAlive );
}

void
GaduAccount::pubDirSearchClose()
{
	p->session_->pubDirSearchClose();
}

void
GaduAccount::slotSearchResult( const SearchResult& result, unsigned int seq )
{
	emit pubDirSearchResult( result, seq );
}

void
GaduAccount::sendFile( GaduContact* peer, TQString& filePath )
{
	GaduDCCTransaction* gtran = new GaduDCCTransaction( p->gaduDcc_ );
	gtran->setupOutgoing( peer, filePath );
}

void
GaduAccount::dccRequest( GaduContact* peer )
{
	if ( peer && p->session_ ) {
		p->session_->dccRequest( peer->uin() );
	}
}

// dcc settings
bool
GaduAccount::dccEnabled()
{
	TQString s = p->config->readEntry( TQString::fromAscii( "useDcc" ) );
	kdDebug( 14100 ) << "dccEnabled: "<< s << endl;
	if ( s == TQString::fromAscii( "enabled" ) ) {
		return true;
	}
	return false;
}

bool
GaduAccount::setDcc( bool d )
{
	TQString s;
	bool f = true;

	if ( d == false ) {
		dccOff();
		s = TQString::fromAscii( "disabled" );
	}
	else {
		s = TQString::fromAscii( "enabled" );
	}

	p->config->writeEntry( TQString::fromAscii( "useDcc" ), s );

	if ( p->session_->isConnected() && d ) {
		dccOn();
	}

	kdDebug( 14100 ) << "s: "<<s<<endl;

	return f;
}

void
GaduAccount::saveFriendsMode( bool i )
{
	p->config->writeEntry( TQString(TQString::fromAscii( "forFriends" )), 
			TQString(i == true ? TQString::fromAscii( "1" ) : TQString::fromAscii( "0" ) ));
}

bool
GaduAccount::loadFriendsMode()
{
	TQString s;
	bool r;
	int n;

	s = p->config->readEntry( TQString::fromAscii( "forFriends" ) );
	n = s.toInt( &r );

	if ( n ) {
		return true;
	}

	return false;

}

// might be bit inconsistent with what I used in DCC, but hell, it is so much easier to parse :-)
bool
GaduAccount::ignoreAnons()
{
	TQString s;
	bool r;
	int n;
	
	s = p->config->readEntry( TQString::fromAscii( "ignoreAnons" ) );
	n = s.toInt( &r );
	
	if ( n ) {
		return true;
	}

	return false;
	
}

void 
GaduAccount::setIgnoreAnons( bool i )
{
	p->ignoreAnons = i;
	p->config->writeEntry( TQString(TQString::fromAscii( "ignoreAnons" )), 
			TQString(i == true ? TQString::fromAscii( "1" ) : TQString::fromAscii( "0" ) ));
}

GaduAccount::tlsConnection
GaduAccount::useTls()
{
	TQString s;
	bool c;
	unsigned int oldC;
	tlsConnection Tls;

	s = p->config->readEntry( TQString::fromAscii( "useEncryptedConnection" ) );
	oldC = s.toUInt( &c );
	// we have old format
	if ( c ) {
		kdDebug( 14100 ) << "old format for param useEncryptedConnection, value " <<
				oldC << " willl be converted to new string value" << endl;
		setUseTls( (tlsConnection) oldC );
		// should be string now, unless there was an error reading
		s = p->config->readEntry( TQString::fromAscii( "useEncryptedConnection" ) );
		kdDebug( 14100 ) << "new useEncryptedConnection value : " << s << endl;
	}

	Tls = TLS_no;
	if ( s == "TLS_ifAvaliable" ) {
		Tls = TLS_ifAvaliable;
	}
	if ( s == "TLS_only" ) {
		Tls = TLS_only;
	}

	return Tls;
}

void
GaduAccount::setUseTls( tlsConnection ut )
{
	TQString s;
	switch( ut ) {
		case TLS_ifAvaliable:
			s = "TLS_ifAvaliable";
		break;

		case TLS_only:
			s = "TLS_only";
		break;

		default:
		case TLS_no:
			s = "TLS_no";
		break;
	}

	p->config->writeEntry( TQString::fromAscii( "useEncryptedConnection" ), s );
}

#include "gaduaccount.moc"
