/*
  icqaccount.cpp  -  ICQ Account Class

  Copyright (c) 2002 by Chris TenHarmsel            <tenharmsel@staticmethod.net>
  Copyright (c) 2004 by Richard Smith               <kde@metafoo.co.uk>
  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 <tqfile.h>

#include <tdeconfig.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tdepopupmenu.h>
#include <kmdcodec.h>
#include <tdemessagebox.h>

#include "kopeteawayaction.h"
#include "kopetemessage.h"
#include "kopetecontactlist.h"
#include "kopeteuiglobal.h"

#include "client.h"
#include "icquserinfo.h"
#include "oscarsettings.h"
#include "oscarutils.h"
#include "ssimanager.h"

#include "icqcontact.h"
#include "icqprotocol.h"
#include "icqaccount.h"

#include "oscarvisibilitydialog.h"

ICQMyselfContact::ICQMyselfContact( ICQAccount *acct ) : OscarMyselfContact( acct )
{
	TQObject::connect( acct->engine(), TQ_SIGNAL( loggedIn() ), this, TQ_SLOT( fetchShortInfo() ) );
	TQObject::connect( acct->engine(), TQ_SIGNAL( receivedIcqShortInfo( const TQString& ) ),
	                  this, TQ_SLOT( receivedShortInfo( const TQString& ) ) );
}

void ICQMyselfContact::userInfoUpdated()
{
	DWORD extendedStatus = details().extendedStatus();
	kdDebug( OSCAR_ICQ_DEBUG ) << k_funcinfo << "extendedStatus is " << TQString::number( extendedStatus, 16 ) << endl;
	ICQ::Presence presence = ICQ::Presence::fromOscarStatus( extendedStatus & 0xffff );
	setOnlineStatus( presence.toOnlineStatus() );
	setProperty( Kopete::Global::Properties::self()->awayMessage(), static_cast<ICQAccount*>( account() )->engine()->statusMessage() );
}

void ICQMyselfContact::receivedShortInfo( const TQString& contact )
{
	if ( Oscar::normalize( contact ) != Oscar::normalize( contactId() ) )
		return;

	ICQShortInfo shortInfo = static_cast<ICQAccount*>( account() )->engine()->getShortInfo( contact );
	if ( !shortInfo.nickname.isEmpty() )
	{
		setProperty( Kopete::Global::Properties::self()->nickName(), static_cast<ICQAccount*>( account() )->defaultCodec()->toUnicode( shortInfo.nickname ) );
	}
}

void ICQMyselfContact::fetchShortInfo()
{
	static_cast<ICQAccount*>( account() )->engine()->requestShortInfo( contactId() );
}

ICQAccount::ICQAccount(Kopete::Protocol *parent, TQString accountID, const char *name)
	: OscarAccount(parent, accountID, name, true)
{
	kdDebug(14152) << k_funcinfo << accountID << ": Called."<< endl;
	setMyself( new ICQMyselfContact( this ) );
	myself()->setOnlineStatus( ICQ::Presence( ICQ::Presence::Offline, ICQ::Presence::Visible ).toOnlineStatus() );

	m_visibilityDialog = 0;

	TQString nickName = configGroup()->readEntry("NickName", TQString());
	mWebAware = configGroup()->readBoolEntry( "WebAware", false );
	mHideIP = configGroup()->readBoolEntry( "HideIP", true );
	mInitialStatusMessage = TQString();

	TQObject::connect( Kopete::ContactList::self(), TQ_SIGNAL( globalIdentityChanged( const TQString&, const TQVariant& ) ),
	                  this, TQ_SLOT( slotGlobalIdentityChanged( const TQString&, const TQVariant& ) ) );

	TQObject::connect( this, TQ_SIGNAL( buddyIconChanged() ), this, TQ_SLOT( slotBuddyIconChanged() ) );

	//setIgnoreUnknownContacts(pluginData(protocol(), "IgnoreUnknownContacts").toUInt() == 1);

	/* FIXME: need to do this when web aware or hide ip change
	if(isConnected() && (oldhideip != mHideIP || oldwebaware != mWebAware))
	{
		kdDebug(14153) << k_funcinfo <<
			"sending status to reflect HideIP and WebAware settings" << endl;
		//setStatus(mStatus, TQString());
	}*/
}

