/*
    kopetecontactlist.cpp - Kopete's Contact List backend

    Copyright (c) 2005      by Michael Larouche       <michael.larouche@kdemail.net>
    Copyright (c) 2002-2003 by Martijn Klingens       <klingens@kde.org>
    Copyright (c) 2002-2004 by Olivier Goffart        <ogoffart @ kde.org>
    Copyright (c) 2002      by Duncan Mac-Vicar Prett <duncan@kde.org>

    Copyright (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 "kopetecontactlist.h"

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

#include <tdeapplication.h>
#include <tdeabc/stdaddressbook.h>
#include <kdebug.h>
#include <ksavefile.h>
#include <kstandarddirs.h>
#include <kopeteconfig.h>
#include <tdeglobal.h>
#include "kopetemetacontact.h"
#include "kopetecontact.h"
#include "kopetechatsession.h"
//#include "kopetemessage.h"
#include "kopetepluginmanager.h"
#include "kopeteprotocol.h"
#include "kopeteaccount.h"
#include "kopeteaccountmanager.h"
#include "kopetegroup.h"
#include "kopetepicture.h"


namespace  Kopete
{

class ContactList::Private
{public:
	/** Flag:  do not save the contactlist until she is completely loaded */
	bool loaded ;

	TQPtrList<MetaContact> contacts;
	TQPtrList<Group> groups;
	TQPtrList<MetaContact> selectedMetaContacts;
	TQPtrList<Group> selectedGroups;

	TQTimer *saveTimer;

	MetaContact *myself;

	/** Flag: does the user uses the global identity */
	bool useGlobalIdentity;

	/**
	 * Current contact list version * 10 ( i.e. '10' is version '1.0' )
	 */
	static const uint ContactListVersion = 10;
};

ContactList *ContactList::s_self = 0L;

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

	return s_self;
}

ContactList::ContactList()
	: TQObject( kapp, "KopeteContactList" )
{
	d=new Private;

	//the myself metacontact can't be created now, because it will use
	//ContactList::self() as parent which will call this constructor -> infinite loop
	d->myself=0L;

	//no contactlist loaded yet, don't save them
	d->loaded=false;

	// automatically save on changes to the list
	d->saveTimer = new TQTimer( this, "saveTimer" );
	connect( d->saveTimer, TQ_SIGNAL( timeout() ), TQ_SLOT ( save() ) );

	connect( this, TQ_SIGNAL( metaContactAdded( Kopete::MetaContact * ) ), TQ_SLOT( slotSaveLater() ) );
	connect( this, TQ_SIGNAL( metaContactRemoved( Kopete::MetaContact * ) ), TQ_SLOT( slotSaveLater() ) );
	connect( this, TQ_SIGNAL( groupAdded( Kopete::Group * ) ), TQ_SLOT( slotSaveLater() ) );
	connect( this, TQ_SIGNAL( groupRemoved( Kopete::Group * ) ), TQ_SLOT( slotSaveLater() ) );
	connect( this, TQ_SIGNAL( groupRenamed( Kopete::Group *, const TQString & ) ), TQ_SLOT( slotSaveLater() ) );
}

ContactList::~ContactList()
{
	delete d->myself;
	delete d;
}

TQPtrList<MetaContact> ContactList::metaContacts() const
{
	return d->contacts;
}


TQPtrList<Group> ContactList::groups() const
{
	return d->groups;
}


MetaContact *ContactList::metaContact( const TQString &metaContactId ) const
{
	TQPtrListIterator<MetaContact> it( d->contacts );

	for( ; it.current(); ++it )
	{
		if( it.current()->metaContactId() == metaContactId )
			return it.current();
	}

	return 0L;
}


Group * ContactList::group(unsigned int groupId) const
{
	Group *groupIterator;
	for ( groupIterator = d->groups.first(); groupIterator; groupIterator = d->groups.next() ) 
	{
		if( groupIterator->groupId()==groupId )
			return groupIterator;
	}
	return 0L;
}


Contact *ContactList::findContact( const TQString &protocolId,
	const TQString &accountId, const TQString &contactId ) const 
{
	//Browsing metacontacts is too slow, better to uses the Dict of the account.
	Account *i=AccountManager::self()->findAccount(protocolId,accountId);
	if(!i)
	{
		kdDebug( 14010 ) << k_funcinfo << "Account not found" << endl;
		return 0L;
	}
	return i->contacts()[contactId];
}


MetaContact *ContactList::findMetaContactByDisplayName( const TQString &displayName ) const
{
	TQPtrListIterator<MetaContact> it( d->contacts );
	for( ; it.current(); ++it )
	{
//		kdDebug(14010) << "Display Name: " << it.current()->displayName() << "\n";
		if( it.current()->displayName() == displayName ) {
			return it.current();
		}
	}

	return 0L;
}

MetaContact* ContactList::findMetaContactByContactId( const TQString &contactId ) const
{
	TQPtrList<Account> acts=AccountManager::self()->accounts();
	TQPtrListIterator<Account> it( acts );
	for ( ; it.current(); ++it )
	{
		Contact *c=(*it)->contacts()[contactId];
		if(c && c->metaContact())
			return c->metaContact();
	}
	return 0L;
}

