/*
    oscaraccount.cpp  -  Oscar Account Class

    Copyright (c) 2002 by Tom Linsky <twl6@po.cwru.edu>
    Copyright (c) 2002 by Chris TenHarmsel <tenharmsel@staticmethod.net>
    Copyright (c) 2004 by Matt Rogers <mattr@kde.org>
    Kopete    (c) 2002-2004 by the Kopete developers  <kopete-devel@kde.org>

    *************************************************************************
    *                                                                       *
    * 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 "oscaraccount.h"

#include "kopetepassword.h"
#include "kopeteprotocol.h"
#include "kopeteaway.h"
#include "kopetemetacontact.h"
#include "kopetecontactlist.h"
#include "kopeteawaydialog.h"
#include "kopetegroup.h"
#include "kopeteuiglobal.h"
#include "kopetecontactlist.h"
#include "kopetecontact.h"
#include "kopetechatsession.h"

#include <assert.h>

#include <tqapplication.h>
#include <tqregexp.h>
#include <tqstylesheet.h>
#include <tqtimer.h>
#include <tqptrlist.h>
#include <tqtextcodec.h>
#include <tqimage.h>
#include <tqfile.h>

#include <kdebug.h>
#include <tdeconfig.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kpassivepopup.h>
#include <kstandarddirs.h>

#include "client.h"
#include "connection.h"
#include "oscartypeclasses.h"
#include "oscarmessage.h"
#include "oscarutils.h"
#include "oscarclientstream.h"
#include "oscarconnector.h"
#include "ssimanager.h"
#include "oscarlistnonservercontacts.h"
#include "oscarversionupdater.h"

class OscarAccountPrivate : public Client::CodecProvider
{
	// Backreference
	OscarAccount& account;
public:
	OscarAccountPrivate( OscarAccount& a ): account( a ) {}

	//The liboscar hook for the account
	Client* engine;

	TQ_UINT32 ssiLastModTime;

	//contacts waiting on SSI add ack and their metacontact
	TQMap<TQString, Kopete::MetaContact*> addContactMap;

	//contacts waiting on their group to be added
	TQMap<TQString, TQString> contactAddQueue;
	TQMap<TQString, TQString> contactChangeQueue;

    OscarListNonServerContacts* olnscDialog;
	
	unsigned int versionUpdaterStamp;
	bool versionAlreadyUpdated;

	virtual TQTextCodec* codecForContact( const TQString& contactName ) const
	{
		return account.contactCodec( Oscar::normalize( contactName ) );
	}

	virtual TQTextCodec* codecForAccount() const
	{
		return account.defaultCodec();
	}
};

OscarAccount::OscarAccount(Kopete::Protocol *parent, const TQString &accountID, const char *name, bool isICQ)
: Kopete::PasswordedAccount( parent, accountID, isICQ ? 8 : 16, name )
{
	kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << " accountID='" << accountID <<
		"', isICQ=" << isICQ << endl;

	d = new OscarAccountPrivate( *this );
	d->engine = new Client( this );
	d->engine->setIsIcq( isICQ );
	
	d->versionAlreadyUpdated = false;
	d->versionUpdaterStamp = OscarVersionUpdater::self()->stamp();
	if ( isICQ )
		d->engine->setVersion( OscarVersionUpdater::self()->getICQVersion() );
	else
		d->engine->setVersion( OscarVersionUpdater::self()->getAIMVersion() );

	d->engine->setCodecProvider( d );
    d->olnscDialog = 0L;
    TQObject::connect( d->engine, TQT_SIGNAL( loggedIn() ), this, TQT_SLOT( loginActions() ) );
	TQObject::connect( d->engine, TQT_SIGNAL( messageReceived( const Oscar::Message& ) ),
	                  this, TQT_SLOT( messageReceived(const Oscar::Message& ) ) );
	TQObject::connect( d->engine, TQT_SIGNAL( socketError( int, const TQString& ) ),
	                  this, TQT_SLOT( slotSocketError( int, const TQString& ) ) );
	TQObject::connect( d->engine, TQT_SIGNAL( taskError( const Oscar::SNAC&, int, bool ) ),
	                  this, TQT_SLOT( slotTaskError( const Oscar::SNAC&, int, bool ) ) );
	TQObject::connect( d->engine, TQT_SIGNAL( userStartedTyping( const TQString& ) ),
	                  this, TQT_SLOT( userStartedTyping( const TQString& ) ) );
	TQObject::connect( d->engine, TQT_SIGNAL( userStoppedTyping( const TQString& ) ),
	                  this, TQT_SLOT( userStoppedTyping( const TQString& ) ) );
	TQObject::connect( d->engine, TQT_SIGNAL( iconNeedsUploading() ),
	                  this, TQT_SLOT( slotSendBuddyIcon() ) );
}

OscarAccount::~OscarAccount()
{
	OscarAccount::disconnect();
	delete d;
}

Client* OscarAccount::engine()
{
	return d->engine;
}

void OscarAccount::logOff( Kopete::Account::DisconnectReason reason )
{
	kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "accountId='" << accountId() << "'" << endl;
	//disconnect the signals
	Kopete::ContactList* kcl = Kopete::ContactList::self();
	TQObject::disconnect( kcl, TQT_SIGNAL( groupRenamed( Kopete::Group*,  const TQString& ) ),
	                     this, TQT_SLOT( kopeteGroupRenamed( Kopete::Group*, const TQString& ) ) );
	TQObject::disconnect( kcl, TQT_SIGNAL( groupRemoved( Kopete::Group* ) ),
	                     this, TQT_SLOT( kopeteGroupRemoved( Kopete::Group* ) ) );
	TQObject::disconnect( d->engine->ssiManager(), TQT_SIGNAL( contactAdded( const Oscar::SSI& ) ),
	                     this, TQT_SLOT( ssiContactAdded( const Oscar::SSI& ) ) );
	TQObject::disconnect( d->engine->ssiManager(), TQT_SIGNAL( groupAdded( const Oscar::SSI& ) ),
	                     this, TQT_SLOT( ssiGroupAdded( const Oscar::SSI& ) ) );
	TQObject::disconnect( d->engine->ssiManager(), TQT_SIGNAL( groupUpdated( const Oscar::SSI& ) ),
	                     this, TQT_SLOT( ssiGroupUpdated( const Oscar::SSI& ) ) );
	TQObject::disconnect( d->engine->ssiManager(), TQT_SIGNAL( contactUpdated( const Oscar::SSI& ) ),
	                     this, TQT_SLOT( ssiContactUpdated( const Oscar::SSI& ) ) );

	d->engine->close();
	myself()->setOnlineStatus( Kopete::OnlineStatus::Offline );

	d->contactAddQueue.clear();
	d->contactChangeQueue.clear();

	disconnected( reason );
}

void OscarAccount::disconnect()
{
	logOff( Kopete::Account::Manual );
}

bool OscarAccount::passwordWasWrong()
{
	return password().isWrong();
}

void OscarAccount::loginActions()
{
    password().setWrong( false );
    kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "processing SSI list" << endl;
    processSSIList();

	//start a chat nav connection
	if ( !engine()->isIcq() )
	{
		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "sending request for chat nav service" << endl;
		d->engine->requestServerRedirect( 0x000D );
	}

	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "sending request for icon service" << endl;
	d->engine->requestServerRedirect( 0x0010 );

}

void OscarAccount::processSSIList()
{
	//disconnect signals so we don't attempt to add things to SSI!
	Kopete::ContactList* kcl = Kopete::ContactList::self();
	TQObject::disconnect( kcl, TQT_SIGNAL( groupRenamed( Kopete::Group*,  const TQString& ) ),
	                     this, TQT_SLOT( kopeteGroupRenamed( Kopete::Group*, const TQString& ) ) );
	TQObject::disconnect( kcl, TQT_SIGNAL( groupRemoved( Kopete::Group* ) ),
	                     this, TQT_SLOT( kopeteGroupRemoved( Kopete::Group* ) ) );

	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << endl;

	SSIManager* listManager = d->engine->ssiManager();

    //first add groups
	TQValueList<SSI> groupList = listManager->groupList();
	TQValueList<SSI>::const_iterator git = groupList.constBegin();
	TQValueList<SSI>::const_iterator listEnd = groupList.constEnd();
	//the protocol dictates that there is at least one group that has contacts
	//so i don't have to check for an empty group list

	kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Adding " << groupList.count() << " groups to contact list" << endl;
	for( ; git != listEnd; ++git )
	{ //add all the groups.
		kdDebug( OSCAR_GEN_DEBUG ) << k_funcinfo << "Adding SSI group'" << ( *git ).name()
			<< "' to the kopete contact list" << endl;
		kcl->findGroup( ( *git ).name() );
	}

	//then add contacts
	TQValueList<SSI> contactList = listManager->contactList();
	TQValueList<SSI>::const_iterator bit = contactList.constBegin();
	TQValueList<SSI>::const_iterator blistEnd = contactList.constEnd();
	kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Adding " << contactList.count() << " contacts to contact list" << endl;
	for ( ; bit != blistEnd; ++bit )
	{
		SSI groupForAdd = listManager->findGroup( ( *bit ).gid() );
		Kopete::Group* group;
		if ( groupForAdd.isValid() )
			group = kcl->findGroup( groupForAdd.name() ); //add if not present
		else
			group = kcl->findGroup( i18n( "Buddies" ) );

		kdDebug( OSCAR_GEN_DEBUG ) << k_funcinfo << "Adding contact '" << ( *bit ).name() << "' to kopete list in group " <<
			group->displayName() << endl;
		OscarContact* oc = dynamic_cast<OscarContact*>( contacts()[( *bit ).name()] );
		if ( oc )
		{
			Oscar::SSI item = ( *bit );
			oc->setSSIItem( item );
		}
		else
			addContact( ( *bit ).name(), TQString(), group, Kopete::Account::DontChangeKABC );
	}

	TQObject::connect( kcl, TQT_SIGNAL( groupRenamed( Kopete::Group*,  const TQString& ) ),
	                  this, TQT_SLOT( kopeteGroupRenamed( Kopete::Group*, const TQString& ) ) );
	TQObject::connect( kcl, TQT_SIGNAL( groupRemoved( Kopete::Group* ) ),
	                  this, TQT_SLOT( kopeteGroupRemoved( Kopete::Group* ) ) );
	TQObject::connect( listManager, TQT_SIGNAL( contactAdded( const Oscar::SSI& ) ),
	                  this, TQT_SLOT( ssiContactAdded( const Oscar::SSI& ) ) );
	TQObject::connect( listManager, TQT_SIGNAL( groupAdded( const Oscar::SSI& ) ),
	                  this, TQT_SLOT( ssiGroupAdded( const Oscar::SSI& ) ) );
	TQObject::connect( listManager, TQT_SIGNAL( groupUpdated( const Oscar::SSI& ) ),
	                  this, TQT_SLOT( ssiGroupUpdated( const Oscar::SSI& ) ) );
	TQObject::connect( listManager, TQT_SIGNAL( contactUpdated( const Oscar::SSI& ) ),
	                  this, TQT_SLOT( ssiContactUpdated( const Oscar::SSI& ) ) );

    //TODO: check the kopete contact list and handle non server side contacts appropriately.
    TQDict<Kopete::Contact> nonServerContacts = contacts();
    TQDictIterator<Kopete::Contact> it( nonServerContacts );
    TQStringList nonServerContactList;
    for ( ; it.current(); ++it )
    {
        OscarContact* oc = dynamic_cast<OscarContact*>( ( *it ) );
        if ( !oc )
            continue;
        kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << oc->contactId() << " contact ssi type: " << oc->ssiItem().type() << endl;
        if ( !oc->isOnServer() )
            nonServerContactList.append( ( *it )->contactId() );
    }
    kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "the following contacts are not on the server side list"
                             << nonServerContactList << endl;
	bool showMissingContactsDialog = configGroup()->readBoolEntry(TQString::fromLatin1("ShowMissingContactsDialog"), true);
    if ( !nonServerContactList.isEmpty() && showMissingContactsDialog )
    {
        d->olnscDialog = new OscarListNonServerContacts( Kopete::UI::Global::mainWidget() );
        TQObject::connect( d->olnscDialog, TQT_SIGNAL( closing() ),
                          this, TQT_SLOT( nonServerAddContactDialogClosed() ) );
        d->olnscDialog->addContacts( nonServerContactList );
        d->olnscDialog->show();
    }
}

void OscarAccount::nonServerAddContactDialogClosed()
{
    if ( !d->olnscDialog )
        return;

    if ( d->olnscDialog->result() == TQDialog::Accepted )
    {
        //start adding contacts
        kdDebug(OSCAR_GEN_DEBUG) << "adding non server contacts to the contact list" << endl;
        //get the contact list. get the OscarContact object, then the group
        //check if the group is on ssi, if not, add it
        //if so, add the contact.
        TQStringList offliners = d->olnscDialog->nonServerContactList();
        TQStringList::iterator it, itEnd = offliners.end();
        for ( it = offliners.begin(); it != itEnd; ++it )
        {
            OscarContact* oc = dynamic_cast<OscarContact*>( contacts()[( *it )] );
            if ( !oc )
            {
                kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "no OscarContact object available for"
                                         << ( *it ) << endl;
                continue;
            }

            Kopete::MetaContact* mc = oc->metaContact();
            if ( !mc )
            {
                kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "no metacontact object available for"
                                         << ( oc->contactId() ) << endl;
                continue;
            }

            Kopete::Group* group = mc->groups().first();
            if ( !group )
            {
                kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "no metacontact object available for"
                                         << ( oc->contactId() ) << endl;
                continue;
            }

	        addContactToSSI( ( *it ), group->displayName(), true );
        }


    }

	bool showOnce = d->olnscDialog->onlyShowOnce();
	configGroup()->writeEntry( TQString::fromLatin1("ShowMissingContactsDialog") , !showOnce);
	configGroup()->sync();
	
    d->olnscDialog->delayedDestruct();
    d->olnscDialog = 0L;
}

void OscarAccount::slotGoOffline()
{
	OscarAccount::disconnect();
}

void OscarAccount::slotGoOnline()
{
	//do nothing
}

void OscarAccount::kopeteGroupRemoved( Kopete::Group* group )
{
	if ( isConnected() )
		d->engine->removeGroup( group->displayName() );
}

void OscarAccount::kopeteGroupAdded( Kopete::Group* group )
{
	if ( isConnected() )
		d->engine->addGroup( group->displayName() );
}

void OscarAccount::kopeteGroupRenamed( Kopete::Group* group, const TQString& oldName )
{
	if ( isConnected() )
		d->engine->renameGroup( oldName, group->displayName() );
}

void OscarAccount::messageReceived( const Oscar::Message& message )
{
	//the message isn't for us somehow
	if ( Oscar::normalize( message.receiver() ) != Oscar::normalize( accountId() ) )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "got a message but we're not the receiver: "
			<< message.textArray() << endl;
		return;
	}

	/* Logic behind this:
	 * If we don't have the contact yet, create it as a temporary
	 * Create the message manager
	 * Get the sanitized message back
	 * Append to the chat window
	 */
	TQString sender = Oscar::normalize( message.sender() );
	if ( !contacts()[sender] )
	{
		kdDebug(OSCAR_RAW_DEBUG) << "Adding '" << sender << "' as temporary contact" << endl;
		addContact( sender, TQString(), 0,  Kopete::Account::Temporary );
	}

	OscarContact* ocSender = static_cast<OscarContact *> ( contacts()[sender] ); //should exist now

	if ( !ocSender )
	{
		kdWarning(OSCAR_RAW_DEBUG) << "Temporary contact creation failed for '"
			<< sender << "'! Discarding message: " << message.textArray() << endl;
		return;
	}
	else
	{
		if ( ( message.properties() & Oscar::Message::WWP ) == Oscar::Message::WWP )
			ocSender->setNickName( i18n("ICQ Web Express") );
		if ( ( message.properties() & Oscar::Message::EMail ) == Oscar::Message::EMail )
			ocSender->setNickName( i18n("ICQ Email Express") );
	}

	Kopete::ChatSession* chatSession = ocSender->manager( Kopete::Contact::CanCreate );
	chatSession->receivedTypingMsg( ocSender, false ); //person is done typing


	//decode message
	TQString realText( message.text( contactCodec( ocSender ) ) );

	//sanitize;
	TQString sanitizedMsg = sanitizedMessage( realText );

	Kopete::ContactPtrList me;
	me.append( myself() );
	Kopete::Message chatMessage( message.timestamp(), ocSender, me, sanitizedMsg,
	                             Kopete::Message::Inbound, Kopete::Message::RichText );

	chatSession->appendMessage( chatMessage );
}


