/*
	client.cpp - Kopete Oscar Protocol

	Copyright (c) 2004-2005 Matt Rogers <mattr@kde.org>

	Based on code Copyright (c) 2004 SuSE Linux AG <http://www.suse.com>
	Based on Iris, Copyright (C) 2003  Justin Karneges

	Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org>

	*************************************************************************
	*                                                                       *
	* This library is free software; you can redistribute it and/or         *
	* modify it under the terms of the GNU Lesser General Public            *
	* License as published by the Free Software Foundation; either          *
	* version 2 of the License, or (at your option) any later version.      *
	*                                                                       *
	*************************************************************************
*/

#include "client.h"

#include <qtimer.h>
#include <qtextcodec.h>

#include <kdebug.h> //for kdDebug()
#include <klocale.h>

#include "buddyicontask.h"
#include "clientreadytask.h"
#include "connectionhandler.h"
#include "changevisibilitytask.h"
#include "chatnavservicetask.h"
#include "errortask.h"
#include "icquserinfo.h"
#include "icquserinfotask.h"
#include "logintask.h"
#include "connection.h"
#include "messagereceivertask.h"
#include "onlinenotifiertask.h"
#include "oscarclientstream.h"
#include "oscarconnector.h"
#include "oscarsettings.h"
#include "oscarutils.h"
#include "ownuserinfotask.h"
#include "profiletask.h"
#include "senddcinfotask.h"
#include "sendmessagetask.h"
#include "serverredirecttask.h"
#include "servicesetuptask.h"
#include "ssimanager.h"
#include "ssimodifytask.h"
#include "ssiauthtask.h"
#include "offlinemessagestask.h"
#include "task.h"
#include "typingnotifytask.h"
#include "userinfotask.h"
#include "usersearchtask.h"
#include "warningtask.h"
#include "chatservicetask.h"
#include "rateclassmanager.h"


namespace
{
	class DefaultCodecProvider : public Client::CodecProvider
	{
	public:
		virtual QTextCodec* codecForContact( const QString& ) const
		{
			return QTextCodec::codecForMib( 4 );
		}
		virtual QTextCodec* codecForAccount() const
		{
			return QTextCodec::codecForMib( 4 );
		}
	};

	DefaultCodecProvider defaultCodecProvider;
}

class Client::ClientPrivate
{
public:
	ClientPrivate() {}

	QString host, user, pass;
	uint port;
	int tzoffset;
	bool active;

	enum { StageOne, StageTwo };
	int stage;

	//Protocol specific data
	bool isIcq;
	bool redirectRequested;
	QValueList<WORD> redirectionServices;
    WORD currentRedirect;
	QByteArray cookie;
	DWORD connectAsStatus; // icq only
	QString connectWithMessage; // icq only
	Oscar::Settings* settings;

	//Tasks
	ErrorTask* errorTask;
	OnlineNotifierTask* onlineNotifier;
	OwnUserInfoTask* ownStatusTask;
	MessageReceiverTask* messageReceiverTask;
	SSIAuthTask* ssiAuthTask;
	ICQUserInfoRequestTask* icqInfoTask;
	UserInfoTask* userInfoTask;
	TypingNotifyTask * typingNotifyTask;
	SSIModifyTask* ssiModifyTask;
	//Managers
	SSIManager* ssiManager;
	ConnectionHandler connections;

	//Our Userinfo
	UserDetails ourDetails;

    //Infos
    QValueList<int> exchanges;

	QString statusMessage; // for away-,DND-message etc...

	//away messages
	struct AwayMsgRequest
	{
		QString contact;
		ICQStatus contactStatus;
	};
	QValueList<AwayMsgRequest> awayMsgRequestQueue;
	QTimer* awayMsgRequestTimer;
	CodecProvider* codecProvider;
	
	const Oscar::ClientVersion* version;
};

Client::Client( QObject* parent )
:QObject( parent, "oscarclient" )
{
	m_loginTask = 0L;
	m_loginTaskTwo = 0L;

	d = new ClientPrivate;
	d->tzoffset = 0;
	d->active = false;
	d->isIcq = false; //default to AIM
	d->redirectRequested = false;
    d->currentRedirect = 0;
	d->connectAsStatus = 0x0; // default to online
	d->ssiManager = new SSIManager( this );
	d->settings = new Oscar::Settings();
	d->errorTask = 0L;
	d->onlineNotifier = 0L;
	d->ownStatusTask = 0L;
	d->messageReceiverTask = 0L;
	d->ssiAuthTask = 0L;
	d->icqInfoTask = 0L;
	d->userInfoTask = 0L;
	d->stage = ClientPrivate::StageOne;
	d->typingNotifyTask = 0L;
	d->ssiModifyTask = 0L;
	d->awayMsgRequestTimer = new QTimer();
	d->codecProvider = &defaultCodecProvider;

	connect( this, SIGNAL( redirectionFinished( WORD ) ),
	         this, SLOT( checkRedirectionQueue( WORD ) ) );
	connect( d->awayMsgRequestTimer, SIGNAL( timeout() ),
	         this, SLOT( nextICQAwayMessageRequest() ) );
}

Client::~Client()
{

	//delete the connections differently than in deleteConnections()
	//deleteLater() seems to cause destruction order issues
	deleteStaticTasks();
    delete d->settings;
	delete d->ssiManager;
	delete d->awayMsgRequestTimer;
	delete d;
}