ICQAccount::~ICQAccount()
{
}

ICQProtocol* ICQAccount::protocol()
{
	return static_cast<ICQProtocol*>(OscarAccount::protocol());
}


ICQ::Presence ICQAccount::presence()
{
	return ICQ::Presence::fromOnlineStatus( myself()->onlineStatus() );
}


TDEActionMenu* ICQAccount::actionMenu()
{
	TDEActionMenu* actionMenu = Kopete::Account::actionMenu();

	actionMenu->popupMenu()->insertSeparator();

	TDEToggleAction* actionInvisible =
	    new TDEToggleAction( i18n( "In&visible" ),
	                       ICQ::Presence( presence().type(), ICQ::Presence::Invisible ).toOnlineStatus().iconFor( this ),
	                       0, this, TQ_SLOT( slotToggleInvisible() ), this );
	actionInvisible->setChecked( presence().visibility() == ICQ::Presence::Invisible );
	actionMenu->insert( actionInvisible );

	actionMenu->popupMenu()->insertSeparator();
	actionMenu->insert( new TDEToggleAction( i18n( "Set Visibility..." ), 0, 0,
	                                       this, TQ_SLOT( slotSetVisiblility() ), this,
	                                       "ICQAccount::mActionSetVisibility") );
	//actionMenu->insert( new TDEToggleAction( i18n( "Send &SMS..." ), 0, 0, this, TQ_SLOT( slotSendSMS() ), this, "ICQAccount::mActionSendSMS") );

	return actionMenu;
}


void ICQAccount::connectWithPassword( const TQString &password )
{
	if ( password.isNull() )
		return;

	kdDebug(14153) << k_funcinfo << "accountId='" << accountId() << "'" << endl;

	Kopete::OnlineStatus status = initialStatus();
	if ( status == Kopete::OnlineStatus() &&
	     status.status() == Kopete::OnlineStatus::Unknown )
		//use default online in case of invalid online status for connecting
		status = Kopete::OnlineStatus( Kopete::OnlineStatus::Online );
	ICQ::Presence pres = ICQ::Presence::fromOnlineStatus( status );
	bool accountIsOffline = ( presence().type() == ICQ::Presence::Offline ||
	                          myself()->onlineStatus() == protocol()->statusManager()->connectingStatus() );

	if ( accountIsOffline )
	{
		myself()->setOnlineStatus( protocol()->statusManager()->connectingStatus() );
		TQString icqNumber = accountId();
		kdDebug(14153) << k_funcinfo << "Logging in as " << icqNumber << endl ;
		TQString server = configGroup()->readEntry( "Server", TQString::fromLatin1( "login.oscar.aol.com" ) );
		uint port = configGroup()->readNumEntry( "Port", 5190 );
		Connection* c = setupConnection( server, port );

		//set up the settings for the account
		Oscar::Settings* oscarSettings = engine()->clientSettings();
		oscarSettings->setWebAware( configGroup()->readBoolEntry( "WebAware", false ) );
		oscarSettings->setHideIP( configGroup()->readBoolEntry( "HideIP", true ) );
		//FIXME: also needed for the other call to setStatus (in setPresenceTarget)
		DWORD status = pres.toOscarStatus();

		if ( !mHideIP )
			status |= ICQ::StatusCode::SHOWIP;
		if ( mWebAware )
			status |= ICQ::StatusCode::WEBAWARE;

		engine()->setStatus( status, mInitialStatusMessage );
		updateVersionUpdaterStamp();
		engine()->start( server, port, accountId(), password );
		engine()->connectToServer( c, server, true /* doAuth */ );

		mInitialStatusMessage = TQString();
	}
}