void OscarAccount::setServerAddress(const TQString &server)
{
	configGroup()->writeEntry( TQString::fromLatin1( "Server" ), server );
}

void OscarAccount::setServerPort(int port)
{
	if ( port > 0 )
		configGroup()->writeEntry( TQString::fromLatin1( "Port" ), port );
	else //set to default 5190
		configGroup()->writeEntry( TQString::fromLatin1( "Port" ), 5190 );
}

TQTextCodec* OscarAccount::defaultCodec() const
{
	return TQTextCodec::codecForMib( configGroup()->readNumEntry( "DefaultEncoding", 4 ) );
}

TQTextCodec* OscarAccount::contactCodec( const OscarContact* contact ) const
{
	if ( contact )
		return contact->contactCodec();
	else
		return defaultCodec();
}

TQTextCodec* OscarAccount::contactCodec( const TQString& contactName ) const
{
	// XXX  Need const_cast because Kopete::Account::contacts()
	// XXX  method is not const for some strange reason.
	OscarContact* contact = static_cast<OscarContact *> ( const_cast<OscarAccount *>(this)->contacts()[contactName] );
	return contactCodec( contact );
}

void OscarAccount::setBuddyIcon( KURL url )
{
	if ( url.path().isEmpty() )
	{
		myself()->removeProperty( Kopete::Global::Properties::self()->photo() );
	}
	else
	{
		TQImage image( url.path() );
		if ( image.isNull() )
			return;
		
		const TQSize size = ( d->engine->isIcq() ) ? TQSize( 52, 64 ) : TQSize( 48, 48 );
		
		image = image.smoothScale( size, TQ_ScaleMax );
		if( image.width() > size.width())
			image = image.copy( ( image.width() - size.width() ) / 2, 0, size.width(), image.height() );
		
		if( image.height() > size.height())
			image = image.copy( 0, ( image.height() - size.height() ) / 2, image.width(), size.height() );
		
		TQString newlocation( locateLocal( "appdata", "oscarpictures/"+ accountId() + ".jpg" ) );
		
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Saving buddy icon: " << newlocation << endl;
		if ( !image.save( newlocation, "JPEG" ) )
			return;
		
		myself()->setProperty( Kopete::Global::Properties::self()->photo() , newlocation );
	}
	
	emit buddyIconChanged();
}