Oscar::Settings* Client::clientSettings() const
{
	return d->settings;
}

void Client::connectToServer( Connection *c, const QString& server, bool auth )
{
	d->connections.append( c );
	if ( auth == true )
	{
		m_loginTask = new StageOneLoginTask( c->rootTask() );
		connect( m_loginTask, SIGNAL( finished() ), this, SLOT( lt_loginFinished() ) );
	}

	connect( c, SIGNAL( socketError( int, const QString& ) ), this, SLOT( determineDisconnection( int, const QString& ) ) );
	c->connectToServer(server, auth);
}

void Client::start( const QString &host, const uint port, const QString &userId, const QString &pass )
{
	Q_UNUSED( host );
	Q_UNUSED( port );
	d->user = userId;
	d->pass = pass;
	d->stage = ClientPrivate::StageOne;
	d->active = false;
}

void Client::close()
{
	d->active = false;
	d->awayMsgRequestTimer->stop();
	d->awayMsgRequestQueue.clear();
	d->connections.clear();
	deleteStaticTasks();

	//don't clear the stored status between stage one and two
	if ( d->stage == ClientPrivate::StageTwo )
	{
		d->connectAsStatus = 0x0;
		d->connectWithMessage = QString::null;
	}

    d->exchanges.clear();
    d->redirectRequested = false;
    d->currentRedirect = 0;
    d->redirectionServices.clear();
    d->ssiManager->clear();
}

void Client::setStatus( AIMStatus status, const QString &_message )
{
	// AIM: you're away exactly when your away message isn't empty.
	// can't use QString::null as a message either; ProfileTask
	// interprets null as "don't change".
	QString message;
	if ( status == Online )
		message = QString::fromAscii("");
	else
	{
		if ( _message.isEmpty() )
			message = QString::fromAscii(" ");
		else
			message = _message;
	}

	Connection* c = d->connections.connectionForFamily( 0x0002 );
	if ( !c )
		return;
	ProfileTask* pt = new ProfileTask( c->rootTask() );
	pt->setAwayMessage( message );
	pt->go( true );
}

void Client::setStatus( DWORD status, const QString &message )
{
	// remember the message to reply with, when requested
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Setting status message to "<< message << endl;
	d->statusMessage = message;
	// ICQ: if we're active, set status. otherwise, just store the status for later.
	if ( d->active )
	{
		//the first connection is always the BOS connection
		Connection* c = d->connections.connectionForFamily( 0x0013 );
		if ( !c )
			return; //TODO trigger an error of some sort?

		ChangeVisibilityTask* cvt = new ChangeVisibilityTask( c->rootTask() );
		if ( ( status & 0x0100 ) == 0x0100 )
		{
			kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Setting invisible" << endl;
			cvt->setVisible( false );
		}
		else
		{
			kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Setting visible" << endl;
			cvt->setVisible( true );
		}
		cvt->go( true );
		c = d->connections.connectionForFamily( 0x0002 );
		if ( !c )
			return;

		SendDCInfoTask* sdcit = new SendDCInfoTask( c->rootTask(), status );
		sdcit->go( true ); //autodelete
		// TODO: send away message
	}
	else
	{
		d->connectAsStatus = status;
		d->connectWithMessage = message;
	}
}

UserDetails Client::ourInfo() const
{
	return d->ourDetails;
}

QString Client::host()
{
	return d->host;
}

int Client::port()
{
	return d->port;
}

SSIManager* Client::ssiManager() const
{
	return d->ssiManager;
}

const Oscar::ClientVersion* Client::version() const
{
	return d->version;
}

// SLOTS //

void Client::streamConnected()
{
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << endl;
	d->stage = ClientPrivate::StageTwo;
	if ( m_loginTaskTwo )
		m_loginTaskTwo->go();
}

void Client::lt_loginFinished()
{
	/* Check for stage two login first, since we create the stage two
	 * task when we finish stage one
	 */
	if ( d->stage == ClientPrivate::StageTwo )
	{
		//we've finished logging in. start the services setup
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "stage two done. setting up services" << endl;
		initializeStaticTasks();
		ServiceSetupTask* ssTask = new ServiceSetupTask( d->connections.defaultConnection()->rootTask() );
		connect( ssTask, SIGNAL( finished() ), this, SLOT( serviceSetupFinished() ) );
		ssTask->go( true ); //fire and forget
		m_loginTaskTwo->deleteLater();
		m_loginTaskTwo = 0;
	}
	else if ( d->stage == ClientPrivate::StageOne )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "stage one login done" << endl;
		disconnect( m_loginTask, SIGNAL( finished() ), this, SLOT( lt_loginFinished() ) );

		if ( m_loginTask->statusCode() == 0 ) //we can start stage two
		{
			kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "no errors from stage one. moving to stage two" << endl;

			//cache these values since they'll be deleted when we close the connections (which deletes the tasks)
			d->host = m_loginTask->bosServer();
			d->port = m_loginTask->bosPort().toUInt();
			d->cookie = m_loginTask->loginCookie();
			close();
			QTimer::singleShot( 100, this, SLOT(startStageTwo() ) );
		}
		else
		{
			kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "errors reported. not moving to stage two" << endl;
			close(); //deletes the connections for us
		}

		m_loginTask->deleteLater();
		m_loginTask = 0;
	}

}