Group * ContactList::findGroup(const TQString& displayName, int type)
{
	if( type == Group::Temporary )
		return Group::temporary();

	Group *groupIterator;
	for ( groupIterator = d->groups.first(); groupIterator; groupIterator = d->groups.next() )
	{
		if( groupIterator->type() == type && groupIterator->displayName() == displayName )
			return groupIterator;
	}

	Group *newGroup = new Group( displayName, (Group::GroupType)type );
	addGroup( newGroup );
	return  newGroup;
}


TQPtrList<MetaContact> ContactList::selectedMetaContacts() const
{
	return d->selectedMetaContacts;
}

TQPtrList<Group> ContactList::selectedGroups() const
{
	return d->selectedGroups;
}


void ContactList::addMetaContact( MetaContact *mc )
{
	if ( d->contacts.contains( mc ) )
		return;

	d->contacts.append( mc );

	emit metaContactAdded( mc );
	connect( mc, TQ_SIGNAL( persistentDataChanged( ) ), TQ_SLOT( slotSaveLater() ) );
	connect( mc, TQ_SIGNAL( addedToGroup( Kopete::MetaContact *, Kopete::Group * ) ), TQ_SIGNAL( metaContactAddedToGroup( Kopete::MetaContact *, Kopete::Group * ) ) );
	connect( mc, TQ_SIGNAL( removedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ), TQ_SIGNAL( metaContactRemovedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ) );
}


void ContactList::removeMetaContact(MetaContact *m)
{
	if ( !d->contacts.contains(m) )
	{
		kdDebug(14010) << k_funcinfo << "Trying to remove a not listed MetaContact." << endl;
		return;
	}

	if ( d->selectedMetaContacts.contains( m ) )
	{
		d->selectedMetaContacts.remove( m );
		setSelectedItems( d->selectedMetaContacts, d->selectedGroups );
	}

	//removes subcontact from server here and now.
	TQPtrList<Contact> cts=m->contacts();
	for( Contact *c = cts.first(); c; c = cts.next() )
	{
		c->deleteContact();
	}

	d->contacts.remove( m );
	emit metaContactRemoved( m );
	m->deleteLater();
}


void ContactList::addGroup( Group * g )
{
	if(!d->groups.contains(g) )
	{
		d->groups.append( g );
		emit groupAdded( g );
		connect( g , TQ_SIGNAL ( displayNameChanged(Kopete::Group* , const TQString & )) , this , TQ_SIGNAL ( groupRenamed(Kopete::Group* , const TQString & )) ) ;
	}
}

void ContactList::removeGroup( Group *g )
{
	if ( d->selectedGroups.contains( g ) )
	{
		d->selectedGroups.remove( g );
		setSelectedItems( d->selectedMetaContacts, d->selectedGroups );
	}

	d->groups.remove( g );
	emit groupRemoved( g );
	g->deleteLater();
}


void ContactList::setSelectedItems(TQPtrList<MetaContact> metaContacts , TQPtrList<Group> groups)
{
	kdDebug( 14010 ) << k_funcinfo << metaContacts.count() << " metacontacts, " << groups.count() << " groups selected" << endl;
	d->selectedMetaContacts=metaContacts;
	d->selectedGroups=groups;

	emit metaContactSelected( groups.isEmpty() && metaContacts.count()==1 );
	emit selectionChanged();
}

MetaContact* ContactList::myself()
{
	if(!d->myself)
		d->myself=new MetaContact();
	return d->myself;
}

void ContactList::loadGlobalIdentity()
{
 	// Apply the global identity
	if(Kopete::Config::enableGlobalIdentity())
 	{
		// Disconnect to make sure it will not cause duplicate calls.
		disconnect(myself(), TQ_SIGNAL(displayNameChanged(const TQString&, const TQString&)), this, TQ_SLOT(slotDisplayNameChanged()));
		disconnect(myself(), TQ_SIGNAL(photoChanged()), this, TQ_SLOT(slotPhotoChanged()));

		connect(myself(), TQ_SIGNAL(displayNameChanged(const TQString&, const TQString&)), this, TQ_SLOT(slotDisplayNameChanged()));
		connect(myself(), TQ_SIGNAL(photoChanged()), this, TQ_SLOT(slotPhotoChanged()));

		// Ensure that the myself metaContactId is always the KABC whoAmI
		TDEABC::Addressee a = TDEABC::StdAddressBook::self()->whoAmI();
		if(!a.isEmpty() && a.uid() != myself()->metaContactId())
		{
			myself()->setMetaContactId(a.uid());
		}

		// Apply the global identity
		// Maybe one of the myself contact from a account has a different displayName/photo at startup.
		slotDisplayNameChanged();
		slotPhotoChanged();
 	}
	else
	{
		disconnect(myself(), TQ_SIGNAL(displayNameChanged(const TQString&, const TQString&)), this, TQ_SLOT(slotDisplayNameChanged()));
		disconnect(myself(), TQ_SIGNAL(photoChanged()), this, TQ_SLOT(slotPhotoChanged()));
	}
}