bool OscarAccount::addContactToSSI( const TQString& contactName, const TQString& groupName, bool autoAddGroup )
{
	SSIManager* listManager = d->engine->ssiManager();
	if ( !listManager->findGroup( groupName ) )
	{
		if ( !autoAddGroup )
			return false;

		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "adding non-existant group "
			<< groupName << endl;

		d->contactAddQueue[Oscar::normalize( contactName )] = groupName;
		d->engine->addGroup( groupName );
	}
	else
	{
		d->engine->addContact( contactName, groupName );
	}

	return true;
}

bool OscarAccount::changeContactGroupInSSI( const TQString& contact, const TQString& newGroupName, bool autoAddGroup )
{
	SSIManager* listManager = d->engine->ssiManager();
	if ( !listManager->findGroup( newGroupName ) )
	{
		if ( !autoAddGroup )
			return false;
		
		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "adding non-existant group " 
				<< newGroupName << endl;
			
		d->contactChangeQueue[Oscar::normalize( contact )] = newGroupName;
		d->engine->addGroup( newGroupName );
	}
	else
	{
		d->engine->changeContactGroup( contact, newGroupName );
	}
	
	return true;
}

Connection* OscarAccount::setupConnection( const TQString& server, uint port )
{
	//set up the connector
	KNetworkConnector* knc = new KNetworkConnector( 0 );
	knc->setOptHostPort( server, port );

	//set up the clientstream
	ClientStream* cs = new ClientStream( knc, 0 );

	Connection* c = new Connection( knc, cs, "AUTHORIZER" );
	c->setClient( d->engine );

	return c;
}