void Client::startStageTwo()
{
	//create a new connection and set it up
	Connection* c = createConnection( d->host, QString::number( d->port ) );
	new CloseConnectionTask( c->rootTask() );

	//create the new login task
	m_loginTaskTwo = new StageTwoLoginTask( c->rootTask() );
	m_loginTaskTwo->setCookie( d->cookie );
	QObject::connect( m_loginTaskTwo, SIGNAL( finished() ), this, SLOT( lt_loginFinished() ) );


	//connect
	QObject::connect( c, SIGNAL( connected() ), this, SLOT( streamConnected() ) );
	connectToServer( c, d->host, false ) ;

}

void Client::serviceSetupFinished()
{
	d->active = true;

	if ( isIcq() )
		setStatus( d->connectAsStatus, d->connectWithMessage );

	d->ownStatusTask->go();

	if ( isIcq() )
	{
		//retrieve offline messages
		Connection* c = d->connections.connectionForFamily( 0x0015 );
		if ( !c )
			return;

		OfflineMessagesTask *offlineMsgTask = new OfflineMessagesTask( c->rootTask() );
		connect( offlineMsgTask, SIGNAL( receivedOfflineMessage(const Oscar::Message& ) ),
				this, SIGNAL( messageReceived(const Oscar::Message& ) ) );
		offlineMsgTask->go( true );
	}

	emit haveSSIList();
	emit loggedIn();
}

void Client::receivedIcqInfo( const QString& contact, unsigned int type )
{
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "received icq info for " << contact
		<< " of type " << type << endl;

	if ( type == ICQUserInfoRequestTask::Short )
		emit receivedIcqShortInfo( contact );
	else
		emit receivedIcqLongInfo( contact );
}

void Client::receivedInfo( Q_UINT16 sequence )
{
	UserDetails details = d->userInfoTask->getInfoFor( sequence );
	emit receivedUserInfo( details.userId(), details );
}

void Client::offlineUser( const QString& user, const UserDetails& )
{
	emit userIsOffline( user );
}

void Client::haveOwnUserInfo()
{
	kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << endl;
	UserDetails ud = d->ownStatusTask->getInfo();
	d->ourDetails = ud;
	emit haveOwnInfo();
}

void Client::setCodecProvider( Client::CodecProvider* codecProvider )
{
	d->codecProvider = codecProvider;
}

void Client::setVersion( const Oscar::ClientVersion* version )
{
	d->version = version;
}

// INTERNALS //

QString Client::userId() const
{
	return d->user;
}

QString Client::password() const
{
	return d->pass;
}

QString Client::statusMessage() const
{
	return d->statusMessage;
}

void Client::setStatusMessage( const QString &message )
{
	d->statusMessage = message;
}

QCString Client::ipAddress() const
{
	//!TODO determine ip address
	return "127.0.0.1";
}

void Client::notifyTaskError( const Oscar::SNAC& s, int errCode, bool fatal )
{
	emit taskError( s, errCode, fatal );
}

void Client::notifySocketError( int errCode, const QString& msg )
{
	emit socketError( errCode, msg );
}

void Client::sendMessage( const Oscar::Message& msg, bool isAuto)
{
    Connection* c = 0L;
    if ( msg.type() == 0x0003 )
    {
        c = d->connections.connectionForChatRoom( msg.exchange(), msg.chatRoom() );
        if ( !c )
            return;

        kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "sending message to chat room" << endl;
        ChatServiceTask* cst = new ChatServiceTask( c->rootTask(), msg.exchange(), msg.chatRoom() );
        cst->setMessage( msg );
        cst->setEncoding( d->codecProvider->codecForAccount()->name() );
        cst->go( true );
    }
    else
    {
        c = d->connections.connectionForFamily( 0x0004 );
        if ( !c )
            return;
        SendMessageTask *sendMsgTask = new SendMessageTask( c->rootTask() );
        // Set whether or not the message is an automated response
        sendMsgTask->setAutoResponse( isAuto );
        sendMsgTask->setMessage( msg );
        sendMsgTask->go( true );
    }
}

void Client::receivedMessage( const Oscar::Message& msg )
{
	if ( msg.type() == 2 && !msg.hasProperty( Oscar::Message::AutoResponse ) )
	{
		// type 2 message needs an autoresponse, regardless of type
		Connection* c = d->connections.connectionForFamily( 0x0004 );
		if ( !c )
			return;
		
		Oscar::Message response ( msg );
		if ( msg.hasProperty( Oscar::Message::StatusMessageRequest ) )
		{
			QTextCodec* codec = d->codecProvider->codecForContact( msg.sender() );
			response.setText( Oscar::Message::UserDefined, statusMessage(), codec );
		}
		else
		{
			response.setEncoding( Oscar::Message::UserDefined );
			response.setTextArray( QByteArray() );
		}
		response.setReceiver( msg.sender() );
		response.addProperty( Oscar::Message::AutoResponse );
		SendMessageTask *sendMsgTask = new SendMessageTask( c->rootTask() );
		sendMsgTask->setMessage( response );
		sendMsgTask->go( true );
	}
	if ( msg.hasProperty( Oscar::Message::StatusMessageRequest ) )
	{
		if ( msg.hasProperty( Oscar::Message::AutoResponse ) )
		{
			// we got a response to a status message request.
			QString awayMessage( msg.text( d->codecProvider->codecForContact( msg.sender() ) ) );
			kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Received an away message: " << awayMessage << endl;
			emit receivedAwayMessage( msg.sender(), awayMessage );
		}
	}
	else if ( ! msg.hasProperty( Oscar::Message::AutoResponse ) )
	{
		// let application handle it
		kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Emitting receivedMessage" << endl;
		emit messageReceived( msg );
	}
}

