/*
    kopetemetacontact.cpp - Kopete Meta Contact

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

    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 "kopetemetacontact.h"

#include <tdeapplication.h>

#include <tdeabc/addressbook.h>
#include <tdeabc/addressee.h>

#include <kdebug.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdeversion.h>

#include "tdeabcpersistence.h"
#include "kopetecontactlist.h"
#include "kopetecontact.h"
#include "kopeteaccountmanager.h"
#include "kopeteprotocol.h"
#include "kopeteaccount.h"
#include "kopetepluginmanager.h"
#include "kopetegroup.h"
#include "kopeteglobal.h"
#include "kopeteprefs.h"
#include "kopeteuiglobal.h"
#include "kopetepicture.h"

namespace Kopete {

// this is just to save typing
const TQString NSCID_ELEM = TQString::fromUtf8("nameSourceContactId" );
const TQString NSPID_ELEM = TQString::fromUtf8( "nameSourcePluginId" );
const TQString NSAID_ELEM = TQString::fromUtf8( "nameSourceAccountId" );
const TQString PSCID_ELEM = TQString::fromUtf8( "photoSourceContactId" );
const TQString PSPID_ELEM = TQString::fromUtf8( "photoSourcePluginId" );
const TQString PSAID_ELEM = TQString::fromUtf8( "photoSourceAccountId" );

class  MetaContact::Private
{ public:
	Private() :
		photoSource(MetaContact::SourceCustom), displayNameSource(MetaContact::SourceCustom),
		displayNameSourceContact(0L),  photoSourceContact(0L), temporary(false),
		onlineStatus(Kopete::OnlineStatus::Offline), photoSyncedWithKABC(false)
	{}

	~Private()
	{}

	TQPtrList<Contact> contacts;

	// property sources	
	PropertySource photoSource;
	PropertySource displayNameSource;

	// when source is contact
	Contact *displayNameSourceContact;
	Contact *photoSourceContact;
	
	// used when source is tdeabc
	TQString metaContactId;
	
	// used when source is custom
	TQString displayName;
	KURL photoUrl;

	TQPtrList<Group> groups;
	TQMap<TQString, TQMap<TQString, TQString> > addressBook;
	bool temporary;
	
	OnlineStatus::StatusType onlineStatus;
	bool photoSyncedWithKABC;

	// Used to set contact source at load.
	TQString nameSourcePID, nameSourceAID, nameSourceCID;
	TQString photoSourcePID, photoSourceAID, photoSourceCID;

	// The photo cache. Reduce disk access and CPU usage.
	Picture customPicture, contactPicture, tdeabcPicture;
};

MetaContact::MetaContact()
	: ContactListElement( ContactList::self() )
{
	d = new Private;

	connect( this, TQT_SIGNAL( pluginDataChanged() ), TQT_SIGNAL( persistentDataChanged() ) );
	connect( this, TQT_SIGNAL( iconChanged( Kopete::ContactListElement::IconState, const TQString & ) ), TQT_SIGNAL( persistentDataChanged() ) );
	connect( this, TQT_SIGNAL( useCustomIconChanged( bool ) ), TQT_SIGNAL( persistentDataChanged() ) );
	connect( this, TQT_SIGNAL( displayNameChanged( const TQString &, const TQString & ) ), TQT_SIGNAL( persistentDataChanged() ) );
	connect( this, TQT_SIGNAL( movedToGroup( Kopete::MetaContact *, Kopete::Group *, Kopete::Group * ) ), TQT_SIGNAL( persistentDataChanged() ) );
	connect( this, TQT_SIGNAL( removedFromGroup( Kopete::MetaContact *, Kopete::Group * ) ), TQT_SIGNAL( persistentDataChanged() ) );
	connect( this, TQT_SIGNAL( addedToGroup( Kopete::MetaContact *, Kopete::Group * ) ), TQT_SIGNAL( persistentDataChanged() ) );
	connect( this, TQT_SIGNAL( contactAdded( Kopete::Contact * ) ), TQT_SIGNAL( persistentDataChanged() ) );
	connect( this, TQT_SIGNAL( contactRemoved( Kopete::Contact * ) ), TQT_SIGNAL( persistentDataChanged() ) );

	// Update the KABC picture when the KDE Address book change.
	connect(KABCPersistence::self()->addressBook(), TQT_SIGNAL(addressBookChanged(AddressBook *)), this, TQT_SLOT(slotUpdateAddressBookPicture()));

	// make sure MetaContact is at least in one group
	addToGroup( Group::topLevel() );
			 //i'm not sure this is correct -Olivier
			 // we probably should do the check in groups() instead
}

MetaContact::~MetaContact()
{
	delete d;
}

void MetaContact::addContact( Contact *c )
{
	if( d->contacts.contains( c ) )
	{
		kdWarning(14010) << "Ignoring attempt to add duplicate contact " << c->contactId() << "!" << endl;
	}
	else
	{
		d->contacts.append( c );

		connect( c, TQT_SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ),
			TQT_SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) );

		connect( c, TQT_SIGNAL( propertyChanged( Kopete::Contact *, const TQString &, const TQVariant &, const TQVariant & ) ),
			this, TQT_SLOT( slotPropertyChanged( Kopete::Contact *, const TQString &, const TQVariant &, const TQVariant & ) ) ) ;

		connect( c, TQT_SIGNAL( contactDestroyed( Kopete::Contact * ) ),
			this, TQT_SLOT( slotContactDestroyed( Kopete::Contact * ) ) );

		connect( c, TQT_SIGNAL( idleStateChanged( Kopete::Contact * ) ),
			this, TQT_SIGNAL( contactIdleStateChanged( Kopete::Contact * ) ) );

		emit contactAdded(c);

		updateOnlineStatus();
		
		// if this is the first contact, probbaly was created by a protocol
		// so it has empty custom properties, then set sources to the contact
		if ( d->contacts.count() == 1 )
		{
			if ( displayName().isEmpty() )
			{
				setDisplayNameSourceContact(c);
				setDisplayNameSource(SourceContact);
			}
			if ( photo().isNull() )
			{
				setPhotoSourceContact(c);
				setPhotoSource(SourceContact);
			}
		}
	}
}

void MetaContact::updateOnlineStatus()
{
	Kopete::OnlineStatus::StatusType newStatus = Kopete::OnlineStatus::Unknown;
	Kopete::OnlineStatus mostSignificantStatus;

	for ( TQPtrListIterator<Contact> it( d->contacts ); it.current(); ++it )
	{
		// find most significant status
		if ( it.current()->onlineStatus() > mostSignificantStatus )
			mostSignificantStatus = it.current()->onlineStatus();
	}

	newStatus = mostSignificantStatus.status();

	if( newStatus != d->onlineStatus )
	{
		d->onlineStatus = newStatus;
		emit onlineStatusChanged( this, d->onlineStatus );
	}
}


void MetaContact::removeContact(Contact *c, bool deleted)
{
	if( !d->contacts.contains( c ) )
	{
		kdDebug(14010) << k_funcinfo << " Contact is not in this metaContact " << endl;
	}
	else
	{
		// must check before removing, or will always be false
		bool wasTrackingName = ( !displayNameSourceContact() && (displayNameSource() == SourceContact) );
		bool wasTrackingPhoto = ( !photoSourceContact() && (photoSource() == SourceContact) );
		// save for later use
		TQString currDisplayName = displayName();

		d->contacts.remove( c );
		
		// if the contact was a source of property data, clean
		if (displayNameSourceContact() == c)
			setDisplayNameSourceContact(0L);
		if (photoSourceContact() == c)
			setPhotoSourceContact(0L);


		if ( wasTrackingName )
		{
			// Oh! this contact was the source for the metacontact's name
			// lets do something
			// is this the only contact?
			if ( d->contacts.isEmpty() )
			{
				// fallback to a custom name as we don't have
				// more contacts to chose as source.
				setDisplayNameSource(SourceCustom);
				// perhaps the custom display name was empty
				// no problems baby, I saved the old one.
				setDisplayName(currDisplayName);
			}
			else
			{
				// we didn't fallback to SourceCustom above so lets use the next
				// contact as source
				setDisplayNameSourceContact( d->contacts.first() );
			}
		}

		if ( wasTrackingPhoto )
		{
			// Oh! this contact was the source for the metacontact's photo
			// lets do something
			// is this the only contact?
			if ( d->contacts.isEmpty() )
			{
				// fallback to a custom photo as we don't have
				// more contacts to chose as source.
				setPhotoSource(SourceCustom);
				// FIXME set the custom photo
			}
			else
			{
				// we didn't fallback to SourceCustom above so lets use the next
				// contact as source
				setPhotoSourceContact( d->contacts.first() );
			}
		}		

		if(!deleted)
		{  //If this function is tell by slotContactRemoved, c is maybe just a TQObject
			disconnect( c, TQT_SIGNAL( onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ),
				this, TQT_SLOT( slotContactStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ) );
			disconnect( c, TQT_SIGNAL( propertyChanged( Kopete::Contact *, const TQString &, const TQVariant &, const TQVariant & ) ),
				this, TQT_SLOT( slotPropertyChanged( Kopete::Contact *, const TQString &, const TQVariant &, const TQVariant & ) ) ) ;
			disconnect( c, TQT_SIGNAL( contactDestroyed( Kopete::Contact * ) ),
				this, TQT_SLOT( slotContactDestroyed( Kopete::Contact * ) ) );
			disconnect( c, TQT_SIGNAL( idleStateChanged( Kopete::Contact * ) ),
				this, TQT_SIGNAL( contactIdleStateChanged( Kopete::Contact *) ) );

			kdDebug( 14010 ) << k_funcinfo << "Contact disconnected" << endl;

			KABCPersistence::self()->write( this );
		}

		// Reparent the contact
		removeChild( c );

		emit contactRemoved( c );
	}
	updateOnlineStatus();
}

Contact *MetaContact::findContact( const TQString &protocolId, const TQString &accountId, const TQString &contactId )
{
	//kdDebug( 14010 ) << k_funcinfo << "Num contacts: " << d->contacts.count() << endl;
	TQPtrListIterator<Contact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		//kdDebug( 14010 ) << k_funcinfo << "Trying " << it.current()->contactId() << ", proto "
		//<< it.current()->protocol()->pluginId() << ", account " << it.current()->accountId() << endl;
		if( ( it.current()->contactId() == contactId ) && ( it.current()->protocol()->pluginId() == protocolId || protocolId.isNull() ) )
		{
			if ( accountId.isNull() )
				return it.current();

			if(it.current()->account())
			{
				if(it.current()->account()->accountId() == accountId)
					return it.current();
			}
		}
	}

	// Contact not found
	return 0L;
}

void MetaContact::setDisplayNameSource(PropertySource source)
{
	TQString oldName = displayName();
	d->displayNameSource = source;
	TQString newName = displayName();
	if ( oldName != newName)
		emit displayNameChanged( oldName, newName );
}

MetaContact::PropertySource MetaContact::displayNameSource() const
{
	return d->displayNameSource;
}

void MetaContact::setPhotoSource(PropertySource source)
{
	PropertySource oldSource = photoSource();
	d->photoSource = source;	
	if ( source != oldSource )
	{
		emit photoChanged();
	}
}

MetaContact::PropertySource MetaContact::photoSource() const
{
	return d->photoSource;
}


Contact *MetaContact::sendMessage()
{
	Contact *c = preferredContact();

	if( !c )
	{
		KMessageBox::queuedMessageBox( UI::Global::mainWidget(), KMessageBox::Sorry,
			i18n( "This user is not reachable at the moment. Please make sure you are connected and using a protocol that supports offline sending, or wait "
			"until this user comes online." ), i18n( "User is Not Reachable" ) );
	}
	else
	{
		c->sendMessage();
		return c;
	}
	return 0L;
}

Contact *MetaContact::startChat()
{
	Contact *c = preferredContact();

	if( !c )
	{
		KMessageBox::queuedMessageBox( UI::Global::mainWidget(), KMessageBox::Sorry,
			i18n( "This user is not reachable at the moment. Please make sure you are connected and using a protocol that supports offline sending, or wait "
			"until this user comes online." ), i18n( "User is Not Reachable" ) );
	}
	else
	{
		c->startChat();
		return c;
	}
	return 0L;
}

Contact *MetaContact::preferredContact()
{
	/*
		This function will determine what contact will be used to reach the contact.

		The prefered contact is choose with the following criterias:  (in that order)
		1) If a contact was an open chatwindow already, we will use that one.
		2) The contact with the better online status is used. But if that
		    contact is not reachable, we prefer return no contact.
		3) If all the criterias aboxe still gives ex-eaquo, we use the preffered
		    account as selected in the account preferances (with the arrows)
	*/

	Contact *contact = 0;
	bool hasOpenView=false; //has the selected contact already an open chatwindow
	for ( TQPtrListIterator<Contact> it( d->contacts ); it.current(); ++it )
	{
		Contact *c=it.current();

		//Does the contact an open chatwindow?
		if( c->manager( Contact::CannotCreate ) )
		{ //no need to check the view. having a manager is enough
			if( !hasOpenView )
			{
				contact=c;
				hasOpenView=true;
				if( c->isReachable() )
					continue;
			} //else, several contact might have an open view, uses following criterias
		}
		else if( hasOpenView && contact->isReachable() )
			continue; //This contact has not open view, but the selected contact has, and is reachable

		// FIXME: The isConnected call should be handled in Contact::isReachable
		//        after KDE 3.2 - Martijn
		if ( !c->account() || !c->account()->isConnected() || !c->isReachable() )
			continue; //if this contact is not reachable, we ignore it.

		if ( !contact )
		{  //this is the first contact.
			contact= c;
			continue;
		}

		if( c->onlineStatus().status() > contact->onlineStatus().status()  )
			contact=c; //this contact has a better status
		else if ( c->onlineStatus().status() == contact->onlineStatus().status() )
		{
			if( c->account()->priority() > contact->account()->priority() )
				contact=c;
			else if(  c->account()->priority() == contact->account()->priority()
					&& c->onlineStatus().weight() > contact->onlineStatus().weight() )
				contact = c;  //the weight is not supposed to follow the same scale for each protocol
		}
	}
	return contact;
}