void ContactList::slotDisplayNameChanged()
{
	static bool mutex=false;
	if(mutex)
	{
		kdDebug (14010) << k_funcinfo << " mutex blocked" << endl ;
		return;
	}
	mutex=true;

	kdDebug( 14010 ) << k_funcinfo << myself()->displayName() << endl;

	emit globalIdentityChanged(Kopete::Global::Properties::self()->nickName().key(), myself()->displayName());
	mutex=false;
}

void ContactList::slotPhotoChanged()
{
	static bool mutex=false;
	if(mutex)
	{
		kdDebug (14010) << k_funcinfo << " mutex blocked" << endl ;
		return;
	}
	mutex=true;
	kdDebug( 14010 ) << k_funcinfo << myself()->picture().path() << endl;

	emit globalIdentityChanged(Kopete::Global::Properties::self()->photo().key(), myself()->picture().path());
	mutex=false;
	/* The mutex is usefull to don't have such as stack overflow 
	Kopete::ContactList::slotPhotoChanged  ->  Kopete::ContactList::globalIdentityChanged  
	MSNAccount::slotGlobalIdentityChanged  ->  Kopete::Contact::propertyChanged 
	Kopete::MetaContact::slotPropertyChanged -> Kopete::MetaContact::photoChanged -> Kopete::ContactList::slotPhotoChanged 
	*/
}

///////////////////////////////////////////////////////////////////////////////////////////////
void ContactList::load()
{
	loadXML();
	// Apply the global identity when all the protocols plugins are loaded.
	connect(PluginManager::self(), TQ_SIGNAL(allPluginsLoaded()), this, TQ_SLOT(loadGlobalIdentity()));
}

void ContactList::loadXML()
{
	// don't save when we're in the middle of this...
	d->loaded = false;

	TQString filename = locateLocal( "appdata", TQString::fromLatin1( "contactlist.xml" ) );
	if( filename.isEmpty() )
	{
		d->loaded=true;
		return ;
	}

	TQDomDocument contactList( TQString::fromLatin1( "kopete-contact-list" ) );

	TQFile contactListFile( filename );
	contactListFile.open( IO_ReadOnly );
	contactList.setContent( &contactListFile );

	TQDomElement list = contactList.documentElement();

	TQString versionString = list.attribute( TQString::fromLatin1( "version" ), TQString() );
	uint version = 0;
	if( TQRegExp( TQString::fromLatin1( "[0-9]+\\.[0-9]" ) ).exactMatch( versionString ) )
		version = versionString.replace( TQString::fromLatin1( "." ), TQString() ).toUInt();

	if( version < Private::ContactListVersion )
	{
		// The version string is invalid, or we're using an older version.
		// Convert first and reparse the file afterwards
		kdDebug( 14010 ) << k_funcinfo << "Contact list version " << version
			<< " is older than current version " <<  Private::ContactListVersion
			<< ". Converting first." << endl;

		contactListFile.close();

		convertContactList( filename, version,  Private::ContactListVersion );

		contactList = TQDomDocument ( TQString::fromLatin1( "kopete-contact-list" ) );

		contactListFile.open( IO_ReadOnly );
		contactList.setContent( &contactListFile );

		list = contactList.documentElement();
	}

	addGroup( Kopete::Group::topLevel() );

	TQDomElement element = list.firstChild().toElement();
	while( !element.isNull() )
	{
		if( element.tagName() == TQString::fromLatin1("meta-contact") )
		{
			//TODO: id isn't used
			//TQString id = element.attribute( "id", TQString() );
			Kopete::MetaContact *metaContact = new Kopete::MetaContact();
			if ( !metaContact->fromXML( element ) )
			{
				delete metaContact;
				metaContact = 0;
			}
			else
			{
				Kopete::ContactList::self()->addMetaContact(
					metaContact );
			}
		}
		else if( element.tagName() == TQString::fromLatin1("kopete-group") )
		{
			Kopete::Group *group = new Kopete::Group();
			if( !group->fromXML( element ) )
			{
				delete group;
				group = 0;
			}
			else
			{
				Kopete::ContactList::self()->addGroup( group );
			}
		}
		// Only load myself metacontact information when Global Identity is enabled.
		else if( element.tagName() == TQString::fromLatin1("myself-meta-contact") && Kopete::Config::enableGlobalIdentity() )
		{
			if( !myself()->fromXML( element ) )
			{
				delete d->myself;
				d->myself = 0;
			}
		}
		else
		{
			kdWarning(14010) << "Kopete::ContactList::loadXML: "
				<< "Unknown element '" << element.tagName()
				<< "' in contact list!" << endl;
		}
		element = element.nextSibling().toElement();
	}
	contactListFile.close();
	d->loaded=true;
}