void ICQAccount::disconnected( DisconnectReason reason )
{
	kdDebug(14153) << k_funcinfo << "Attempting to set status offline" << endl;
	ICQ::Presence presOffline = ICQ::Presence( ICQ::Presence::Offline, presence().visibility() );
	myself()->setOnlineStatus( presOffline.toOnlineStatus() );

	TQDictIterator<Kopete::Contact> it( contacts() );
	for( ; it.current(); ++it )
	{
		OscarContact* oc = dynamic_cast<OscarContact*>( it.current() );
		if ( oc )
		{
			if ( oc->ssiItem().waitingAuth() )
				oc->setOnlineStatus( protocol()->statusManager()->waitingForAuth() );
			else
				oc->setOnlineStatus( ICQ::Presence( ICQ::Presence::Offline, ICQ::Presence::Visible ).toOnlineStatus() );
		}
	}

	OscarAccount::disconnected( reason );
}


void ICQAccount::slotToggleInvisible()
{
	using namespace ICQ;
	setInvisible( (presence().visibility() == Presence::Visible) ? Presence::Invisible : Presence::Visible );
}

void ICQAccount::slotSetVisiblility()
{
	if( !isConnected() )
	{
		KMessageBox::sorry( Kopete::UI::Global::mainWidget(),
		                    i18n("You must be online to set users visibility."),
		                    i18n("ICQ Plugin") );
		return;
	}
	
	if ( !m_visibilityDialog )
	{
		m_visibilityDialog = new OscarVisibilityDialog( engine(), Kopete::UI::Global::mainWidget() );
		TQObject::connect( m_visibilityDialog, TQ_SIGNAL( closing() ),
		                  this, TQ_SLOT( slotVisibilityDialogClosed() ) );
		
		//add all contacts;
		OscarVisibilityDialog::ContactMap contactMap;
		//temporary map for faster lookup of contactId
		TQMap<TQString, TQString> revContactMap;
		
		TQValueList<Oscar::SSI> contactList = engine()->ssiManager()->contactList();
		TQValueList<Oscar::SSI>::const_iterator it, cEnd = contactList.constEnd();
		
		for ( it = contactList.constBegin(); it != cEnd; ++it )
		{
			TQString contactId = ( *it ).name();
			
			OscarContact* oc = dynamic_cast<OscarContact*>( contacts()[( *it ).name()] );
			if ( oc )
			{	//for better orientation in lists use nickName and icq number
				TQString screenName( "%1 (%2)" );
				screenName = screenName.arg( oc->nickName(), contactId);
				contactMap.insert( screenName, contactId );
				revContactMap.insert( contactId, screenName );
			}
			else
			{
				contactMap.insert( contactId, contactId );
				revContactMap.insert( contactId, contactId );
			}
		}
		m_visibilityDialog->addContacts( contactMap );
		
		//add contacts from visible list
		TQStringList tmpList;
		
		contactList = engine()->ssiManager()->visibleList();
		cEnd = contactList.constEnd();
		
		for ( it = contactList.constBegin(); it != cEnd; ++it )
			tmpList.append( revContactMap[( *it ).name()] );
		
		m_visibilityDialog->addVisibleContacts( tmpList );
		
		//add contacts from invisible list
		tmpList.clear();
		
		contactList = engine()->ssiManager()->invisibleList();
		cEnd = contactList.constEnd();
		
		for ( it = contactList.constBegin(); it != cEnd; ++it )
			tmpList.append( revContactMap[( *it ).name()] );
		
		m_visibilityDialog->addInvisibleContacts( tmpList );
		
		m_visibilityDialog->resize( 550, 350 );
		m_visibilityDialog->show();
	}
	else
	{
		m_visibilityDialog->raise();
	}
}