void Client::requestAuth( const QString& contactid, const QString& reason )
{
	Connection* c = d->connections.connectionForFamily( 0x0013 );
	if ( !c )
		return;
	d->ssiAuthTask->sendAuthRequest( contactid, reason );
}

void Client::sendAuth( const QString& contactid, const QString& reason, bool auth )
{
	Connection* c = d->connections.connectionForFamily( 0x0013 );
	if ( !c )
		return;
	d->ssiAuthTask->sendAuthReply( contactid, reason, auth );
}

bool Client::isActive() const
{
	return d->active;
}

bool Client::isIcq() const
{
	return d->isIcq;
}

void Client::setIsIcq( bool isIcq )
{
	d->isIcq = isIcq;
}

void Client::debug( const QString& str )
{
	Q_UNUSED(str);
//	qDebug( "CLIENT: %s", str.ascii() );
}

void Client::initializeStaticTasks()
{
	//set up the extra tasks
	Connection* c = d->connections.defaultConnection();
	if ( !c )
		return;
	d->errorTask = new ErrorTask( c->rootTask() );
	d->onlineNotifier = new OnlineNotifierTask( c->rootTask() );
	d->ownStatusTask = new OwnUserInfoTask( c->rootTask() );
	d->messageReceiverTask = new MessageReceiverTask( c->rootTask() );
	d->ssiAuthTask = new SSIAuthTask( c->rootTask() );
	d->icqInfoTask = new ICQUserInfoRequestTask( c->rootTask() );
	d->userInfoTask = new UserInfoTask( c->rootTask() );
	d->typingNotifyTask = new TypingNotifyTask( c->rootTask() );
	d->ssiModifyTask = new SSIModifyTask( c->rootTask(), true );

	connect( d->onlineNotifier, SIGNAL( userIsOnline( const QString&, const UserDetails& ) ),
	         this, SIGNAL( receivedUserInfo( const QString&, const UserDetails& ) ) );
	connect( d->onlineNotifier, SIGNAL( userIsOffline( const QString&, const UserDetails& ) ),
	         this, SLOT( offlineUser( const QString&, const UserDetails & ) ) );

	connect( d->ownStatusTask, SIGNAL( gotInfo() ), this, SLOT( haveOwnUserInfo() ) );
	connect( d->ownStatusTask, SIGNAL( buddyIconUploadRequested() ), this,
	         SIGNAL( iconNeedsUploading() ) );

	connect( d->messageReceiverTask, SIGNAL( receivedMessage( const Oscar::Message& ) ),
	         this, SLOT( receivedMessage( const Oscar::Message& ) ) );

	connect( d->ssiAuthTask, SIGNAL( authRequested( const QString&, const QString& ) ),
	         this, SIGNAL( authRequestReceived( const QString&, const QString& ) ) );
	connect( d->ssiAuthTask, SIGNAL( authReplied( const QString&, const QString&, bool ) ),
	         this, SIGNAL( authReplyReceived( const QString&, const QString&, bool ) ) );

	connect( d->icqInfoTask, SIGNAL( receivedInfoFor( const QString&, unsigned int ) ),
	         this, SLOT( receivedIcqInfo( const QString&, unsigned int ) ) );

	connect( d->userInfoTask, SIGNAL( receivedProfile( const QString&, const QString& ) ),
	         this, SIGNAL( receivedProfile( const QString&, const QString& ) ) );
	connect( d->userInfoTask, SIGNAL( receivedAwayMessage( const QString&, const QString& ) ),
	         this, SIGNAL( receivedAwayMessage( const QString&, const QString& ) ) );
	connect( d->typingNotifyTask, SIGNAL( typingStarted( const QString& ) ),
	         this, SIGNAL( userStartedTyping( const QString& ) ) );
	connect( d->typingNotifyTask, SIGNAL( typingFinished( const QString& ) ),
	         this, SIGNAL( userStoppedTyping( const QString& ) ) );
}

void Client::removeGroup( const QString& groupName )
{
	Connection* c = d->connections.connectionForFamily( 0x0013 );
	if ( !c )
		return;

	kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Removing group " << groupName << " from SSI" << endl;
	SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
	if ( ssimt->removeGroup( groupName ) )
		ssimt->go( true );
	else
		delete ssimt;
}

void Client::addGroup( const QString& groupName )
{
	Connection* c = d->connections.connectionForFamily( 0x0013 );
	if ( !c )
		return;

	kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Adding group " << groupName << " to SSI" << endl;
	SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
	if ( ssimt->addGroup( groupName ) )
		ssimt->go( true );
	else
		delete ssimt;
}

void Client::addContact( const QString& contactName, const QString& groupName )
{
	Connection* c = d->connections.connectionForFamily( 0x0013 );
	if ( !c )
		return;

	kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Adding contact " << contactName << " to SSI in group " << groupName << endl;
	SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
	if ( ssimt->addContact( contactName, groupName )  )
		ssimt->go( true );
	else
		delete ssimt;
}