void ContactList::convertContactList( const TQString &fileName, uint /* fromVersion */, uint /* toVersion */ )
{
	// For now, ignore fromVersion and toVersion. These are meant for future
	// changes to allow incremental (multi-pass) conversion so we don't have
	// to rewrite the whole conversion code for each change.

	TQDomDocument contactList( TQString::fromLatin1( "messaging-contact-list" ) );
	TQFile contactListFile( fileName );
	contactListFile.open( IO_ReadOnly );
	contactList.setContent( &contactListFile );

	TQDomElement oldList = contactList.documentElement();

	TQDomDocument newList( TQString::fromLatin1( "kopete-contact-list" ) );
	newList.appendChild( newList.createProcessingInstruction( TQString::fromLatin1( "xml" ), TQString::fromLatin1( "version=\"1.0\"" ) ) );

	TQDomElement newRoot = newList.createElement( TQString::fromLatin1( "kopete-contact-list" ) );
	newList.appendChild( newRoot );
	newRoot.setAttribute( TQString::fromLatin1( "version" ), TQString::fromLatin1( "1.0" ) );

	TQDomNode oldNode = oldList.firstChild();
	while( !oldNode.isNull() )
	{
		TQDomElement oldElement = oldNode.toElement();
		if( !oldElement.isNull() )
		{
			if( oldElement.tagName() == TQString::fromLatin1("meta-contact") )
			{
				// Ignore ID, it is not used in the current list anyway
				TQDomElement newMetaContact = newList.createElement( TQString::fromLatin1( "meta-contact" ) );
				newRoot.appendChild( newMetaContact );

				// Plugin data is stored completely different, and requires
				// some bookkeeping to convert properly
				TQMap<TQString, TQDomElement> pluginData;
				TQStringList icqData;
				TQStringList gaduData;

				// ICQ and Gadu can only be converted properly if the address book fields
				// are already parsed. Therefore, scan for those first and add the rest
				// afterwards
				TQDomNode oldContactNode = oldNode.firstChild();
				while( !oldContactNode.isNull() )
				{
					TQDomElement oldContactElement = oldContactNode.toElement();
					if( !oldContactElement.isNull() && oldContactElement.tagName() == TQString::fromLatin1("address-book-field") )
					{
						// Convert address book fields.
						// Jabber will be called "xmpp", Aim/Toc and Aim/Oscar both will
						// be called "aim". MSN, AIM, IRC, Oscar and SMS don't use address
						// book fields yet; Gadu and ICQ can be converted as-is.
						// As Yahoo is unfinished we won't try to convert at all.
						TQString id   = oldContactElement.attribute( TQString::fromLatin1( "id" ), TQString() );
						TQString data = oldContactElement.text();

						TQString app, key, val;
						TQString separator = TQString::fromLatin1( "," );
						if( id == TQString::fromLatin1( "messaging/gadu" ) )
							separator = TQString::fromLatin1( "\n" );
						else if( id == TQString::fromLatin1( "messaging/icq" ) )
							separator = TQString::fromLatin1( ";" );
						else if( id == TQString::fromLatin1( "messaging/jabber" ) )
							id = TQString::fromLatin1( "messaging/xmpp" );

						if( id == TQString::fromLatin1( "messaging/gadu" ) || id == TQString::fromLatin1( "messaging/icq" ) ||
							id == TQString::fromLatin1( "messaging/winpopup" ) || id == TQString::fromLatin1( "messaging/xmpp" ) )
						{
							app = id;
							key = TQString::fromLatin1( "All" );
							val = data.replace( separator, TQChar( 0xE000 ) );
						}

						if( !app.isEmpty() )
						{
							TQDomElement addressBookField = newList.createElement( TQString::fromLatin1( "address-book-field" ) );
							newMetaContact.appendChild( addressBookField );

							addressBookField.setAttribute( TQString::fromLatin1( "app" ), app );
							addressBookField.setAttribute( TQString::fromLatin1( "key" ), key );

							addressBookField.appendChild( newList.createTextNode( val ) );

							// ICQ didn't store the contactId locally, only in the address
							// book fields, so we need to be able to access it later
							if( id == TQString::fromLatin1( "messaging/icq" ) )
								icqData = TQStringList::split( TQChar( 0xE000 ), val );
							else if( id == TQString::fromLatin1("messaging/gadu") )
								gaduData = TQStringList::split( TQChar( 0xE000 ), val );
						}
					}
					oldContactNode = oldContactNode.nextSibling();
				}

				// Now, convert the other elements
				oldContactNode = oldNode.firstChild();
				while( !oldContactNode.isNull() )
				{
					TQDomElement oldContactElement = oldContactNode.toElement();
					if( !oldContactElement.isNull() )
					{
						if( oldContactElement.tagName() == TQString::fromLatin1("display-name") )
						{
							TQDomElement displayName = newList.createElement( TQString::fromLatin1( "display-name" ) );
							displayName.appendChild( newList.createTextNode( oldContactElement.text() ) );
							newMetaContact.appendChild( displayName );
						}
						else if( oldContactElement.tagName() == TQString::fromLatin1("groups") )
						{
							TQDomElement groups = newList.createElement( TQString::fromLatin1( "groups" ) );
							newMetaContact.appendChild( groups );

							TQDomNode oldGroup = oldContactElement.firstChild();
							while( !oldGroup.isNull() )
							{
								TQDomElement oldGroupElement = oldGroup.toElement();
								if ( oldGroupElement.tagName() == TQString::fromLatin1("group") )
								{
									TQDomElement group = newList.createElement( TQString::fromLatin1( "group" ) );
									group.appendChild( newList.createTextNode( oldGroupElement.text() ) );
									groups.appendChild( group );
								}
								else if ( oldGroupElement.tagName() == TQString::fromLatin1("top-level") )
								{
									TQDomElement group = newList.createElement( TQString::fromLatin1( "top-level" ) );
									groups.appendChild( group );
								}

								oldGroup = oldGroup.nextSibling();
							}
						}
						else if( oldContactElement.tagName() == TQString::fromLatin1( "plugin-data" ) )
						{
							// Convert the plugin data
							TQString id   = oldContactElement.attribute( TQString::fromLatin1( "plugin-id" ), TQString() );
							TQString data = oldContactElement.text();

							bool convertOldAim = false;
							uint fieldCount = 1;
							TQString addressBookLabel;
							if( id == TQString::fromLatin1("MSNProtocol") )
							{
								fieldCount = 3;
								addressBookLabel = TQString::fromLatin1("msn");
							}
							else if( id == TQString::fromLatin1("IRCProtocol") )
							{
								fieldCount = 3;
								addressBookLabel = TQString::fromLatin1("irc");
							}
							else if( id == TQString::fromLatin1("OscarProtocol") )
							{
								fieldCount = 2;
								addressBookLabel = TQString::fromLatin1("aim");
							}
							else if( id == TQString::fromLatin1("AIMProtocol") )
							{
								id = TQString::fromLatin1("OscarProtocol");
								convertOldAim = true;
								addressBookLabel = TQString::fromLatin1("aim");
							}
							else if( id == TQString::fromLatin1("ICQProtocol") || id == TQString::fromLatin1("WPProtocol") || id == TQString::fromLatin1("GaduProtocol") )
							{
								fieldCount = 1;
							}
							else if( id == TQString::fromLatin1("JabberProtocol") )
							{
								fieldCount = 4;
							}
							else if( id == TQString::fromLatin1("SMSProtocol") )
							{
								// SMS used a variable serializing using a dot as delimiter.
								// The minimal count is three though (id, name, delimiter).
								fieldCount = 2;
								addressBookLabel = TQString::fromLatin1("sms");
							}

							if( pluginData[ id ].isNull() )
							{
								pluginData[ id ] = newList.createElement( TQString::fromLatin1( "plugin-data" ) );
								pluginData[ id ].setAttribute( TQString::fromLatin1( "plugin-id" ), id );
								newMetaContact.appendChild( pluginData[ id ] );
							}

							// Do the actual conversion
							if( id == TQString::fromLatin1( "MSNProtocol" ) || id == TQString::fromLatin1( "OscarProtocol" ) ||
								id == TQString::fromLatin1( "AIMProtocol" ) || id == TQString::fromLatin1( "IRCProtocol" ) ||
								id == TQString::fromLatin1( "ICQProtocol" ) || id == TQString::fromLatin1( "JabberProtocol" ) ||
								id == TQString::fromLatin1( "SMSProtocol" ) || id == TQString::fromLatin1( "WPProtocol" ) ||
								id == TQString::fromLatin1( "GaduProtocol" ) )
							{
								TQStringList strList = TQStringList::split( TQString::fromLatin1( "||" ), data );

								// Unescape '||'
								for( TQStringList::iterator it = strList.begin(); it != strList.end(); ++it )
								{
									( *it ).replace( TQString::fromLatin1( "\\|;" ), TQString::fromLatin1( "|" ) ).
									replace( TQString::fromLatin1( "\\\\" ), TQString::fromLatin1( "\\" ) );
								}

								uint idx = 0;
								while( idx < strList.size() )
								{
									TQDomElement dataField;

									dataField = newList.createElement( TQString::fromLatin1( "plugin-data-field" ) );
									pluginData[ id ].appendChild( dataField );
									dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "contactId" ) );
									if( id == TQString::fromLatin1("ICQProtocol") )
										dataField.appendChild( newList.createTextNode( icqData[ idx ] ) );
									else if( id == TQString::fromLatin1("GaduProtocol") )
										dataField.appendChild( newList.createTextNode( gaduData[ idx ] ) );
									else if( id == TQString::fromLatin1("JabberProtocol") )
										dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) );
									else
										dataField.appendChild( newList.createTextNode( strList[ idx ] ) );

									dataField = newList.createElement( TQString::fromLatin1( "plugin-data-field" ) );
									pluginData[ id ].appendChild( dataField );
									dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "displayName" ) );
									if( convertOldAim || id == TQString::fromLatin1("ICQProtocol") || id == TQString::fromLatin1("WPProtocol") || id == TQString::fromLatin1("GaduProtocol") )
										dataField.appendChild( newList.createTextNode( strList[ idx ] ) );
									else if( id == TQString::fromLatin1("JabberProtocol") )
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );
									else
										dataField.appendChild( newList.createTextNode( strList[ idx + 1 ] ) );

									if( id == TQString::fromLatin1("MSNProtocol") )
									{
										dataField = newList.createElement( TQString::fromLatin1( "plugin-data-field" ) );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "groups" ) );
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );
									}
									else if( id == TQString::fromLatin1("IRCProtocol") )
									{
										dataField = newList.createElement( TQString::fromLatin1( "plugin-data-field" ) );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "serverName" ) );
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );
									}
									else if( id == TQString::fromLatin1("JabberProtocol") )
									{
										dataField = newList.createElement( TQString::fromLatin1( "plugin-data-field" ) );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "accountId" ) );
										dataField.appendChild( newList.createTextNode( strList[ idx ] ) );

										dataField = newList.createElement( TQString::fromLatin1( "plugin-data-field" ) );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "groups" ) );
										dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) );
									}
									else if( id == TQString::fromLatin1( "SMSProtocol" ) &&
										( idx + 2 < strList.size() ) && strList[ idx + 2 ] != TQString::fromLatin1( "." ) )
									{
										dataField = newList.createElement( TQString::fromLatin1( "plugin-data-field" ) );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "serviceName" ) );
										dataField.appendChild( newList.createTextNode( strList[ idx + 2 ] ) );

										dataField = newList.createElement( TQString::fromLatin1( "plugin-data-field" ) );
										pluginData[ id ].appendChild( dataField );
										dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "servicePrefs" ) );
										dataField.appendChild( newList.createTextNode( strList[ idx + 3 ] ) );

										// Add extra fields
										idx += 2;
									}

									// MSN, AIM, IRC, Oscar and SMS didn't store address book fields up
									// to now, so create one
									if( id != TQString::fromLatin1("ICQProtocol") && id != TQString::fromLatin1("JabberProtocol") && id != TQString::fromLatin1("WPProtocol") && id != TQString::fromLatin1("GaduProtocol") )
									{
										TQDomElement addressBookField = newList.createElement( TQString::fromLatin1( "address-book-field" ) );
										newMetaContact.appendChild( addressBookField );

										addressBookField.setAttribute( TQString::fromLatin1( "app" ),
											TQString::fromLatin1( "messaging/" ) + addressBookLabel );
										addressBookField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "All" ) );
										addressBookField.appendChild( newList.createTextNode( strList[ idx ] ) );
									}

									idx += fieldCount;
								}
							}
							else if( id == TQString::fromLatin1("ContactNotesPlugin") || id == TQString::fromLatin1("CryptographyPlugin") || id == TQString::fromLatin1("TranslatorPlugin") )
							{
								TQDomElement dataField = newList.createElement( TQString::fromLatin1( "plugin-data-field" ) );
								pluginData[ id ].appendChild( dataField );
								if( id == TQString::fromLatin1("ContactNotesPlugin") )
									dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "notes" ) );
								else if( id == TQString::fromLatin1("CryptographyPlugin") )
									dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "gpgKey" ) );
								else if( id == TQString::fromLatin1("TranslatorPlugin") )
									dataField.setAttribute( TQString::fromLatin1( "key" ), TQString::fromLatin1( "languageKey" ) );

								dataField.appendChild( newList.createTextNode( data ) );
							}
						}
					}
					oldContactNode = oldContactNode.nextSibling();
				}
			}
			else if( oldElement.tagName() == TQString::fromLatin1("kopete-group") )
			{
				TQDomElement newGroup = newList.createElement( TQString::fromLatin1( "kopete-group" ) );
				newRoot.appendChild( newGroup );

				TQDomNode oldGroupNode = oldNode.firstChild();
				while( !oldGroupNode.isNull() )
				{
					TQDomElement oldGroupElement = oldGroupNode.toElement();

					if( oldGroupElement.tagName() == TQString::fromLatin1("display-name") )
					{
						TQDomElement displayName = newList.createElement( TQString::fromLatin1( "display-name" ) );
						displayName.appendChild( newList.createTextNode( oldGroupElement.text() ) );
						newGroup.appendChild( displayName );
					}
					if( oldGroupElement.tagName() == TQString::fromLatin1("type") )
					{
						if( oldGroupElement.text() == TQString::fromLatin1("Temporary") )
							newGroup.setAttribute( TQString::fromLatin1( "type" ), TQString::fromLatin1( "temporary" ) );
						else if( oldGroupElement.text() == TQString::fromLatin1( "TopLevel" ) )
							newGroup.setAttribute( TQString::fromLatin1( "type" ), TQString::fromLatin1( "top-level" ) );
						else
							newGroup.setAttribute( TQString::fromLatin1( "type" ), TQString::fromLatin1( "standard" ) );
					}
					if( oldGroupElement.tagName() == TQString::fromLatin1("view") )
					{
						if( oldGroupElement.text() == TQString::fromLatin1("collapsed") )
							newGroup.setAttribute( TQString::fromLatin1( "view" ), TQString::fromLatin1( "collapsed" ) );
						else
							newGroup.setAttribute( TQString::fromLatin1( "view" ), TQString::fromLatin1( "expanded" ) );
					}
					else if( oldGroupElement.tagName() == TQString::fromLatin1("plugin-data") )
					{
						// Per-group plugin data
						// FIXME: This needs updating too, ideally, convert this in a later
						//        contactlist.xml version
						TQDomElement groupPluginData = newList.createElement( TQString::fromLatin1( "plugin-data" ) );
						newGroup.appendChild( groupPluginData );

						groupPluginData.setAttribute( TQString::fromLatin1( "plugin-id" ),
							oldGroupElement.attribute( TQString::fromLatin1( "plugin-id" ), TQString() ) );
						groupPluginData.appendChild( newList.createTextNode( oldGroupElement.text() ) );
					}

					oldGroupNode = oldGroupNode.nextSibling();
				}
			}
			else
			{
				kdWarning( 14010 ) << k_funcinfo << "Unknown element '" << oldElement.tagName()
					<< "' in contact list!" << endl;
			}
		}
		oldNode = oldNode.nextSibling();
	}

	// Close the file, and save the new file
	contactListFile.close();

	TQDir().rename( fileName, fileName + TQString::fromLatin1( ".bak" ) );

	// kdDebug( 14010 ) << k_funcinfo << "XML output:\n" << newList.toString( 2 ) << endl;

	contactListFile.open( IO_WriteOnly );
	TQTextStream stream( &contactListFile );
	stream.setEncoding( TQTextStream::UnicodeUTF8 );
	stream << newList.toString( 2 );

	contactListFile.flush();
	contactListFile.close();
}