bool OscarAccount::createContact(const TQString &contactId,
	Kopete::MetaContact *parentContact)
{
	/* We're not even online or connecting
	 * (when getting server contacts), so don't bother
	 */
	if ( !engine()->isActive() )
	{
		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Can't add contact, we are offline!" << endl;
		return false;
	}

	/* Logic for SSI additions
	If the contact is temporary, no SSI addition at all. Just create the contact and be done with it
	If the contact is not temporary, we need to do the following:
		1. Check if contact already exists in the SSI manager, if so, just create the contact
		2. If contact doesn't exist:
		2.a. create group on SSI if needed
		2.b. create contact on SSI
		2.c. create kopete contact
	 */

	TQValueList<TLV> dummyList;
	if ( parentContact->isTemporary() )
	{
		SSI tempItem( contactId, 0, 0, 0xFFFF, dummyList, 0 );
		return createNewContact( contactId, parentContact, tempItem );
	}

	SSI ssiItem = d->engine->ssiManager()->findContact( contactId );
	if ( ssiItem )
	{
		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Have new SSI entry. Finding contact" << endl;
		if ( contacts()[ssiItem.name()] )
		{
			kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Found contact in list. Updating SSI item" << endl;
			OscarContact* oc = static_cast<OscarContact*>( contacts()[ssiItem.name()] );
			oc->setSSIItem( ssiItem );
			return true;
		}
		else
		{
			kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Didn't find contact in list, creating new contact" << endl;
			return createNewContact( contactId, parentContact, ssiItem );
		}
	}
	else
	{ //new contact, check temporary, if temporary, don't add to SSI. otherwise, add.
		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "New contact '" << contactId << "' not in SSI."
			<< " Creating new contact" << endl;

		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Adding " << contactId << " to server side list" << endl;

		TQString groupName;
		Kopete::GroupList kopeteGroups = parentContact->groups(); //get the group list

		if ( kopeteGroups.isEmpty() || kopeteGroups.first() == Kopete::Group::topLevel() )
		{
			kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Contact with NO group. " << "Adding to group 'Buddies'" << endl;
			groupName = i18n("Buddies");
		}
		else
		{
				//apparently kopeteGroups.first() can be invalid. Attempt to prevent
				//crashes in SSIData::findGroup(const TQString& name)
			groupName = kopeteGroups.first() ? kopeteGroups.first()->displayName() : i18n("Buddies");

			kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Contact with group." << " No. of groups = " << kopeteGroups.count() <<
				" Name of first group = " << groupName << endl;
		}

		if( groupName.isEmpty() )
		{ // emergency exit, should never occur
			kdWarning(OSCAR_GEN_DEBUG) << k_funcinfo << "Could not add contact because no groupname was given" << endl;
			return false;
		}

		d->addContactMap[Oscar::normalize( contactId )] = parentContact;
		addContactToSSI( Oscar::normalize( contactId ), groupName, true );
		return true;
	}
}