Contact *MetaContact::execute()
{
	Contact *c = preferredContact();

	if( !c )
	{
		KMessageBox::queuedMessageBox( UI::Global::mainWidget(), KMessageBox::Sorry,
			i18n( "This user is not reachable at the moment. Please make sure you are connected and using a protocol that supports offline sending, or wait "
			"until this user comes online." ), i18n( "User is Not Reachable" ) );
	}
	else
	{
		c->execute();
		return c;
	}

	return 0L;
}

unsigned long int MetaContact::idleTime() const
{
	unsigned long int time = 0;
	TQPtrListIterator<Contact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		unsigned long int i = it.current()->idleTime();
		if( it.current()->isOnline() && i < time || time == 0 )
		{
			time = i;
		}
	}
	return time;
}

TQString MetaContact::statusIcon() const
{
	switch( status() )
	{
		case OnlineStatus::Online:
			if( useCustomIcon() )
				return icon( ContactListElement::Online );
			else
				return TQString::fromUtf8( "metacontact_online" );
		case OnlineStatus::Away:
			if( useCustomIcon() )
				return icon( ContactListElement::Away );
			else
				return TQString::fromUtf8( "metacontact_away" );

		case OnlineStatus::Unknown:
			if( useCustomIcon() )
				return icon( ContactListElement::Unknown );
			if ( d->contacts.isEmpty() )
				return TQString::fromUtf8( "metacontact_unknown" );
			else
				return TQString::fromUtf8( "metacontact_offline" );

		case OnlineStatus::Offline:
		default:
			if( useCustomIcon() )
				return icon( ContactListElement::Offline );
			else
				return TQString::fromUtf8( "metacontact_offline" );
	}
}