void Kopete::ContactList::save()
{
	saveXML();
}

void Kopete::ContactList::saveXML()
{
	if(!d->loaded)
	{
		kdDebug(14010) << "Kopete::ContactList::saveXML: contactlist not loaded, abort saving" << endl;
		return;
	}

	TQString contactListFileName = locateLocal( "appdata", TQString::fromLatin1( "contactlist.xml" ) );
	KSaveFile contactListFile( contactListFileName );
	if( contactListFile.status() == 0 )
	{
		TQTextStream *stream = contactListFile.textStream();
		stream->setEncoding( TQTextStream::UnicodeUTF8 );
		toXML().save( *stream, 4 );

		if ( contactListFile.close() )
		{
			// cancel any scheduled saves
			d->saveTimer->stop();
			return;
		}
		else
		{
			kdDebug(14010) << "Kopete::ContactList::saveXML: failed to write contactlist, error code is: " << contactListFile.status() << endl;
		}
	}
	else
	{
		kdWarning(14010) << "Kopete::ContactList::saveXML: Couldn't open contact list file "
			<< contactListFileName << ". Contact list not saved." << endl;
	}

	// if we got here, saving the contact list failed. retry every minute until it works.
	d->saveTimer->start( 60000, true /* single-shot: will get restarted by us next time if it's still failing */ );
}