void ICQAccount::slotVisibilityDialogClosed()
{
	m_visibilityDialog->delayedDestruct();
	m_visibilityDialog = 0L;
}

void ICQAccount::setAway( bool away, const TQString &awayReason )
{
	kdDebug(14153) << k_funcinfo << "account='" << accountId() << "'" << endl;
	if ( away )
		setPresenceType( ICQ::Presence::Away, awayReason );
	else
		setPresenceType( ICQ::Presence::Online );
}


void ICQAccount::setInvisible( ICQ::Presence::Visibility vis )
{
	ICQ::Presence pres = presence();
	if ( vis == pres.visibility() )
		return;

	kdDebug(14153) << k_funcinfo << "changing invisible setting to " << (int)vis << endl;
	setPresenceTarget( ICQ::Presence( pres.type(), vis ) );
}

void ICQAccount::setPresenceType( ICQ::Presence::Type type, const TQString &message )
{
	ICQ::Presence pres = presence();
	kdDebug(14153) << k_funcinfo << "new type=" << (int)type << ", old type=" << (int)pres.type() << ", new message=" << message << endl;
	//setAwayMessage(awayMessage);
	setPresenceTarget( ICQ::Presence( type, pres.visibility() ), message );
}

void ICQAccount::setPresenceTarget( const ICQ::Presence &newPres, const TQString &message )
{
	bool targetIsOffline = (newPres.type() == ICQ::Presence::Offline);
	bool accountIsOffline = ( presence().type() == ICQ::Presence::Offline ||
	                          myself()->onlineStatus() == protocol()->statusManager()->connectingStatus() );

	if ( targetIsOffline )
	{
		OscarAccount::disconnect();
		// allow toggling invisibility when offline
		myself()->setOnlineStatus( newPres.toOnlineStatus() );
	}
	else if ( accountIsOffline )
	{
		mInitialStatusMessage = message;
		OscarAccount::connect( newPres.toOnlineStatus() );
	}
	else
	{
		engine()->setStatus( newPres.toOscarStatus(), message );
	}
}


void ICQAccount::setOnlineStatus( const Kopete::OnlineStatus& status, const TQString& reason )
{
	if ( status.status() == Kopete::OnlineStatus::Invisible )
	{
		// called from outside, i.e. not by our custom action menu entry...

		if ( presence().type() == ICQ::Presence::Offline )
		{
			// ...when we are offline go online invisible.
			setPresenceTarget( ICQ::Presence( ICQ::Presence::Online, ICQ::Presence::Invisible ) );
		}
		else
		{
			// ...when we are not offline set invisible.
			setInvisible( ICQ::Presence::Invisible );
		}
	}
	else
	{
		setPresenceType( ICQ::Presence::fromOnlineStatus( status ).type(), reason );
	}
}


OscarContact *ICQAccount::createNewContact( const TQString &contactId, Kopete::MetaContact *parentContact, const SSI& ssiItem )
{
	ICQContact* contact = new ICQContact( this, contactId, parentContact, TQString(), ssiItem );
	if ( !ssiItem.alias().isEmpty() )
		contact->setProperty( Kopete::Global::Properties::self()->nickName(), ssiItem.alias() );

	if ( isConnected() )
		contact->loggedIn();

	return contact;
}

TQString ICQAccount::sanitizedMessage( const TQString& message )
{
	return Kopete::Message::escape( message );
}


void ICQAccount::slotGlobalIdentityChanged( const TQString& key, const TQVariant& value )
{
	//do something with the photo
	kdDebug(14153) << k_funcinfo << "Global identity changed" << endl;
	kdDebug(14153) << k_funcinfo << "key: " << key << endl;
	kdDebug(14153) << k_funcinfo << "value: " << value << endl;
	
	if( !configGroup()->readBoolEntry("ExcludeGlobalIdentity", false) )
	{
		if ( key == Kopete::Global::Properties::self()->nickName().key() )
		{
			//edit ssi item to change alias (if possible)
		}
		
		if ( key == Kopete::Global::Properties::self()->photo().key() )
		{
			setBuddyIcon( value.toString() );
		}
	}
}