TQString MetaContact::statusString() const
{
	switch( status() )
	{
		case OnlineStatus::Online:
			return i18n( "Online" );
		case OnlineStatus::Away:
			return i18n( "Away" );
		case OnlineStatus::Offline:
			return i18n( "Offline" );
		case OnlineStatus::Unknown:
		default:
			return i18n( "Status not available" );
	}
}

OnlineStatus::StatusType MetaContact::status() const
{
	return d->onlineStatus;
}

bool MetaContact::isOnline() const
{
	TQPtrListIterator<Contact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		if( it.current()->isOnline() )
			return true;
	}
	return false;
}

bool MetaContact::isReachable() const
{
	if ( isOnline() )
		return true;

	for ( TQPtrListIterator<Contact> it( d->contacts ); it.current(); ++it )
	{
		if ( it.current()->account()->isConnected() && it.current()->isReachable() )
			return true;
	}
	return false;
}

//Determine if we are capable of accepting file transfers
bool MetaContact::canAcceptFiles() const
{
	if( !isOnline() )
		return false;

	TQPtrListIterator<Contact> it( d->contacts );
	for( ; it.current(); ++it )
	{
		if( it.current()->canAcceptFiles() )
			return true;
	}
	return false;
}

//Slot for sending files
void MetaContact::sendFile( const KURL &sourceURL, const TQString &altFileName, unsigned long fileSize )
{
	//If we can't send any files then exit
	if( d->contacts.isEmpty() || !canAcceptFiles() )
		return;

	//Find the highest ranked protocol that can accept files
	Contact *contact = d->contacts.first();
	for( TQPtrListIterator<Contact> it( d->contacts ) ; it.current(); ++it )
	{
		if( ( *it )->onlineStatus() > contact->onlineStatus() && ( *it )->canAcceptFiles() )
			contact = *it;
	}

	//Call the sendFile slot of this protocol
	contact->sendFile( sourceURL, altFileName, fileSize );
}