const TQDomDocument ContactList::toXML()
{
	TQDomDocument doc;
	doc.appendChild( doc.createElement( TQString::fromLatin1("kopete-contact-list") ) );
	doc.documentElement().setAttribute( TQString::fromLatin1("version"), TQString::fromLatin1("1.0"));

	// Save group information. ie: Open/Closed, pehaps later icons? Who knows.
	for( Kopete::Group *g = d->groups.first(); g; g = d->groups.next() )
		doc.documentElement().appendChild( doc.importNode( g->toXML(), true ) );

	// Save metacontact information.
	for( Kopete::MetaContact *m = d->contacts.first(); m; m = d->contacts.next() )
		if( !m->isTemporary() )
			doc.documentElement().appendChild( doc.importNode( m->toXML(), true ) );

	// Save myself metacontact information
	if( Kopete::Config::enableGlobalIdentity() )
	{
		TQDomElement myselfElement = myself()->toXML(true); // Save minimal information.
		myselfElement.setTagName( TQString::fromLatin1("myself-meta-contact") );
		doc.documentElement().appendChild( doc.importNode( myselfElement, true ) );
	}

	return doc;
}

TQStringList ContactList::contacts() const
{
	TQStringList contacts;
	TQPtrListIterator<Kopete::MetaContact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		contacts.append( it.current()->displayName() );
	}
	return contacts;
}