void OscarAccount::updateVersionUpdaterStamp()
{
	d->versionUpdaterStamp = OscarVersionUpdater::self()->stamp();
}

void OscarAccount::ssiContactAdded( const Oscar::SSI& item )
{
	if ( d->addContactMap.contains( Oscar::normalize( item.name() ) ) )
	{
		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Received confirmation from server. adding " << item.name()
			<< " to the contact list" << endl;
		createNewContact( item.name(), d->addContactMap[Oscar::normalize( item.name() )], item );
	}
	else if ( contacts()[item.name()] )
	{
		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Received confirmation from server. modifying " << item.name() << endl;
		OscarContact* oc = static_cast<OscarContact*>( contacts()[item.name()] );
		oc->setSSIItem( item );
	}
	else
		kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Got addition for contact we weren't waiting on" << endl;
}

void OscarAccount::ssiGroupAdded( const Oscar::SSI& item )
{
	//check the contact add queue for any contacts matching the
	//group name we just added
	kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "Looking for contacts to be added in group " << item.name() << endl;
	TQMap<TQString,TQString>::iterator it;
	for ( it = d->contactAddQueue.begin(); it != d->contactAddQueue.end(); ++it )
	{
		if ( Oscar::normalize( it.data() ) == Oscar::normalize( item.name() ) )
		{
			kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "starting delayed add of contact '" << it.key()
				<< "' to group " << item.name() << endl;
			
			d->engine->addContact( Oscar::normalize( it.key() ), item.name() );
			d->contactAddQueue.remove( it );
		}
	}
	
	for ( it = d->contactChangeQueue.begin(); it != d->contactChangeQueue.end(); ++it )
	{
		if ( Oscar::normalize( it.data() ) == Oscar::normalize( item.name() ) )
		{
			kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "starting delayed change of contact '" << it.key()
				<< "' to group " << item.name() << endl;
			
			d->engine->changeContactGroup( it.key(),  item.name() );
			d->contactChangeQueue.remove( it );
		}
	}
}