void MetaContact::slotContactStatusChanged( Contact * c, const OnlineStatus &status, const OnlineStatus &/*oldstatus*/  )
{
	updateOnlineStatus();
	emit contactStatusChanged( c, status );
}

void MetaContact::setDisplayName( const TQString &name )
{
	/*kdDebug( 14010 ) << k_funcinfo << "Change displayName from " << d->displayName <<
		" to " << name  << ", d->trackChildNameChanges=" << d->trackChildNameChanges << endl;
	kdDebug(14010) << kdBacktrace(6) << endl;*/

	if( name == d->displayName )
		return;

	const TQString old = d->displayName;
	d->displayName = name;

	emit displayNameChanged( old , name );

	for( TQPtrListIterator<Kopete::Contact> it( d->contacts ) ; it.current(); ++it )
		( *it )->sync(Contact::DisplayNameChanged);

}

TQString MetaContact::customDisplayName() const
{
	return d->displayName;
}

TQString MetaContact::displayName() const
{
	PropertySource source = displayNameSource();
	if ( source == SourceKABC )
	{
		// tdeabc source, try to get from addressbook
		// if the metacontact has a tdeabc association
		if ( !metaContactId().isEmpty() )
			return nameFromKABC(metaContactId());
	}
	else if ( source == SourceContact )
	{
		if ( d->displayNameSourceContact==0 )
		{
			if( d->contacts.count() >= 1 )
			{// don't call setDisplayNameSource , or there will probably be an infinite loop
				d->displayNameSourceContact=d->contacts.first();
//				kdDebug( 14010 ) << k_funcinfo << " setting displayname source for " << metaContactId()  << endl;
			}
		}
		if ( displayNameSourceContact() != 0L )
		{
			return nameFromContact(displayNameSourceContact());
		}
		else
		{
//			kdDebug( 14010 ) << k_funcinfo << " source == SourceContact , but there is no displayNameSourceContact for contact " << metaContactId() << endl;
		}
	}
	return d->displayName;
}

TQString nameFromKABC( const TQString &id ) /*const*/
{
	TDEABC::AddressBook* ab = KABCPersistence::self()->addressBook();
	if ( ! id.isEmpty() && !id.contains(':') )
	{
		TDEABC::Addressee theAddressee = ab->findByUid(id);
		if ( theAddressee.isEmpty() )
		{
			kdDebug( 14010 ) << k_funcinfo << "no TDEABC::Addressee found for ( " << id << " ) " << " in current address book" << endl;
		}
		else
		{
			return theAddressee.formattedName();
		}
	}
	// no tdeabc association, return null image
	return TQString();
}

TQString nameFromContact( Kopete::Contact *c) /*const*/
{
	if ( !c ) 
		return TQString();

	TQString contactName;
	if ( c->hasProperty( Kopete::Global::Properties::self()->nickName().key() ) )
		contactName = c->property( Global::Properties::self()->nickName()).value().toString();

				//the replace is there to workaround the Bug 95444
	return contactName.isEmpty() ? c->contactId() : contactName.replace('\n',TQString::fromUtf8(""));
}

KURL MetaContact::customPhoto() const
{
	return d->photoUrl;
}

void MetaContact::setPhoto( const KURL &url )
{
	d->photoUrl = url;
	d->customPicture.setPicture(url.path());

	if ( photoSource() == SourceCustom )
	{
		emit photoChanged();
	}
}

TQImage MetaContact::photo() const
{
  if( picture().image().width() > 96 && picture().image().height() > 96 )
    {
      kdDebug( 14010 )  << k_funcinfo << "Resizing image from " << picture().image().width() << " x " << picture().image().height() << endl;
      return picture().image().smoothScale(96,96,TQ_ScaleMin);
    }
  else
    return picture().image();
}

Picture &MetaContact::picture() const
{
	if ( photoSource() == SourceKABC )
	{
		return d->tdeabcPicture;
	}
	else if ( photoSource() == SourceContact )
	{
		return d->contactPicture;
	}

	return d->customPicture;
}

TQImage MetaContact::photoFromCustom() const
{
	return d->customPicture.image();
}

TQImage photoFromContact( Kopete::Contact *contact) /*const*/
{
	if ( contact == 0L )
		return TQImage();

	TQVariant photoProp;
	if ( contact->hasProperty( Kopete::Global::Properties::self()->photo().key() ) )
		photoProp = contact->property( Kopete::Global::Properties::self()->photo().key() ).value();
	
	TQImage img;
	if(photoProp.canCast( TQVariant::Image ))
		img=photoProp.toImage();
	else if(photoProp.canCast( TQVariant::Pixmap ))
		img=photoProp.toPixmap().convertToImage();
	else if(!photoProp.asString().isEmpty())
	{
		img=TQPixmap( photoProp.toString() ).convertToImage();
	}
	return img;
}

TQImage photoFromKABC( const TQString &id ) /*const*/
{
	TDEABC::AddressBook* ab = KABCPersistence::self()->addressBook();
	if ( ! id.isEmpty() && !id.contains(':') )
	{
		TDEABC::Addressee theAddressee = ab->findByUid(id);
		if ( theAddressee.isEmpty() )
		{
			kdDebug( 14010 ) << k_funcinfo << "no TDEABC::Addressee found for ( " << id << " ) " << " in current address book" << endl;
		}
		else
		{
			TDEABC::Picture pic = theAddressee.photo();
			if ( pic.data().isNull() && pic.url().isEmpty() )
				pic = theAddressee.logo();

			if ( pic.isIntern())
			{
				return pic.data();
			}
			else
			{
				return TQPixmap( pic.url() ).convertToImage();
			}
		}
	}
	// no tdeabc association, return null image
	return TQImage();
}

Contact *MetaContact::displayNameSourceContact() const
{
	return d->displayNameSourceContact;
}

Contact *MetaContact::photoSourceContact() const
{
	return d->photoSourceContact;
}