TQStringList ContactList::contactStatuses() const
{
	TQStringList meta_contacts;
	TQPtrListIterator<Kopete::MetaContact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		meta_contacts.append( TQString::fromLatin1( "%1 (%2)" ).
			arg( it.current()->displayName(), it.current()->statusString() ));
	}
	return meta_contacts;
}

TQStringList ContactList::reachableContacts() const
{
	TQStringList contacts;
	TQPtrListIterator<Kopete::MetaContact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		if ( it.current()->isReachable() )
			contacts.append( it.current()->displayName() );
	}
	return contacts;
}

TQPtrList<Contact> ContactList::onlineContacts() const
{
	TQPtrList<Kopete::Contact> result;
	TQPtrListIterator<Kopete::MetaContact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		if ( it.current()->isOnline() )
		{
			TQPtrList<Kopete::Contact> contacts = it.current()->contacts();
			TQPtrListIterator<Kopete::Contact> cit( contacts );
			for( ; cit.current(); ++cit )
			{
				if ( cit.current()->isOnline() )
					result.append( cit.current() );
			}
		}
	}
	return result;
}

TQPtrList<Kopete::MetaContact> Kopete::ContactList::onlineMetaContacts() const
{
	TQPtrList<Kopete::MetaContact> result;
	TQPtrListIterator<Kopete::MetaContact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		if ( it.current()->isOnline() )
			result.append( it.current() );
	}
	return result;
}