void Client::removeContact( const QString& contactName )
{
	Connection* c = d->connections.connectionForFamily( 0x0013 );
	if ( !c )
		return;

	kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Removing contact " << contactName << " from SSI" << endl;
	SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
	if ( ssimt->removeContact( contactName ) )
		ssimt->go( true );
	else
		delete ssimt;
}

void Client::renameGroup( const QString & oldGroupName, const QString & newGroupName )
{
	Connection* c = d->connections.connectionForFamily( 0x0013 );
	if ( !c )
		return;

	kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Renaming group " << oldGroupName << " to " << newGroupName << endl;
	SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
	if ( ssimt->renameGroup( oldGroupName, newGroupName ) )
		ssimt->go( true );
	else
		delete ssimt;
}

void Client::modifySSIItem( const Oscar::SSI& oldItem, const Oscar::SSI& newItem )
{
	int action = 0; //0 modify, 1 add, 2 remove TODO cleanup!
	Connection* c = d->connections.connectionForFamily( 0x0013 );
	if ( !c )
		return;

	if ( !oldItem && newItem )
		action = 1;
	if ( oldItem && !newItem )
		action = 2;

	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Add/Mod/Del item on server" << endl;
	SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
	switch ( action )
	{
	case 0:
		if ( ssimt->modifyItem( oldItem, newItem ) )
			ssimt->go( true );
		else
			delete ssimt;
		break;
	case 1:
		if ( ssimt->addItem( newItem ) )
			ssimt->go( true );
		else
			delete ssimt;
		break;
	case 2:
		if ( ssimt->removeItem( oldItem ) )
			ssimt->go( true );
		else
			delete ssimt;
		break;
	}
}

void Client::changeContactGroup( const QString& contact, const QString& newGroupName )
{
	Connection* c = d->connections.connectionForFamily( 0x0013 );
	if ( !c )
		return;

	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Changing " << contact << "'s group to "
		<< newGroupName << endl;
	SSIModifyTask* ssimt = new SSIModifyTask( c->rootTask() );
	if ( ssimt->changeGroup( contact, newGroupName ) )
		ssimt->go( true );
	else
		delete ssimt;
}

void Client::requestFullInfo( const QString& contactId )
{
	Connection* c = d->connections.connectionForFamily( 0x0015 );
	if ( !c )
		return;
	d->icqInfoTask->setUser( contactId );
	d->icqInfoTask->setType( ICQUserInfoRequestTask::Long );
	d->icqInfoTask->go();
}

void Client::requestShortInfo( const QString& contactId )
{
	Connection* c = d->connections.connectionForFamily( 0x0015 );
	if ( !c )
		return;
	d->icqInfoTask->setUser( contactId );
	d->icqInfoTask->setType( ICQUserInfoRequestTask::Short );
	d->icqInfoTask->go();
}

void Client::sendWarning( const QString& contact, bool anonymous )
{
	Connection* c = d->connections.connectionForFamily( 0x0004 );
	if ( !c )
		return;
	WarningTask* warnTask = new WarningTask( c->rootTask() );
	warnTask->setContact( contact );
	warnTask->setAnonymous( anonymous );
	QObject::connect( warnTask, SIGNAL( userWarned( const QString&, Q_UINT16, Q_UINT16 ) ),
	                  this, SIGNAL( userWarned( const QString&, Q_UINT16, Q_UINT16 ) ) );
	warnTask->go( true );
}

ICQGeneralUserInfo Client::getGeneralInfo( const QString& contact )
{
	return d->icqInfoTask->generalInfoFor( contact );
}

ICQWorkUserInfo Client::getWorkInfo( const QString& contact )
{
	return d->icqInfoTask->workInfoFor( contact );
}

ICQEmailInfo Client::getEmailInfo( const QString& contact )
{
	return d->icqInfoTask->emailInfoFor( contact );
}

ICQMoreUserInfo Client::getMoreInfo( const QString& contact )
{
	return d->icqInfoTask->moreInfoFor( contact );
}

ICQInterestInfo Client::getInterestInfo( const QString& contact )
{
	return d->icqInfoTask->interestInfoFor( contact );
}

ICQShortInfo Client::getShortInfo( const QString& contact )
{
	return d->icqInfoTask->shortInfoFor( contact );
}

QValueList<int> Client::chatExchangeList() const
{
    return d->exchanges;
}

void Client::setChatExchangeList( const QValueList<int>& exchanges )
{
	d->exchanges = exchanges;
}

void Client::requestAIMProfile( const QString& contact )
{
	d->userInfoTask->requestInfoFor( contact, UserInfoTask::Profile );
}

void Client::requestAIMAwayMessage( const QString& contact )
{
	d->userInfoTask->requestInfoFor( contact, UserInfoTask::AwayMessage );
}

void Client::requestICQAwayMessage( const QString& contact, ICQStatus contactStatus )
{
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "requesting away message for " << contact << endl;
	Oscar::Message msg;
	msg.setType( 2 );
	msg.setReceiver( contact );
	msg.addProperty( Oscar::Message::StatusMessageRequest );
	switch ( contactStatus )
	{
	case ICQAway:
		msg.setMessageType( 0xE8 ); // away
		break;
	case ICQOccupied:
		msg.setMessageType( 0xE9 ); // occupied
		break;
	case ICQNotAvailable:
		msg.setMessageType( 0xEA ); // not awailable
		break;
	case ICQDoNotDisturb:
		msg.setMessageType( 0xEB ); // do not disturb
		break;
	case ICQFreeForChat:
		msg.setMessageType( 0xEC ); // free for chat
		break;
	default:
		// may be a good way to deal with possible error and lack of online status message?
		emit receivedAwayMessage( contact, "Sorry, this protocol does not support this type of status message" );
		return;
	}
	sendMessage( msg );
}