void MetaContact::setDisplayNameSourceContact( Contact *contact )
{
	Contact *old = d->displayNameSourceContact;
	d->displayNameSourceContact = contact;
	if ( displayNameSource() == SourceContact )
	{
		emit displayNameChanged( nameFromContact(old), nameFromContact(contact));
	}
}

void MetaContact::setPhotoSourceContact( Contact *contact )
{
	d->photoSourceContact = contact;
	
	// Create a cache for the contact photo.
	if(d->photoSourceContact != 0L)
	{
		TQVariant photoProp;
		if ( contact->hasProperty( Kopete::Global::Properties::self()->photo().key() ) )
			photoProp = contact->property( Kopete::Global::Properties::self()->photo().key() ).value();

		if(photoProp.canCast( TQVariant::Image ))
			d->contactPicture.setPicture(photoProp.toImage());
		else if(photoProp.canCast( TQVariant::Pixmap ))
			d->contactPicture.setPicture(photoProp.toPixmap().convertToImage());
		else if(!photoProp.asString().isEmpty())
		{
			d->contactPicture.setPicture(photoProp.toString());
		}
	}

	if ( photoSource() == SourceContact )
	{
		emit photoChanged();
	}
}

void MetaContact::slotPropertyChanged( Contact* subcontact, const TQString &key,
		const TQVariant &oldValue, const TQVariant &newValue  )
{
	if ( displayNameSource() == SourceContact )
	{
		if( key == Global::Properties::self()->nickName().key() )
		{
			if (displayNameSourceContact() == subcontact)
			{
				emit displayNameChanged( oldValue.toString(), newValue.toString());
			}
			else
			{
				// HACK the displayName that changed is not from the contact we are tracking, but
				// as the current one is null, lets use this new one
				if (displayName().isEmpty())
					setDisplayNameSourceContact(subcontact);
			}
		}
	}

	if (photoSource() == SourceContact)
	{
		if ( key == Global::Properties::self()->photo().key() )
		{
			if (photoSourceContact() != subcontact)
			{
				// HACK the displayName that changed is not from the contact we are tracking, but
				// as the current one is null, lets use this new one
				if (photo().isNull())
					setPhotoSourceContact(subcontact);
					
			}
			else if(photoSourceContact() == subcontact)
			{
				if(d->photoSyncedWithKABC)
					setPhotoSyncedWithKABC(true);
					
				setPhotoSourceContact(subcontact);
			}
		}
	}
}

void MetaContact::moveToGroup( Group *from, Group *to )
{
	if ( !from || !groups().contains( from )  )
	{
		// We're adding, not moving, because 'from' is illegal
		addToGroup( to );
		return;
	}

	if ( !to || groups().contains( to )  )
	{
		// We're removing, not moving, because 'to' is illegal
		removeFromGroup( from );
		return;
	}

	if ( isTemporary() && to->type() != Group::Temporary )
		return;


	//kdDebug( 14010 ) << k_funcinfo << from->displayName() << " => " << to->displayName() << endl;

	d->groups.remove( from );
	d->groups.append( to );

	for( Contact *c = d->contacts.first(); c ; c = d->contacts.next() )
		c->sync(Contact::MovedBetweenGroup);

	emit movedToGroup( this, from, to );
}

void MetaContact::removeFromGroup( Group *group )
{
	if ( !group || !groups().contains( group ) || ( isTemporary() && group->type() == Group::Temporary ) )
	{
		return;
	}

	d->groups.remove( group );

	// make sure MetaContact is at least in one group
	if ( d->groups.isEmpty() )
	{
		d->groups.append( Group::topLevel() );
		emit addedToGroup( this, Group::topLevel() );
	}

	for( Contact *c = d->contacts.first(); c ; c = d->contacts.next() )
		c->sync(Contact::MovedBetweenGroup);

	emit removedFromGroup( this, group );
}

void MetaContact::addToGroup( Group *to )
{
	if ( !to || groups().contains( to )  )
		return;

	if ( d->temporary && to->type() != Group::Temporary )
		return;

	if ( d->groups.contains( Group::topLevel() ) )
	{
		d->groups.remove( Group::topLevel() );
		emit removedFromGroup( this, Group::topLevel() );
	}

	d->groups.append( to );

	for( Contact *c = d->contacts.first(); c ; c = d->contacts.next() )
		c->sync(Contact::MovedBetweenGroup);

	emit addedToGroup( this, to );
}

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

void MetaContact::slotContactDestroyed( Contact *contact )
{
	removeContact(contact,true);
}