TQPtrList<Kopete::MetaContact> Kopete::ContactList::onlineMetaContacts( const TQString &protocolId ) const
{
	TQPtrList<Kopete::MetaContact> result;
	TQPtrListIterator<Kopete::MetaContact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		// FIXME: This loop is not very efficient :(
		if ( it.current()->isOnline() )
		{
			TQPtrList<Kopete::Contact> contacts = it.current()->contacts();
			TQPtrListIterator<Kopete::Contact> cit( contacts );
			for( ; cit.current(); ++cit )
			{
				if( cit.current()->isOnline() && cit.current()->protocol()->pluginId() == protocolId )
					result.append( it.current() );
			}
		}
	}
	return result;
}

TQPtrList<Kopete::Contact> Kopete::ContactList::onlineContacts( const TQString &protocolId ) const
{
	TQPtrList<Kopete::Contact> result;
	TQPtrListIterator<Kopete::MetaContact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		// FIXME: This loop is not very efficient :(
		if ( it.current()->isOnline() )
		{
			TQPtrList<Kopete::Contact> contacts = it.current()->contacts();
			TQPtrListIterator<Kopete::Contact> cit( contacts );
			for( ; cit.current(); ++cit )
			{
				if( cit.current()->isOnline() && cit.current()->protocol()->pluginId() == protocolId )
					result.append( cit.current() );
			}
		}
	}
	return result;
}

TQStringList Kopete::ContactList::fileTransferContacts() const
{
	TQStringList contacts;
	TQPtrListIterator<Kopete::MetaContact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		if ( it.current()->canAcceptFiles() )
			contacts.append( it.current()->displayName() );
	}
	return contacts;
}

void Kopete::ContactList::sendFile( const TQString &displayName, const KURL &sourceURL,
	const TQString &altFileName, const long unsigned int fileSize)
{
//	kdDebug(14010) << "Send To Display Name: " << displayName << "\n";

	Kopete::MetaContact *c = findMetaContactByDisplayName( displayName );
	if( c )
		c->sendFile( sourceURL, altFileName, fileSize );
}

void Kopete::ContactList::messageContact( const TQString &contactId, const TQString &messageText )
{
	Kopete::MetaContact *mc = findMetaContactByContactId( contactId );
	if (!mc) return;

	Kopete::Contact *c = mc->execute(); //We need to know which contact was chosen as the preferred in order to message it
	if (!c) return;

	Kopete::Message msg(c->account()->myself(), c, messageText, Kopete::Message::Outbound);
	c->manager(Contact::CanCreate)->sendMessage(msg);

}


TQStringList Kopete::ContactList::contactFileProtocols(const TQString &displayName)
{
//	kdDebug(14010) << "Get contacts for: " << displayName << "\n";
	TQStringList protocols;

	Kopete::MetaContact *c = findMetaContactByDisplayName( displayName );
	if( c )
	{
		TQPtrList<Kopete::Contact> mContacts = c->contacts();
		kdDebug(14010) << mContacts.count() << endl;
		TQPtrListIterator<Kopete::Contact> jt( mContacts );
		for ( ; jt.current(); ++jt )
		{
			kdDebug(14010) << "1" << jt.current()->protocol()->pluginId() << endl;
			if( jt.current()->canAcceptFiles() ) {
				kdDebug(14010) << jt.current()->protocol()->pluginId() << endl;
				protocols.append ( jt.current()->protocol()->pluginId() );
			}
		}
		return protocols;
	}
	return TQStringList();
}


void ContactList::slotSaveLater()
{
	// if we already have a save scheduled, it will be cancelled. either way,
	// start a timer to save the contact list a bit later.
	d->saveTimer->start( 17100 /* 17,1 seconds */, true /* single-shot */ );
}

void ContactList::slotKABCChanged()
{
	// TODO: react to changes in KABC, replacing this function, post 3.4 (Will)
	// call syncWithKABC on each metacontact to check if its associated tdeabc entry has changed.
/*	for ( MetaContact * mc = d->contacts.first(); mc; mc = d->contacts.next() )

		mc->syncWithKABC();*/
}


} //END namespace Kopete

#include "kopetecontactlist.moc"