void OscarAccount::ssiContactUpdated( const Oscar::SSI& item )
{
	Kopete::Contact* contact = contacts()[item.name()];
	if ( !contact )
		return;
	else
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Updating SSI Item" << endl;
		OscarContact* oc = static_cast<OscarContact*>( contact );
		oc->setSSIItem( item );
	}
}

void OscarAccount::userStartedTyping( const TQString & contact )
{
	Kopete::Contact * ct = contacts()[ Oscar::normalize( contact ) ];
	if ( ct && contact != accountId() )
	{
		OscarContact * oc = static_cast<OscarContact *>( ct );
		oc->startedTyping();
	}
}

void OscarAccount::userStoppedTyping( const TQString & contact )
{
	Kopete::Contact * ct = contacts()[ Oscar::normalize( contact ) ];
	if ( ct && contact != accountId() )
	{
		OscarContact * oc = static_cast<OscarContact *>( ct );
		oc->stoppedTyping();
	}
}

void OscarAccount::slotSocketError( int errCode, const TQString& errString )
{
	Q_UNUSED( errCode );
	KPassivePopup::message( i18n( "account has been disconnected", "%1 disconnected" ).arg( accountId() ),
	                        errString,
	                        myself()->onlineStatus().protocolIcon(),
	                        Kopete::UI::Global::mainWidget() );
	logOff( Kopete::Account::ConnectionReset );
}