void Client::addICQAwayMessageRequest( const QString& contact, ICQStatus contactStatus )
{
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "adding away message request for "
	                         << contact << " to queue" << endl;

	//remove old request if still exists
	removeICQAwayMessageRequest( contact );

	ClientPrivate::AwayMsgRequest amr = { contact, contactStatus };
	d->awayMsgRequestQueue.prepend( amr );
	
	if ( !d->awayMsgRequestTimer->isActive() )
		d->awayMsgRequestTimer->start( 1000 );
}

void Client::removeICQAwayMessageRequest( const QString& contact )
{
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "removing away message request for "
	                         << contact << " from queue" << endl;

	QValueList<ClientPrivate::AwayMsgRequest>::iterator it = d->awayMsgRequestQueue.begin();
	while ( it != d->awayMsgRequestQueue.end() )
	{
		if ( (*it).contact == contact )
			it = d->awayMsgRequestQueue.erase( it );
		else
			it++;
	}
}

void Client::nextICQAwayMessageRequest()
{
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "request queue count " << d->awayMsgRequestQueue.count() << endl;
	
	if ( d->awayMsgRequestQueue.empty() )
	{
		d->awayMsgRequestTimer->stop();
		return;
	}
	else
	{
		Connection* c = d->connections.connectionForFamily( 0x0004 );
		if ( !c )
			return;
		
		SNAC s = { 0x0004, 0x0006, 0x0000, 0x00000000 };
		//get time needed to restore level to initial
		//for some reason when we are long under initial level
		//icq server will start to block our messages
		int time = c->rateManager()->timeToInitialLevel( s );
		if ( time > 0 )
		{
			d->awayMsgRequestTimer->changeInterval( time );
			return;
		}
		else
		{
			d->awayMsgRequestTimer->changeInterval( 5000 );
		}
	}

	ClientPrivate::AwayMsgRequest amr;

	amr = d->awayMsgRequestQueue.back();
	d->awayMsgRequestQueue.pop_back();
	requestICQAwayMessage( amr.contact, amr.contactStatus );
}

void Client::requestStatusInfo( const QString& contact )
{
	d->userInfoTask->requestInfoFor( contact, UserInfoTask::General );
}

void Client::whitePagesSearch( const ICQWPSearchInfo& info )
{
	Connection* c = d->connections.connectionForFamily( 0x0015 );
	if ( !c )
		return;
	UserSearchTask* ust = new UserSearchTask( c->rootTask() );
	connect( ust, SIGNAL( foundUser( const ICQSearchResult& ) ),
	         this, SIGNAL( gotSearchResults( const ICQSearchResult& ) ) );
	connect( ust, SIGNAL( searchFinished( int ) ), this, SIGNAL( endOfSearch( int ) ) );
	ust->go( true ); //onGo does nothing in this task. This is just here so autodelete works
	ust->searchWhitePages( info );
}

void Client::uinSearch( const QString& uin )
{
	Connection* c = d->connections.connectionForFamily( 0x0015 );
	if ( !c )
		return;
	UserSearchTask* ust = new UserSearchTask( c->rootTask() );
	connect( ust, SIGNAL( foundUser( const ICQSearchResult& ) ),
	         this, SIGNAL( gotSearchResults( const ICQSearchResult& ) ) );
	connect( ust, SIGNAL( searchFinished( int ) ), this, SIGNAL( endOfSearch( int ) ) );
	ust->go( true ); //onGo does nothing in this task. This is just here so autodelete works
	ust->searchUserByUIN( uin );
}

void Client::updateProfile( const QString& profile )
{
	Connection* c = d->connections.connectionForFamily( 0x0002 );
	if ( !c )
		return;
	ProfileTask* pt = new ProfileTask( c->rootTask() );
	pt->setProfileText( profile );
	pt->go(true);
}

void Client::sendTyping( const QString & contact, bool typing )
{
	Connection* c = d->connections.connectionForFamily( 0x0004 );
	if ( !c )
		return;
	d->typingNotifyTask->setParams( contact, ( typing ? TypingNotifyTask::Begin : TypingNotifyTask::Finished ) );
	d->typingNotifyTask->go( false ); 	// don't delete the task after sending
}

void Client::connectToIconServer()
{
	Connection* c = d->connections.connectionForFamily( 0x0010 );
	if ( c )
		return;

	requestServerRedirect( 0x0010 );
	return;
}

void Client::setIgnore( const QString& user, bool ignore )
{
	Oscar::SSI item = ssiManager()->findItem( user,  ROSTER_IGNORE );
	if ( item && !ignore )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing " << user << " from ignore list" << endl;
		this->modifySSIItem( item, Oscar::SSI() );
	}
	else if ( !item && ignore )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding " << user << " to ignore list" << endl;
		Oscar::SSI s( user, 0, ssiManager()->nextContactId(), ROSTER_IGNORE, QValueList<TLV>() );
		this->modifySSIItem( Oscar::SSI(), s );
	}
}

