/*
    kopeteaccountmanager.cpp - Kopete Account Manager

    Copyright (c) 2002-2003 by Martijn Klingens      <klingens@kde.org>
    Copyright (c) 2003-2004 by Olivier Goffart       <ogoffart @ kde.org>

    Kopete    (c) 2002-2004 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 "kopeteaccountmanager.h"

#include <tqapplication.h>
#include <tqregexp.h>
#include <tqtimer.h>

#include <tdeconfig.h>
#include <kdebug.h>
#include <tdeglobal.h>
#include <kplugininfo.h>

#include "kopeteaccount.h"
#include "kopeteaway.h"
#include "kopeteprotocol.h"
#include "kopetecontact.h"
#include "kopetecontactlist.h"
#include "kopetepluginmanager.h"
#include "kopeteonlinestatus.h"
#include "kopeteonlinestatusmanager.h"
#include "kopetemetacontact.h"
#include "kopetegroup.h"

namespace Kopete {

class AccountManager::Private
{
public:

	class AccountPtrList : public TQPtrList<Account>
	{
		protected:
			int compareItems( AccountPtrList::Item a, AccountPtrList::Item b )
			{
				uint priority1 = static_cast<Account*>(a)->priority();
				uint priority2 = static_cast<Account*>(b)->priority();

				if( a==b ) //two account are equal only if they are equal :-)
					return 0;  // remember than an account can be only once on the list, but two account may have the same priority when loading
				else if( priority1 > priority2 )
					return 1;
				else
					return -1;
			}
	} accounts;

};

AccountManager * AccountManager::s_self = 0L;

AccountManager * AccountManager::self()
{
	if ( !s_self )
		s_self = new AccountManager;

	return s_self;
}


AccountManager::AccountManager()
: TQObject( tqApp, "KopeteAccountManager" )
{
	d = new Private;
}


AccountManager::~AccountManager()
{
	s_self = 0L;

	delete d;
}

bool AccountManager::isAnyAccountConnected()
{
	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
	{
		if(it.current()->isConnected())
			return true;
	}
	return false;
}

void AccountManager::connectAll()
{
	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
		if(!it.current()->excludeConnect())
			it.current()->connect();
}

void AccountManager::setAvailableAll( const TQString &awayReason )
{
	Away::setGlobalAway( false );
	bool anyConnected = isAnyAccountConnected();

	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
	{
		if ( anyConnected )
		{
			if ( it.current()->isConnected() )
				it.current()->setAway( false, awayReason );
		}
		else
			if(!it.current()->excludeConnect())
				it.current()->connect();
	}
}

void AccountManager::disconnectAll()
{
	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
		it.current()->disconnect();
}

void AccountManager::setAwayAll( const TQString &awayReason, bool away )
{
	Away::setGlobalAway( true );
	bool anyConnected = isAnyAccountConnected();

	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
	{
		// FIXME: ICQ's invisible online should be set to invisible away
		Contact *self = it.current()->myself();
		bool isInvisible = self && self->onlineStatus().status() == OnlineStatus::Invisible;
		if ( anyConnected )
		{
			if ( it.current()->isConnected() && !isInvisible )
				it.current()->setAway( away, awayReason );
		}
		else
		{
			if ( !it.current()->excludeConnect() && !isInvisible )
				it.current()->setAway( away, awayReason );
		}
	}
}

void AccountManager::setOnlineStatus( uint category , const TQString& awayMessage, uint flags )
{
	OnlineStatusManager::Categories katgor=(OnlineStatusManager::Categories)category;
	bool anyConnected = isAnyAccountConnected();

	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
	{
		Account *account = it.current();
		Kopete::OnlineStatus status = OnlineStatusManager::self()->onlineStatus(account->protocol() , katgor);
		if ( anyConnected )
		{
			if ( account->isConnected() || ( (flags & ConnectIfOffline) && !account->excludeConnect() ) )
				account->setOnlineStatus( status , awayMessage );
		}
		else
		{
			if ( !account->excludeConnect() )
				account->setOnlineStatus( status , awayMessage );
		}
	}
}


TQColor AccountManager::guessColor( Protocol *protocol ) const
{
	// In a perfect wold, we should check if the color is actually not used by the account.
	// Anyway, this is not really required,  It would be a difficult job for about nothing more.
	//   -- Olivier
	int protocolCount = 0;

	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
	{
		if ( it.current()->protocol()->pluginId() == protocol->pluginId() )
			protocolCount++;
	}

	// let's figure a color
	TQColor color;
	switch ( protocolCount % 7 )
	{
	case 0:
		color = TQColor();
		break;
	case 1:
		color = TQt::red;
		break;
	case 2:
		color = TQt::green;
		break;
	case 3:
		color = TQt::blue;
		break;
	case 4:
		color = TQt::yellow;
		break;
	case 5:
		color = TQt::magenta;
		break;
	case 6:
		color = TQt::cyan;
		break;
	}

	return color;
}

Account* AccountManager::registerAccount( Account *account )
{
	if( !account || d->accounts.contains( account ) )
		return account;

	if( account->accountId().isEmpty() )
	{
		account->deleteLater();
		return 0L;
	}

	// If this account already exists, do nothing
	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
	{
		if ( ( account->protocol() == it.current()->protocol() ) && ( account->accountId() == it.current()->accountId() ) )
		{
			account->deleteLater();
			return 0L;
		}
	}

	d->accounts.append( account );
	d->accounts.sort();

	// Connect to the account's status changed signal
	connect(account->myself(), TQT_SIGNAL(onlineStatusChanged(Kopete::Contact *,
			const Kopete::OnlineStatus &, const Kopete::OnlineStatus &)),
		this, TQT_SLOT(slotAccountOnlineStatusChanged(Kopete::Contact *,
			const Kopete::OnlineStatus &, const Kopete::OnlineStatus &)));

	connect(account, TQT_SIGNAL(accountDestroyed(const Kopete::Account *)) , this, TQT_SLOT( unregisterAccount(const Kopete::Account *) ));

	emit accountRegistered( account );
	return account;
}

void AccountManager::unregisterAccount( const Account *account )
{
	kdDebug( 14010 ) << k_funcinfo << "Unregistering account " << account->accountId() << endl;
	d->accounts.remove( account );
	emit accountUnregistered( account );
}

const TQPtrList<Account>& AccountManager::accounts() const
{
	return d->accounts;
}

TQDict<Account> AccountManager::accounts( const Protocol *protocol ) const
{
	TQDict<Account> dict;
	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
	{
		if ( it.current()->protocol() == protocol && !it.current()->accountId().isNull() )
			dict.insert( it.current()->accountId(), it.current() );
	}

	return dict;
}

Account * AccountManager::findAccount( const TQString &protocolId, const TQString &accountId )
{
	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
	{
		if ( it.current()->protocol()->pluginId() == protocolId && it.current()->accountId() == accountId )
			return it.current();
	}
	return 0L;
}

void AccountManager::removeAccount( Account *account )
{
	if(!account->removeAccount())
		return;

	Protocol *protocol = account->protocol();


	TDEConfigGroup *configgroup = account->configGroup();

	// Clean up the contact list
	TQDictIterator<Kopete::Contact> it( account->contacts() );
	for ( ; it.current(); ++it )
	{
		Contact* c = it.current();
		MetaContact* mc = c->metaContact();
		if ( mc == ContactList::self()->myself() )
			continue;
		mc->removeContact( c );
		c->deleteLater();
		if ( mc->contacts().count() == 0 ) //we can delete the metacontact
		{
			//get the first group and it's members
			Group* group = mc->groups().first();
			TQPtrList<MetaContact> groupMembers = group->members();
			ContactList::self()->removeMetaContact( mc );
			if ( groupMembers.count() == 1 && groupMembers.findRef( mc ) != -1 )
				ContactList::self()->removeGroup( group );
		}
	}

	// Clean up the account list
	d->accounts.remove( account );

	// Clean up configuration
	configgroup->deleteGroup();
	configgroup->sync();

	delete account;

	if ( accounts( protocol ).isEmpty() )
	{
		// FIXME: pluginId() should return the internal name and not the class name, so
		//        we can get rid of this hack - Olivier/Martijn
		TQString protocolName = protocol->pluginId().remove( TQString::fromLatin1( "Protocol" ) ).lower();

		PluginManager::self()->setPluginEnabled( protocolName, false );
		PluginManager::self()->unloadPlugin( protocolName );
	}
}

void AccountManager::save()
{
	//kdDebug( 14010 ) << k_funcinfo << endl;
	d->accounts.sort();

	for ( TQPtrListIterator<Account> it( d->accounts ); it.current(); ++it )
	{
		TDEConfigBase *config = it.current()->configGroup();

		config->writeEntry( "Protocol", it.current()->protocol()->pluginId() );
		config->writeEntry( "AccountId", it.current()->accountId() );
	}

	TDEGlobal::config()->sync();
}

void AccountManager::load()
{
	connect( PluginManager::self(), TQT_SIGNAL( pluginLoaded( Kopete::Plugin * ) ),
		this, TQT_SLOT( slotPluginLoaded( Kopete::Plugin * ) ) );

	// Iterate over all groups that start with "Account_" as those are accounts
	// and load the required protocols if the account is enabled.
	// Don't try to optimize duplicate calls out, the plugin queue is smart enough
	// (and fast enough) to handle that without adding complexity here
	TDEConfig *config = TDEGlobal::config();
	TQStringList accountGroups = config->groupList().grep( TQRegExp( TQString::fromLatin1( "^Account_" ) ) );
	for ( TQStringList::Iterator it = accountGroups.begin(); it != accountGroups.end(); ++it )
	{
		config->setGroup( *it );

		TQString protocol = config->readEntry( "Protocol" );
		if ( protocol.endsWith( TQString::fromLatin1( "Protocol" ) ) )
			protocol = TQString::fromLatin1( "kopete_" ) + protocol.lower().remove( TQString::fromLatin1( "protocol" ) );

		if ( config->readBoolEntry( "Enabled", true ) )
			PluginManager::self()->loadPlugin( protocol, PluginManager::LoadAsync );
	}
}

void AccountManager::slotPluginLoaded( Plugin *plugin )
{
	Protocol* protocol = dynamic_cast<Protocol*>( plugin );
	if ( !protocol )
		return;

	// Iterate over all groups that start with "Account_" as those are accounts
	// and parse them if they are from this protocol
	TDEConfig *config = TDEGlobal::config();
	TQStringList accountGroups = config->groupList().grep( TQRegExp( TQString::fromLatin1( "^Account_" ) ) );
	for ( TQStringList::Iterator it = accountGroups.begin(); it != accountGroups.end(); ++it )
	{
		config->setGroup( *it );

		if ( config->readEntry( "Protocol" ) != protocol->pluginId() )
			continue;

		// There's no GUI for this, but developers may want to disable an account.
		if ( !config->readBoolEntry( "Enabled", true ) )
			continue;

		TQString accountId = config->readEntry( "AccountId" );
		if ( accountId.isEmpty() )
		{
			kdWarning( 14010 ) << k_funcinfo <<
				"Not creating account for empty accountId." << endl;
			continue;
		}

		kdDebug( 14010 ) << k_funcinfo <<
			"Creating account for '" << accountId << "'" << endl;

		Account *account = 0L;
		account = registerAccount( protocol->createNewAccount( accountId ) );
		if ( !account )
		{
			kdWarning( 14010 ) << k_funcinfo <<
				"Failed to create account for '" << accountId << "'" << endl;
			continue;
		}
	}
}

void AccountManager::slotAccountOnlineStatusChanged(Contact *c,
	const OnlineStatus &oldStatus, const OnlineStatus &newStatus)
{
	Account *account = c->account();
	if (!account)
		return;

	//kdDebug(14010) << k_funcinfo << endl;
	emit accountOnlineStatusChanged(account, oldStatus, newStatus);
}

} //END namespace Kopete

#include "kopeteaccountmanager.moc"
// vim: set noet ts=4 sts=4 sw=4:
// kate: tab-width 4; indent-mode csands;