void ICQAccount::slotBuddyIconChanged()
{
	// need to disconnect because we could end up with many connections
	TQObject::disconnect( engine(), TQ_SIGNAL( iconServerConnected() ), this, TQ_SLOT( slotBuddyIconChanged() ) );
	if ( !engine()->isActive() )
	{
		TQObject::connect( engine(), TQ_SIGNAL( iconServerConnected() ), this, TQ_SLOT( slotBuddyIconChanged() ) );
		return;
	}
	
	TQString photoPath = myself()->property( Kopete::Global::Properties::self()->photo() ).value().toString();
	
	SSIManager* ssi = engine()->ssiManager();
	Oscar::SSI item = ssi->findItemForIconByRef( 1 );
	
	if ( photoPath.isEmpty() )
	{
		if ( item )
		{
			kdDebug(14153) << k_funcinfo << "Removing icon hash item from ssi" << endl;
			Oscar::SSI s(item);
			
			//remove hash and alias
			TQValueList<TLV> tList( item.tlvList() );
			TLV t = Oscar::findTLV( tList, 0x00D5 );
			if ( t )
				tList.remove( t );
			
			t = Oscar::findTLV( tList, 0x0131 );
			if ( t )
				tList.remove( t );
			
			item.setTLVList( tList );
			//s is old, item is new. modification will occur
			engine()->modifySSIItem( s, item );
		}
	}
	else
	{
		TQFile iconFile( photoPath );
		iconFile.open( IO_ReadOnly );
		
		KMD5 iconHash;
		iconHash.update( iconFile );
		kdDebug(14153) << k_funcinfo  << "hash is :" << iconHash.hexDigest() << endl;
	
		//find old item, create updated item
		if ( !item )
		{
			kdDebug(14153) << k_funcinfo << "no existing icon hash item in ssi. creating new" << endl;
			
			TLV t;
			t.type = 0x00D5;
			t.data.resize( 18 );
			t.data[0] = 0x01;
			t.data[1] = 0x10;
			memcpy(t.data.data() + 2, iconHash.rawDigest(), 16);
			t.length = t.data.size();
			
			//alias, it's always empty
			TLV t2;
			t2.type = 0x0131;
			t2.length = 0;
			
			TQValueList<Oscar::TLV> list;
			list.append( t );
			list.append( t2 );
			
			Oscar::SSI s( "1", 0, ssi->nextContactId(), ROSTER_BUDDYICONS, list );
			
			//item is a non-valid ssi item, so the function will add an item
			kdDebug(14153) << k_funcinfo << "setting new icon item" << endl;
			engine()->modifySSIItem( item, s );
		}
		else
		{ //found an item
			Oscar::SSI s(item);
			kdDebug(14153) << k_funcinfo << "modifying old item in ssi." << endl;
			TQValueList<TLV> tList( item.tlvList() );
			
			TLV t = Oscar::findTLV( tList, 0x00D5 );
			if ( t )
				tList.remove( t );
			else
				t.type = 0x00D5;
			
			t.data.resize( 18 );
			t.data[0] = 0x01;
			t.data[1] = 0x10;
			memcpy(t.data.data() + 2, iconHash.rawDigest(), 16);
			t.length = t.data.size();
			tList.append( t );
			
			//add empty alias
			t = Oscar::findTLV( tList, 0x0131 );
			if ( !t )
			{
				t.type = 0x0131;
				t.length = 0;
				tList.append( t );
			}
			
			item.setTLVList( tList );
			//s is old, item is new. modification will occur
			engine()->modifySSIItem( s, item );
		}
		iconFile.close();
	}
}

#include "icqaccount.moc"