void Client::setVisibleTo( const QString& user, bool visible )
{
	Oscar::SSI item = ssiManager()->findItem( user,  ROSTER_VISIBLE );
	if ( item && !visible )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing " << user << " from visible list" << endl;
		this->modifySSIItem( item, Oscar::SSI() );
	}
	else if ( !item && visible )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding " << user << " to visible list" << endl;
		Oscar::SSI s( user, 0, ssiManager()->nextContactId(), ROSTER_VISIBLE, QValueList<TLV>() );
		this->modifySSIItem( Oscar::SSI(), s );
	}
}

void Client::setInvisibleTo( const QString& user, bool invisible )
{
	Oscar::SSI item = ssiManager()->findItem( user,  ROSTER_INVISIBLE );
	if ( item && !invisible )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing " << user << " from invisible list" << endl;
		this->modifySSIItem( item, Oscar::SSI() );
	}
	else if ( !item && invisible )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding " << user << " to invisible list" << endl;
		Oscar::SSI s( user, 0, ssiManager()->nextContactId(), ROSTER_INVISIBLE, QValueList<TLV>() );
		this->modifySSIItem( Oscar::SSI(), s );
	}
}

void Client::requestBuddyIcon( const QString& user, const QByteArray& hash, BYTE hashType )
{
	Connection* c = d->connections.connectionForFamily( 0x0010 );
	if ( !c )
		return;

	BuddyIconTask* bit = new BuddyIconTask( c->rootTask() );
	connect( bit, SIGNAL( haveIcon( const QString&, QByteArray ) ),
	         this, SIGNAL( haveIconForContact( const QString&, QByteArray ) ) );
	bit->requestIconFor( user );
	bit->setHashType( hashType );
	bit->setHash( hash );
	bit->go( true );
}

void Client::requestServerRedirect( WORD family, WORD exchange,
                                    QByteArray cookie, WORD instance,
                                    const QString& room )
{
	//making the assumption that family 2 will always be the BOS connection
	//use it instead since we can't query for family 1
	Connection* c = d->connections.connectionForFamily( family );
	if ( c && family != 0x000E )
		return; //we already have the connection

	c = d->connections.connectionForFamily( 0x0002 );
	if ( !c )
		return;

    if ( d->redirectionServices.findIndex( family ) == -1 )
        d->redirectionServices.append( family ); //don't add families twice

    if ( d->currentRedirect != 0 )
        return; //we're already doing one redirection

	d->currentRedirect = family;

    //FIXME. this won't work if we have to defer the connection because we're
    //already connecting to something
	ServerRedirectTask* srt = new ServerRedirectTask( c->rootTask() );
    if ( family == 0x000E )
    {
        srt->setChatParams( exchange, cookie, instance );
        srt->setChatRoom( room );
    }

	connect( srt, SIGNAL( haveServer( const QString&, const QByteArray&, WORD ) ),
	         this, SLOT( haveServerForRedirect( const QString&, const QByteArray&, WORD ) ) );
	srt->setService( family );
	srt->go( true );
}

void Client::haveServerForRedirect( const QString& host, const QByteArray& cookie, WORD )
{
    //nasty sender() usage to get the task with chat room info
	QObject* o = const_cast<QObject*>( sender() );
    ServerRedirectTask* srt = dynamic_cast<ServerRedirectTask*>( o );

	//create a new connection and set it up
	int colonPos = host.find(':');
	QString realHost, realPort;
	if ( colonPos != -1 )
	{
		realHost = host.left( colonPos );
		realPort = host.right(4); //we only need 4 bytes
	}
	else
	{
		realHost = host;
		realPort = QString::fromLatin1("5190");
	}

	Connection* c = createConnection( realHost, realPort );
	//create the new login task
	m_loginTaskTwo = new StageTwoLoginTask( c->rootTask() );
	m_loginTaskTwo->setCookie( cookie );
	QObject::connect( m_loginTaskTwo, SIGNAL( finished() ), this, SLOT( serverRedirectFinished() ) );

	//connect
	connectToServer( c, d->host, false );
  	QObject::connect( c, SIGNAL( connected() ), this, SLOT( streamConnected() ) );

    if ( srt )
        d->connections.addChatInfoForConnection( c, srt->chatExchange(), srt->chatRoomName() );
}

void Client::serverRedirectFinished()
{
	if ( m_loginTaskTwo->statusCode() == 0 )
	{ //stage two was successful
		Connection* c = d->connections.connectionForFamily( d->currentRedirect );
		if ( !c )
			return;
		ClientReadyTask* crt = new ClientReadyTask( c->rootTask() );
		crt->setFamilies( c->supportedFamilies() );
		crt->go( true );
	}

	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "redirection finished for service "
	                         << d->currentRedirect << endl;

	if ( d->currentRedirect == 0x0010 )
		emit iconServerConnected();

	if ( d->currentRedirect == 0x000D )
	{
		connect( this, SIGNAL( chatNavigationConnected() ),
				 this, SLOT( requestChatNavLimits() ) );
		emit chatNavigationConnected();
	}

    if ( d->currentRedirect == 0x000E )
    {
        //HACK! such abuse! think of a better way
        if ( !m_loginTaskTwo )
        {
            kdWarning(OSCAR_RAW_DEBUG) << k_funcinfo << "no login task to get connection from!" << endl;
            emit redirectionFinished( d->currentRedirect );
            return;
        }

        Connection* c = m_loginTaskTwo->client();
        QString roomName = d->connections.chatRoomForConnection( c );
        WORD exchange = d->connections.exchangeForConnection( c );
        if ( c )
        {
            kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "setting up chat connection" << endl;
            ChatServiceTask* cst = new ChatServiceTask( c->rootTask(), exchange, roomName );
            connect( cst, SIGNAL( userJoinedChat( Oscar::WORD, const QString&, const QString& ) ),
                     this, SIGNAL( userJoinedChat( Oscar::WORD, const QString&, const QString& ) ) );
            connect( cst, SIGNAL( userLeftChat( Oscar::WORD, const QString&, const QString& ) ),
                     this, SIGNAL( userLeftChat( Oscar::WORD, const QString&, const QString& ) ) );
            connect( cst, SIGNAL( newChatMessage( const Oscar::Message& ) ),
                     this, SIGNAL( messageReceived( const Oscar::Message& ) ) );
        }
        emit chatRoomConnected( exchange, roomName );
    }

	emit redirectionFinished( d->currentRedirect );

}