const TQDomElement MetaContact::toXML(bool minimal)
{
	// This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data
	emit aboutToSave(this);

	TQDomDocument metaContact;
	metaContact.appendChild( metaContact.createElement( TQString::fromUtf8( "meta-contact" ) ) );
	metaContact.documentElement().setAttribute( TQString::fromUtf8( "contactId" ), metaContactId() );

	// the custom display name, used for the custom name source
	TQDomElement displayName = metaContact.createElement( TQString::fromUtf8("display-name" ) );
	displayName.appendChild( metaContact.createTextNode( d->displayName ) );
	metaContact.documentElement().appendChild( displayName );
	TQDomElement photo = metaContact.createElement( TQString::fromUtf8("photo" ) );
	KURL photoUrl = d->photoUrl;
	photo.appendChild( metaContact.createTextNode( photoUrl.url() ) );
	metaContact.documentElement().appendChild( photo );

	// Property sources
	TQDomElement propertySources = metaContact.createElement( TQString::fromUtf8("property-sources" ) );
	TQDomElement _nameSource = metaContact.createElement( TQString::fromUtf8("name") );
	TQDomElement _photoSource = metaContact.createElement( TQString::fromUtf8("photo") );

	// set the contact source for display name
	_nameSource.setAttribute(TQString::fromUtf8("source"), sourceToString(displayNameSource()));
	
	// set contact source metadata
	if (displayNameSourceContact())
	{
		TQDomElement contactNameSource = metaContact.createElement( TQString::fromUtf8("contact-source") );
		contactNameSource.setAttribute( NSCID_ELEM, displayNameSourceContact()->contactId() );
		contactNameSource.setAttribute( NSPID_ELEM, displayNameSourceContact()->protocol()->pluginId() );
		contactNameSource.setAttribute( NSAID_ELEM, displayNameSourceContact()->account()->accountId() );
		_nameSource.appendChild( contactNameSource );
	}

	// set the contact source for photo
	_photoSource.setAttribute(TQString::fromUtf8("source"), sourceToString(photoSource()));

	if( !d->metaContactId.isEmpty()  )
		photo.setAttribute( TQString::fromUtf8("syncWithKABC") , TQString::fromUtf8( d->photoSyncedWithKABC ? "true" : "false" ) );

	if (photoSourceContact())
	{
		//kdDebug(14010) << k_funcinfo << "serializing photo source " << nameFromContact(photoSourceContact()) << endl;
		// set contact source metadata for photo
		TQDomElement contactPhotoSource = metaContact.createElement( TQString::fromUtf8("contact-source") );
		contactPhotoSource.setAttribute( NSCID_ELEM, photoSourceContact()->contactId() );
		contactPhotoSource.setAttribute( NSPID_ELEM, photoSourceContact()->protocol()->pluginId() );
		contactPhotoSource.setAttribute( NSAID_ELEM, photoSourceContact()->account()->accountId() );
		_photoSource.appendChild( contactPhotoSource );
	}
	// apend name and photo sources to property sources
	propertySources.appendChild(_nameSource);
	propertySources.appendChild(_photoSource);
	
	metaContact.documentElement().appendChild(propertySources);

	// Don't store these information in minimal mode.
	if(!minimal)
	{
		// Store groups
		if ( !d->groups.isEmpty() )
		{
			TQDomElement groups = metaContact.createElement( TQString::fromUtf8("groups") );
			Group *g;
			for ( g = d->groups.first(); g; g = d->groups.next() )
			{
				TQDomElement group = metaContact.createElement( TQString::fromUtf8("group") );
				group.setAttribute( TQString::fromUtf8("id"), g->groupId() );
				groups.appendChild( group );
			}
			metaContact.documentElement().appendChild( groups );
		}
	
		// Store other plugin data
		TQValueList<TQDomElement> pluginData = Kopete::ContactListElement::toXML();
		for( TQValueList<TQDomElement>::Iterator it = pluginData.begin(); it != pluginData.end(); ++it )
			metaContact.documentElement().appendChild( metaContact.importNode( *it, true ) );
	
		// Store custom notification data
		TQDomElement notifyData = NotifyDataObject::notifyDataToXML();
		if ( notifyData.hasChildNodes() )
			metaContact.documentElement().appendChild( metaContact.importNode( notifyData, true ) );
	}
	return metaContact.documentElement();
}