void OscarAccount::slotTaskError( const Oscar::SNAC& s, int code, bool fatal )
{
	kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "error recieived from task" << endl;
	kdDebug(OSCAR_GEN_DEBUG) << k_funcinfo << "service: " << s.family
		<< " subtype: " << s.subtype << " code: " << code << endl;

	TQString message;
	if ( s.family == 0 && s.subtype == 0 )
	{
		message = getFLAPErrorMessage( code );
		KPassivePopup::message( i18n( "account has been disconnected", "%1 disconnected" ).arg( accountId() ),
		                        message, myself()->onlineStatus().protocolIcon(),
		                        Kopete::UI::Global::mainWidget() );
		switch ( code )
		{
		case 0x0000:
			logOff( Kopete::Account::Unknown );
			break;
		case 0x0004:
		case 0x0005:
			logOff( Kopete::Account::BadPassword );
			break;
		case 0x0007:
		case 0x0008:
		case 0x0009:
		case 0x0011:
			logOff( Kopete::Account::BadUserName );
			break;
		default:
			logOff( Kopete::Account::Manual );
		}
		return;
	}
	if ( !fatal )
		message = i18n("There was an error in the protocol handling; it was not fatal, so you will not be disconnected.");
	else
		message = i18n("There was an error in the protocol handling; automatic reconnection occurring.");

	KPassivePopup::message( i18n("OSCAR Protocol error"), message, myself()->onlineStatus().protocolIcon(),
	                        Kopete::UI::Global::mainWidget() );
	if ( fatal )
		logOff( Kopete::Account::ConnectionReset );
}

void OscarAccount::slotSendBuddyIcon()
{
	//need to disconnect because we could end up with many connections
	TQObject::disconnect( engine(), TQT_SIGNAL( iconServerConnected() ), this, TQT_SLOT( slotSendBuddyIcon() ) );
	TQString photoPath = myself()->property( Kopete::Global::Properties::self()->photo() ).value().toString();
	if ( photoPath.isEmpty() )
		return;
	
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << photoPath << endl;
	TQFile iconFile( photoPath );
	
	if ( iconFile.open( IO_ReadOnly ) )
	{
		if ( !engine()->hasIconConnection() )
		{
			//will send icon when we connect to icon server
			TQObject::connect( engine(), TQT_SIGNAL( iconServerConnected() ),
			                  this, TQT_SLOT( slotSendBuddyIcon() ) );
			return;
		}
		TQByteArray imageData = iconFile.readAll();
		engine()->sendBuddyIcon( imageData );
	}
}