void Client::checkRedirectionQueue( WORD family )
{
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "checking redirection queue" << endl;
	d->redirectionServices.remove( family );
    d->currentRedirect = 0;
	if ( !d->redirectionServices.isEmpty() )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "scheduling new redirection" << endl;
		requestServerRedirect( d->redirectionServices.front() );
	}
}


void Client::requestChatNavLimits()
{
	Connection* c = d->connections.connectionForFamily( 0x000D );
	if ( !c )
		return;

	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "requesting chat nav service limits" << endl;
	ChatNavServiceTask* cnst = new ChatNavServiceTask( c->rootTask() );
    cnst->setRequestType( ChatNavServiceTask::Limits );
    QObject::connect( cnst, SIGNAL( haveChatExchanges( const QValueList<int>& ) ),
                      this, SLOT( setChatExchangeList( const QValueList<int>& ) ) );
	cnst->go( true ); //autodelete

}

void Client::determineDisconnection( int code, const QString& string )
{
    if ( !sender() )
        return;

    //yay for the sender() hack!
    QObject* obj = const_cast<QObject*>( sender() );
    Connection* c = dynamic_cast<Connection*>( obj );
    if ( !c )
        return;

	if ( c->isSupported( 0x0002 ) ||
	     d->stage == ClientPrivate::StageOne ) //emit on login
	{
		emit socketError( code, string );
	}

    //connection is deleted. deleteLater() is used
    d->connections.remove( c );
    c = 0;
}

void Client::sendBuddyIcon( const QByteArray& iconData )
{
	Connection* c = d->connections.connectionForFamily( 0x0010 );
	if ( !c )
		return;

	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "icon length is " << iconData.size() << endl;
	BuddyIconTask* bit = new BuddyIconTask( c->rootTask() );
	bit->uploadIcon( iconData.size(), iconData );
	bit->go( true );
}

void Client::joinChatRoom( const QString& roomName, int exchange )
{
    Connection* c = d->connections.connectionForFamily( 0x000D );
    if ( !c )
        return;

    kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "joining the chat room '" << roomName
                             << "' on exchange " << exchange << endl;
    ChatNavServiceTask* cnst = new ChatNavServiceTask( c->rootTask() );
    connect( cnst, SIGNAL( connectChat( WORD, QByteArray, WORD, const QString& ) ),
             this, SLOT( setupChatConnection( WORD, QByteArray, WORD, const QString& ) ) );
    cnst->createRoom( exchange, roomName );

}

void Client::setupChatConnection( WORD exchange, QByteArray cookie, WORD instance, const QString& room )
{
    kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "cookie is:" << cookie << endl;
    QByteArray realCookie( cookie );
    kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "connection to chat room" << endl;
    requestServerRedirect( 0x000E, exchange, realCookie, instance, room );
}

void Client::disconnectChatRoom( WORD exchange, const QString& room )
{
    Connection* c = d->connections.connectionForChatRoom( exchange, room );
    if ( !c )
        return;

    d->connections.remove( c );
    c = 0;
}


Connection* Client::createConnection( const QString& host, const QString& port )
{
	KNetworkConnector* knc = new KNetworkConnector( 0 );
	knc->setOptHostPort( host, port.toUInt() );
	ClientStream* cs = new ClientStream( knc, 0 );
	cs->setNoopTime( 60000 );
	Connection* c = new Connection( knc, cs, "BOS" );
	cs->setConnection( c );
	c->setClient( this );
	return c;
}

void Client::deleteStaticTasks()
{
	delete d->errorTask;
	delete d->onlineNotifier;
	delete d->ownStatusTask;
	delete d->messageReceiverTask;
	delete d->ssiAuthTask;
	delete d->icqInfoTask;
	delete d->userInfoTask;
	delete d->typingNotifyTask;
	delete d->ssiModifyTask;

	d->errorTask = 0;
	d->onlineNotifier = 0;
	d->ownStatusTask = 0;
	d->messageReceiverTask = 0;
	d->ssiAuthTask = 0;
	d->icqInfoTask = 0;
	d->userInfoTask = 0;
	d->typingNotifyTask = 0;
	d->ssiModifyTask = 0;
}

bool Client::hasIconConnection( ) const
{
	Connection* c = d->connections.connectionForFamily( 0x0010 );
	return c;
}

#include "client.moc"
//kate: tab-width 4; indent-mode csands; space-indent off; replace-tabs off;