bool MetaContact::fromXML( const TQDomElement& element )
{
	if( !element.hasChildNodes() )
		return false;

	bool oldPhotoTracking = false;
	bool oldNameTracking = false;

	TQString strContactId = element.attribute( TQString::fromUtf8("contactId") );
	if( !strContactId.isEmpty() )
	{
		d->metaContactId = strContactId;
		// Set the KABC Picture
		slotUpdateAddressBookPicture();
	}

	TQDomElement contactElement = element.firstChild().toElement();
	while( !contactElement.isNull() )
	{
		
		if( contactElement.tagName() == TQString::fromUtf8( "display-name" ) )
		{ // custom display name, used for the custom name source
			
			// WTF, why were we not loading the metacontact if nickname was empty.
			//if ( contactElement.text().isEmpty() )
			//	return false;
			
			//the replace is there to workaround the Bug 95444
			d->displayName = contactElement.text().replace('\n',TQString::fromUtf8(""));

			if ( contactElement.hasAttribute(NSCID_ELEM) && contactElement.hasAttribute(NSPID_ELEM) && contactElement.hasAttribute(NSAID_ELEM))
			{
				oldNameTracking = true;
				//kdDebug(14010) << k_funcinfo << "old name tracking" << endl;
				// retrieve deprecated data (now stored in property-sources)
				// save temporarely, we will find a Contact* with this later
				d->nameSourceCID = contactElement.attribute( NSCID_ELEM );
				d->nameSourcePID = contactElement.attribute( NSPID_ELEM );
				d->nameSourceAID = contactElement.attribute( NSAID_ELEM );
			}
// 			else
// 				kdDebug(14010) << k_funcinfo << "no old name tracking" << endl;
		}
		else if( contactElement.tagName() == TQString::fromUtf8( "photo" ) )
		{
			// custom photo, used for custom photo source
			setPhoto( KURL(contactElement.text()) );

			d->photoSyncedWithKABC = (contactElement.attribute(TQString::fromUtf8("syncWithKABC")) == TQString::fromUtf8("1")) || (contactElement.attribute(TQString::fromUtf8("syncWithKABC")) == TQString::fromUtf8("true"));

			// retrieve deprecated data (now stored in property-sources)
			// save temporarely, we will find a Contact* with this later
			if ( contactElement.hasAttribute(PSCID_ELEM) && contactElement.hasAttribute(PSPID_ELEM) && contactElement.hasAttribute(PSAID_ELEM))
			{
				oldPhotoTracking = true;
// 				kdDebug(14010) << k_funcinfo << "old photo tracking" << endl;
				d->photoSourceCID = contactElement.attribute( PSCID_ELEM );
				d->photoSourcePID = contactElement.attribute( PSPID_ELEM );
				d->photoSourceAID = contactElement.attribute( PSAID_ELEM );
			}
// 			else
// 				kdDebug(14010) << k_funcinfo << "no old photo tracking" << endl;
		}
		else if( contactElement.tagName() == TQString::fromUtf8( "property-sources" ) )
		{
			TQDomNode property = contactElement.firstChild();
			while( !property.isNull() )
			{
				TQDomElement propertyElement = property.toElement();

				if( propertyElement.tagName() == TQString::fromUtf8( "name" ) )
				{
					TQString source = propertyElement.attribute( TQString::fromUtf8("source") );
					setDisplayNameSource(stringToSource(source));
					// find contact sources now.
					TQDomNode propertyParam = propertyElement.firstChild();
					while( !propertyParam.isNull() )
					{
						TQDomElement propertyParamElement = propertyParam.toElement();
						if( propertyParamElement.tagName() == TQString::fromUtf8( "contact-source" ) )
						{
							d->nameSourceCID = propertyParamElement.attribute( NSCID_ELEM );
							d->nameSourcePID = propertyParamElement.attribute( NSPID_ELEM );
							d->nameSourceAID = propertyParamElement.attribute( NSAID_ELEM );
						}
						propertyParam = propertyParam.nextSibling();
					}
				}
				if( propertyElement.tagName() == TQString::fromUtf8( "photo" ) )
				{
					TQString source = propertyElement.attribute( TQString::fromUtf8("source") );
					setPhotoSource(stringToSource(source));
					// find contact sources now.
					TQDomNode propertyParam = propertyElement.firstChild();
					while( !propertyParam.isNull() )
					{
						TQDomElement propertyParamElement = propertyParam.toElement();
						if( propertyParamElement.tagName() == TQString::fromUtf8( "contact-source" ) )
						{
							d->photoSourceCID = propertyParamElement.attribute( NSCID_ELEM );
							d->photoSourcePID = propertyParamElement.attribute( NSPID_ELEM );
							d->photoSourceAID = propertyParamElement.attribute( NSAID_ELEM );
						}
						propertyParam = propertyParam.nextSibling();
					}
				}
				property = property.nextSibling();
			}
		}
		else if( contactElement.tagName() == TQString::fromUtf8( "groups" ) )
		{
			TQDomNode group = contactElement.firstChild();
			while( !group.isNull() )
			{
				TQDomElement groupElement = group.toElement();

				if( groupElement.tagName() == TQString::fromUtf8( "group" ) )
				{
					TQString strGroupId = groupElement.attribute( TQString::fromUtf8("id") );
					if( !strGroupId.isEmpty() )
						addToGroup( Kopete::ContactList::self()->group( strGroupId.toUInt() ) );
					else //kopete 0.6 contactlist
						addToGroup( Kopete::ContactList::self()->findGroup( groupElement.text() ) );
				}
				else if( groupElement.tagName() == TQString::fromUtf8( "top-level" ) ) //kopete 0.6 contactlist
					addToGroup( Kopete::Group::topLevel() );

				group = group.nextSibling();
			}
		}
		else if( contactElement.tagName() == TQString::fromUtf8( "address-book-field" ) )
		{
			TQString app = contactElement.attribute( TQString::fromUtf8( "app" ), TQString() );
			TQString key = contactElement.attribute( TQString::fromUtf8( "key" ), TQString() );
			TQString val = contactElement.text();
			d->addressBook[ app ][ key ] = val;
		}
		else if( contactElement.tagName() == TQString::fromUtf8( "custom-notifications" ) )
		{
			Kopete::NotifyDataObject::notifyDataFromXML( contactElement );
		}
		else //if( groupElement.tagName() == TQString::fromUtf8( "plugin-data" ) || groupElement.tagName() == TQString::fromUtf8("custom-icons" ))
		{
			Kopete::ContactListElement::fromXML(contactElement);
		}
		contactElement = contactElement.nextSibling().toElement();
	}

	if( oldNameTracking )
	{
		/* if (displayNameSourceContact() )  <- doesn't work because the contact is only set up when all plugin are loaded (BUG 111956) */
		if ( !d->nameSourceCID.isEmpty() )
		{
// 			kdDebug(14010) << k_funcinfo << "Converting old name source" << endl;
			// even if the old tracking attributes exists, they could have been null, that means custom
				setDisplayNameSource(SourceContact);
		}
		else
		{
			// lets do the best conversion for the old name tracking
			// if the custom display name is the same as tdeabc name, set the source to tdeabc
			if ( !d->metaContactId.isEmpty() && ( d->displayName == nameFromKABC(d->metaContactId)) )
				setDisplayNameSource(SourceKABC);
			else
				setDisplayNameSource(SourceCustom);
		}
	}

	if ( oldPhotoTracking )
	{
// 		kdDebug(14010) << k_funcinfo << "Converting old photo source" << endl;
		if ( !d->photoSourceCID.isEmpty() )   
		{
			setPhotoSource(SourceContact);
		}
		else
		{
			if ( !d->metaContactId.isEmpty() && !photoFromKABC(d->metaContactId).isNull())
				setPhotoSource(SourceKABC);
			else
				setPhotoSource(SourceCustom);
		}
	}
	
	// If a plugin is loaded, load data cached
	connect( Kopete::PluginManager::self(), TQT_SIGNAL( pluginLoaded(Kopete::Plugin*) ),
		this, TQT_SLOT( slotPluginLoaded(Kopete::Plugin*) ) );

	// All plugins are already loaded, call manually the contact setting slot.
	if( Kopete::PluginManager::self()->isAllPluginsLoaded() )
		slotAllPluginsLoaded();
	else
		// When all plugins are loaded, set the source contact.
		connect( Kopete::PluginManager::self(), TQT_SIGNAL( allPluginsLoaded() ), 
			this, TQT_SLOT( slotAllPluginsLoaded() ) );

	// track changes only works if ONE Contact is inside the MetaContact
//	if (d->contacts.count() > 1) // Does NOT work as intended
//		d->trackChildNameChanges=false;

// 	kdDebug(14010) << k_funcinfo << "END" << endl;
	return true;
}

TQString MetaContact::sourceToString(PropertySource source) const
{
	if ( source == SourceCustom )
		return TQString::fromUtf8("custom");
	else if ( source == SourceKABC )
		return TQString::fromUtf8("addressbook");
	else if ( source == SourceContact )
		return TQString::fromUtf8("contact");
	else // recovery
		return sourceToString(SourceCustom);
}