TQString OscarAccount::getFLAPErrorMessage( int code )
{
	bool isICQ = d->engine->isIcq();
	TQString acctType = isICQ ? i18n("ICQ") : i18n("AIM");
	TQString acctDescription = isICQ ? i18n("ICQ user id", "UIN") : i18n("AIM user id", "screen name");
	TQString reason;
	//FLAP errors are always fatal
	//negative codes are things added by liboscar developers
	//to indicate generic errors in the task
	switch ( code )
	{
	case 0x0001:
		if ( isConnected() ) // multiple logins (on same UIN)
		{
			reason = i18n( "You have logged in more than once with the same %1," \
			               " account %2 is now disconnected.")
				.arg( acctDescription ).arg( accountId() );
		}
		else // error while logging in
		{
			reason = i18n( "Sign on failed because either your %1 or " \
			               "password are invalid. Please check your settings for account %2.")
				.arg( acctDescription ).arg( accountId() );

		}
		break;
	case 0x0002: // Service temporarily unavailable
	case 0x0014: // Reservation map error
		reason = i18n("The %1 service is temporarily unavailable. Please try again later.")
			.arg( acctType );
		break;
	case 0x0004: // Incorrect nick or password, re-enter
	case 0x0005: // Mismatch nick or password, re-enter
		reason = i18n("Could not sign on to %1 with account %2 because the " \
		              "password was incorrect.").arg( acctType ).arg( accountId() );
		break;
	case 0x0007: // non-existant ICQ#
	case 0x0008: // non-existant ICQ#
		reason = i18n("Could not sign on to %1 with nonexistent account %2.")
			.arg( acctType ).arg( accountId() );
		break;
	case 0x0009: // Expired account
		reason = i18n("Sign on to %1 failed because your account %2 expired.")
			.arg( acctType ).arg( accountId() );
		break;
	case 0x0011: // Suspended account
		reason = i18n("Sign on to %1 failed because your account %2 is " \
		              "currently suspended.").arg( acctType ).arg( accountId() );
		break;
	case 0x0015: // too many clients from same IP
	case 0x0016: // too many clients from same IP
	case 0x0017: // too many clients from same IP (reservation)
		reason = i18n("Could not sign on to %1 as there are too many clients" \
		              " from the same computer.").arg( acctType );
		break;
	case 0x0018: // rate exceeded (turboing)
		if ( isConnected() )
		{
			reason = i18n("Account %1 was blocked on the %2 server for" \
							" sending messages too quickly." \
							" Wait ten minutes and try again." \
							" If you continue to try, you will" \
							" need to wait even longer.")
				.arg( accountId() ).arg( acctType );
		}
		else
		{
			reason = i18n("Account %1 was blocked on the %2 server for" \
							" reconnecting too quickly." \
							" Wait ten minutes and try again." \
							" If you continue to try, you will" \
							" need to wait even longer.")
				.arg( accountId() ).arg( acctType) ;
		}
		break;
	case 0x001C:
		OscarVersionUpdater::self()->update( d->versionUpdaterStamp );
		if ( !d->versionAlreadyUpdated )
		{
			reason = i18n("Sign on to %1 with your account %2 failed.")
				.arg( acctType ).arg( accountId() );
			
			d->versionAlreadyUpdated = true;
		}
		else
		{
			reason = i18n("The %1 server thinks the client you are using is " \
						  "too old. Please report this as a bug at http://bugs.trinitydesktop.org")
				.arg( acctType );
		}
		break;
	case 0x0022: // Account suspended because of your age (age < 13)
		reason = i18n("Account %1 was disabled on the %2 server because " \
		              "of your age (less than 13).")
			.arg( accountId() ).arg( acctType );
		break;
	default:
		if ( !isConnected() )
		{
			reason = i18n("Sign on to %1 with your account %2 failed.")
				.arg( acctType ).arg( accountId() );
		}
		break;
	}
	return reason;
}

#include "oscaraccount.moc"