MetaContact::PropertySource MetaContact::stringToSource(const TQString &name) const
{
	if ( name == TQString::fromUtf8("custom") )
		return SourceCustom;
	else if ( name == TQString::fromUtf8("addressbook") )
		return SourceKABC;
	else if ( name == TQString::fromUtf8("contact") )
		return SourceContact;
	else // recovery
		return SourceCustom;
}

TQString MetaContact::addressBookField( Kopete::Plugin * /* p */, const TQString &app, const TQString & key ) const
{
	return d->addressBook[ app ][ key ];
}

void Kopete::MetaContact::setAddressBookField( Kopete::Plugin * /* p */, const TQString &app, const TQString &key, const TQString &value )
{
	d->addressBook[ app ][ key ] = value;
}

void MetaContact::slotPluginLoaded( Plugin *p )
{
	if( !p )
		return;

	TQMap<TQString, TQString> map= pluginData( p );
	if(!map.isEmpty())
	{
		p->deserialize(this,map);
	}
}

void MetaContact::slotAllPluginsLoaded()
{
	// Now that the plugins and subcontacts are loaded, set the source contact.
	setDisplayNameSourceContact( findContact( d->nameSourcePID, d->nameSourceAID, d->nameSourceCID) ); 
	setPhotoSourceContact( findContact( d->photoSourcePID, d->photoSourceAID, d->photoSourceCID) );
}

void MetaContact::slotUpdateAddressBookPicture()
{
	TDEABC::AddressBook* ab = KABCPersistence::self()->addressBook();
	TQString id = metaContactId();
	if ( !id.isEmpty() && !id.contains(':') )
	{
		TDEABC::Addressee theAddressee = ab->findByUid(id);
		if ( theAddressee.isEmpty() )
		{
			kdDebug( 14010 ) << k_funcinfo << "no TDEABC::Addressee found for ( " << id << " ) " << " in current address book" << endl;
		}
		else
		{
			TDEABC::Picture pic = theAddressee.photo();
			if ( pic.data().isNull() && pic.url().isEmpty() )
				pic = theAddressee.logo();

			d->tdeabcPicture.setPicture(pic);
		}
	}
}

bool MetaContact::isTemporary() const
{
	return d->temporary;
}

void MetaContact::setTemporary( bool isTemporary, Group *group )
{
	d->temporary = isTemporary;
	Group *temporaryGroup = Group::temporary();
	if ( d->temporary )
	{
		addToGroup (temporaryGroup);
		Group *g;
		for( g = d->groups.first(); g; g = d->groups.next() )
		{
			if(g != temporaryGroup)
				removeFromGroup(g);
		}
	}
	else
		moveToGroup(temporaryGroup, group ? group : Group::topLevel());
}

TQString MetaContact::metaContactId() const
{
	if(d->metaContactId.isEmpty())
	{
		Contact *c=d->contacts.first();
		if(!c)
			return TQString();
		return c->protocol()->pluginId()+TQString::fromUtf8(":")+c->account()->accountId()+TQString::fromUtf8(":") + c->contactId() ;
	}
	return d->metaContactId;
}

void MetaContact::setMetaContactId( const TQString& newMetaContactId )
{
	if(newMetaContactId == d->metaContactId)
		return;

	// 1) Check the Id is not already used by another contact
	// 2) cause a tdeabc write ( only in response to metacontactLVIProps calling this, or will
	//      write be called twice when creating a brand new MC? )
	// 3) What about changing from one valid tdeabc to another, are tdeabc fields removed?
	// 4) May be called with Null to remove an invalid tdeabc uid by KMC::toKABC()
	// 5) Is called when reading the saved contact list

	// Don't remove IM addresses from tdeabc if we are changing contacts; 
	// other programs may have written that data and depend on it
	d->metaContactId = newMetaContactId;
	KABCPersistence::self()->write( this );
	emit onlineStatusChanged( this, d->onlineStatus );
	emit persistentDataChanged();
}

bool MetaContact::isPhotoSyncedWithKABC() const
{
	return d->photoSyncedWithKABC;
}

void MetaContact::setPhotoSyncedWithKABC(bool b)
{
	d->photoSyncedWithKABC=b;
	if(b)
	{
		TQVariant newValue;
		
		switch( photoSource() )
		{
			case SourceContact:
			{
				Contact *source = photoSourceContact();
				if(source != 0L)
					newValue = source->property( Kopete::Global::Properties::self()->photo() ).value();
				break;
			}
			case SourceCustom:
			{
				if( !d->customPicture.isNull() )
					newValue = d->customPicture.path();
				break;
			}
			// Don't sync the photo with KABC if the source is KABC !
			default:
				return;
		}

		if ( !d->metaContactId.isEmpty() && !newValue.isNull())
		{
			TDEABC::Addressee theAddressee = KABCPersistence::self()->addressBook()->findByUid( metaContactId() );

			if ( !theAddressee.isEmpty() )
			{
				TQImage img;
				if(newValue.canCast( TQVariant::Image ))
					img=newValue.toImage();
				else if(newValue.canCast( TQVariant::Pixmap ))
					img=newValue.toPixmap().convertToImage();

				if(img.isNull())
				{
					// Some protocols like MSN save the photo as a url in
					// contact properties, we should not use this url
					// to sync with tdeabc but try first to embed the
					// photo data in the tdeabc addressee, because it could
					// be remote resource and the local url makes no sense
					TQImage fallBackImage = TQImage(newValue.toString());
					if(fallBackImage.isNull())
						theAddressee.setPhoto(newValue.toString());
					else
						theAddressee.setPhoto(fallBackImage);
				}
				else
					theAddressee.setPhoto(img);

				KABCPersistence::self()->addressBook()->insertAddressee(theAddressee);
				KABCPersistence::self()->writeAddressBook( theAddressee.resource() );
			}
		}
	}
}

TQPtrList<Contact> MetaContact::contacts() const
{
	return d->contacts;
}
} //END namespace Kopete

#include "kopetemetacontact.moc"

// vim: set noet ts=4 sts=4 sw=4: