/*
    kopetecontact.cpp - Kopete Contact

    Copyright (c) 2002-2004 by Duncan Mac-Vicar Prett <duncan@kde.org>
    Copyright (c) 2002-2003 by Martijn Klingens       <klingens@kde.org>
    Copyright (c) 2002-2004 by Olivier Goffart        <ogoffart @tiscalinet.be>

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

#include <qapplication.h>

#include <kdebug.h>

#include <kdeversion.h>
#include <kinputdialog.h>

#include <kabcpersistence.h>
#include <kdialogbase.h>
#include <klocale.h>
#include <kpopupmenu.h>
#include <kmessagebox.h>
#include <klistviewsearchline.h>

#include "kopetecontactlist.h"
#include "kopeteglobal.h"
#include "kopeteuiglobal.h"
#include "kopeteprotocol.h"
#include "kopeteaccount.h"
#include "kopetestdaction.h"
#include "kopetechatsession.h"
#include "kopeteview.h"
#include "kopetemetacontact.h"
#include "kopeteprefs.h"
#include "metacontactselectorwidget.h"
#include "kopeteemoticons.h"

//For the moving to another metacontact dialog
#include <qlabel.h>
#include <qimage.h>
#include <qmime.h>
#include <qvbox.h>
#include <klistview.h>
#include <qcheckbox.h>
#include <qwhatsthis.h>
#include <qstylesheet.h>

namespace Kopete {

struct Contact::Private
{
public:
	bool fileCapable;

	OnlineStatus onlineStatus;
	Account *account;

	MetaContact *metaContact;

	QString contactId;
	QString icon;

	QTime idleTimer;
	unsigned long int idleTime;

	Kopete::ContactProperty::Map properties;

};

Contact::Contact( Account *account, const QString &contactId,
	MetaContact *parent, const QString &icon )
	: QObject( parent )
{
	d = new Private;

	//kdDebug( 14010 ) << k_funcinfo << "Creating contact with id " << contactId << endl;

	d->contactId = contactId;
	d->metaContact = parent;
	d->fileCapable = false;
	d->account = account;
	d->idleTime = 0;
	d->icon = icon;

	// If can happend that a MetaContact may be used without a account
	// (ex: for unit tests or chat window style preview)
	if ( account )
	{
		account->registerContact( this );
		connect( account, SIGNAL( isConnectedChanged() ), SLOT( slotAccountIsConnectedChanged() ) );
	}

	// Need to check this because myself() may have no parent
	// Maybe too the metaContact doesn't have a valid protocol() 
	// (ex: for unit tests or chat window style preview)
	if( parent && protocol() )
	{
		connect( parent, SIGNAL( aboutToSave( Kopete::MetaContact * ) ),
			protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) );

		parent->addContact( this );
	}

	
}

Contact::~Contact()
{
	//kdDebug(14010) << k_funcinfo << endl;
	emit( contactDestroyed( this ) );
	delete d;
}



OnlineStatus Contact::onlineStatus() const
{
	if ( this == account()->myself() || account()->isConnected() )
		return d->onlineStatus;
	else
		return protocol()->accountOfflineStatus();
}

void Contact::setOnlineStatus( const OnlineStatus &status )
{
	if( status == d->onlineStatus )
		return;

	OnlineStatus oldStatus = d->onlineStatus;
	d->onlineStatus = status;

	Kopete::Global::Properties *globalProps = Kopete::Global::Properties::self();

	// Contact changed from Offline to another status
	if( oldStatus.status() == OnlineStatus::Offline &&
		status.status() != OnlineStatus::Offline )
	{
		setProperty( globalProps->onlineSince(), QDateTime::currentDateTime() );
		/*kdDebug(14010) << k_funcinfo << "REMOVING lastSeen property for " <<
			d->displayName << endl;*/
		removeProperty( globalProps->lastSeen() );
	}
	else if( oldStatus.status() != OnlineStatus::Offline &&
		oldStatus.status() != OnlineStatus::Unknown &&
		status.status() == OnlineStatus::Offline ) // Contact went back offline
	{
		removeProperty( globalProps->onlineSince() );
		/*kdDebug(14010) << k_funcinfo << "SETTING lastSeen property for " <<
			d->displayName << endl;*/
		setProperty( globalProps->lastSeen(), QDateTime::currentDateTime() );
	}

	if ( this == account()->myself() || account()->isConnected() )
		emit onlineStatusChanged( this, status, oldStatus );
}

void Contact::slotAccountIsConnectedChanged()
{
	if ( this == account()->myself() )
		return;

	if ( account()->isConnected() )
		emit onlineStatusChanged( this, d->onlineStatus, protocol()->accountOfflineStatus() );
	else
		emit onlineStatusChanged( this, protocol()->accountOfflineStatus(), d->onlineStatus );
}


void Contact::sendFile( const KURL &, const QString &, uint )
{
	kdWarning( 14010 ) << k_funcinfo << "Plugin "
		<< protocol()->pluginId() << " has enabled file sending, "
		<< "but didn't implement it!" << endl;
}

void Contact::slotAddContact()
{
	if( metaContact() )
	{
		metaContact()->setTemporary( false );
		ContactList::self()->addMetaContact( metaContact() );
	}
}

KPopupMenu* Contact::popupMenu( ChatSession *manager )
{
	// Build the menu
	KPopupMenu *menu = new KPopupMenu();

	// insert title
	QString titleText;
	QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
	if( nick.isEmpty() )
		titleText = QString::fromLatin1( "%1 (%2)" ).arg( contactId(), onlineStatus().description() );
	else
		titleText = QString::fromLatin1( "%1 <%2> (%3)" ).arg( nick, contactId(), onlineStatus().description() );
	menu->insertTitle( titleText );

	if( metaContact() && metaContact()->isTemporary() && contactId() != account()->myself()->contactId() )
	{
		KAction *actionAddContact = new KAction( i18n( "&Add to Your Contact List" ), QString::fromLatin1( "add_user" ),
		                                         0, this, SLOT( slotAddContact() ), menu, "actionAddContact" );
		actionAddContact->plug( menu );
		menu->insertSeparator();
	}

	// FIXME: After KDE 3.2 we should make isReachable do the isConnected call so it can be removed here - Martijn
	bool reach = account()->isConnected() && isReachable();
	bool myself = (this == account()->myself());

	KAction *actionSendMessage = KopeteStdAction::sendMessage( this, SLOT( sendMessage() ), menu, "actionSendMessage" );
	actionSendMessage->setEnabled( reach && !myself );
	actionSendMessage->plug( menu );

	KAction *actionChat = KopeteStdAction::chat( this, SLOT( startChat() ), menu, "actionChat" );
	actionChat->setEnabled( reach && !myself );
	actionChat->plug( menu );

	KAction *actionSendFile = KopeteStdAction::sendFile( this, SLOT( sendFile() ), menu, "actionSendFile" );
	actionSendFile->setEnabled( reach && d->fileCapable && !myself );
	actionSendFile->plug( menu );

	// Protocol specific options will go below this separator
	// through the use of the customContextMenuActions() function

	// Get the custom actions from the protocols ( pure virtual function )
	QPtrList<KAction> *customActions = customContextMenuActions( manager );
	if( customActions && !customActions->isEmpty() )
	{
		menu->insertSeparator();

		for( KAction *a = customActions->first(); a; a = customActions->next() )
			a->plug( menu );
	}
	delete customActions;

	menu->insertSeparator();

	if( metaContact() && !metaContact()->isTemporary() )
		KopeteStdAction::changeMetaContact( this, SLOT( changeMetaContact() ), menu, "actionChangeMetaContact" )->plug( menu );

	KopeteStdAction::contactInfo( this, SLOT( slotUserInfo() ), menu, "actionUserInfo" )->plug( menu );

#if 0 //this is not fully implemented yet (and doesn't work).  disable for now   - Olivier 2005-01-11
	if ( account()->isBlocked( d->contactId ) )
		KopeteStdAction::unblockContact( this, SLOT( slotUnblock() ), menu, "actionUnblockContact" )->plug( menu );
	else
		KopeteStdAction::blockContact( this, SLOT( slotBlock() ), menu, "actionBlockContact" )->plug( menu );
#endif

	if( metaContact() && !metaContact()->isTemporary() )
		KopeteStdAction::deleteContact( this, SLOT( slotDelete() ), menu, "actionDeleteContact" )->plug( menu );

	return menu;
}

void Contact::changeMetaContact()
{
	KDialogBase *moveDialog = new KDialogBase( Kopete::UI::Global::mainWidget(), "moveDialog", true, i18n( "Move Contact" ),
		KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok, true );

	QVBox *w = new QVBox( moveDialog );
	w->setSpacing( KDialog::spacingHint() );
	Kopete::UI::MetaContactSelectorWidget *selector = new Kopete::UI::MetaContactSelectorWidget(w);
	selector->setLabelMessage(i18n( "Select the meta contact to which you want to move this contact:" ));
	// exclude this metacontact as a target metacontact for the move
	selector->excludeMetaContact( metaContact() );
	QCheckBox *chkCreateNew = new QCheckBox( i18n( "Create a new metacontact for this contact" ), w );
	QWhatsThis::add( chkCreateNew , i18n( "If you select this option, a new metacontact will be created in the top-level group "
		"with the name of this contact and the contact will be moved to it." ) );
	QObject::connect( chkCreateNew , SIGNAL( toggled(bool) ) ,  selector , SLOT ( setDisabled(bool) ) ) ;

	moveDialog->setMainWidget(w);
	if( moveDialog->exec() == QDialog::Accepted )
	{
		Kopete::MetaContact *mc = selector->metaContact();
		if(chkCreateNew->isChecked())
		{
			mc=new Kopete::MetaContact();
			Kopete::ContactList::self()->addMetaContact(mc);
		}
		if( mc )
		{
			setMetaContact( mc );
		}
	}

	moveDialog->deleteLater();
}

void Contact::setMetaContact( MetaContact *m )
{
	MetaContact *old = d->metaContact;
	if(old==m) //that make no sens
		return;

	if( old )
	{
		int result=KMessageBox::No;
		if( old->isTemporary() )
			result=KMessageBox::Yes;
		else if( old->contacts().count()==1 )
		{ //only one contact, including this one, that mean the contact will be empty efter the move
			result = KMessageBox::questionYesNoCancel( Kopete::UI::Global::mainWidget(), i18n( "You are moving the contact `%1' to the meta contact `%2'.\n"
				"`%3' will be empty afterwards. Do you want to delete this contact?" )
					.arg(contactId(), m ? m->displayName() : QString::null, old->displayName())
				, i18n( "Move Contact" ), KStdGuiItem::del(), i18n( "&Keep" ) , QString::fromLatin1("delete_old_contact_when_move") );
			if(result==KMessageBox::Cancel)
				return;
		}
		old->removeContact( this );
		disconnect( old, SIGNAL( aboutToSave( Kopete::MetaContact * ) ),
			protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) );

		if(result==KMessageBox::Yes)
		{
			//remove the old metacontact.  (this delete the MC)
			ContactList::self()->removeMetaContact(old);
		}
		else
		{
			d->metaContact = m; //i am forced to do that now if i want the next line works
			//remove cached data for this protocol which will not be removed since we disconnected
			protocol()->slotMetaContactAboutToSave( old );
		}
	}

	d->metaContact = m;

	if( m )
	{
		m->addContact( this );
		m->insertChild( this );
		// it is necessary to call this write here, because MetaContact::addContact() does not differentiate
		// between adding completely new contacts (which should be written to kabc) and restoring upon restart
		// (where no write is needed).
		KABCPersistence::self()->write( m );
		connect( d->metaContact, SIGNAL( aboutToSave( Kopete::MetaContact * ) ),
		protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) );
	}
	sync();
}

void Contact::serialize( QMap<QString, QString> &/*serializedData*/,
	QMap<QString, QString> & /* addressBookData */ )
{
}


void Contact::serializeProperties(QMap<QString, QString> &serializedData)
{

	Kopete::ContactProperty::Map::ConstIterator it;// = d->properties.ConstIterator;
	for (it=d->properties.begin(); it != d->properties.end(); ++it)
	{
		if (!it.data().tmpl().persistent())
			continue;

		QVariant val = it.data().value();
		QString key = QString::fromLatin1("prop_%1_%2").arg(QString::fromLatin1(val.typeName()), it.key());

		serializedData[key] = val.toString();

	} // end for()
} // end serializeProperties()

void Contact::deserializeProperties(
	QMap<QString, QString> &serializedData )
{
	QMap<QString, QString>::ConstIterator it;
	for ( it=serializedData.begin(); it != serializedData.end(); ++it )
	{
		QString key = it.key();

		if ( !key.startsWith( QString::fromLatin1("prop_") ) ) // avoid parsing other serialized data
			continue;

		QStringList keyList = QStringList::split( QChar('_'), key, false );
		if( keyList.count() < 3 ) // invalid key, not enough parts in string "prop_X_Y"
			continue;

		key = keyList[2]; // overwrite key var with the real key name this property has
		QString type( keyList[1] ); // needed for QVariant casting

		QVariant variant( it.data() );
		if( !variant.cast(QVariant::nameToType(type.latin1())) )
		{
			kdDebug(14010) << k_funcinfo <<
				"Casting QVariant to needed type FAILED" <<
				"key=" << key << ", type=" << type << endl;
			continue;
		}

		Kopete::ContactPropertyTmpl tmpl = Kopete::Global::Properties::self()->tmpl(key);
		if( tmpl.isNull() )
		{
			kdDebug( 14010 ) << k_funcinfo << "no ContactPropertyTmpl defined for" \
				" key " << key << ", cannot restore persistent property" << endl;
			continue;
		}

		setProperty(tmpl, variant);
	} // end for()
}


bool Contact::isReachable()
{
	// The default implementation returns false when offline and true
	// otherwise. Subclass if you need more control over the process.
	return onlineStatus().status() != OnlineStatus::Offline;
}


void Contact::startChat()
{
	KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_chatwindow") );
	if(v)
		v->raise(true);
}

void Contact::sendMessage()
{
	KopeteView *v=manager( CanCreate )->view(true, QString::fromLatin1("kopete_emailwindow") );
	if(v)
		v->raise(true);
}

void Contact::execute()
{
	// FIXME: After KDE 3.2 remove the isConnected check and move it to isReachable - Martijn
	if ( account()->isConnected() && isReachable() )
	{
		KopeteView *v=manager( CanCreate )->view(true, KopetePrefs::prefs()->interfacePreference() );
		if(v)
			v->raise(true);
	}
	else
	{
		KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry,
			i18n( "This user is not reachable at the moment. Please try a protocol that supports offline sending, or wait "
			"until this user comes online." ), i18n( "User is Not Reachable" ) );
	}
}

void Contact::slotDelete()
{
	if ( KMessageBox::warningContinueCancel( Kopete::UI::Global::mainWidget(),
		i18n( "Are you sure you want to remove the contact  '%1' from your contact list?" ).
		arg( d->contactId ), i18n( "Remove Contact" ), KGuiItem(i18n("Remove"), QString::fromLatin1("delete_user") ),
		QString::fromLatin1("askRemoveContact"), KMessageBox::Notify | KMessageBox::Dangerous )
		== KMessageBox::Continue )
	{
		deleteContact();
	}
}

void Contact::deleteContact()
{
	// Default implementation simply deletes the contact
	deleteLater();
}


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

QString Contact::contactId() const
{
	return d->contactId;
}

Protocol * Contact::protocol() const
{
	return d->account ? d->account->protocol() : 0L;
}

Account * Contact::account() const
{
	return d->account;
}



void Contact::sync(unsigned int)
{
	/* Default implementation does nothing */
}

QString& Contact::icon() const
{
	return d->icon;
}

void Contact::setIcon( const QString& icon )
{
	d->icon = icon;
	return;
}

QPtrList<KAction> *Contact::customContextMenuActions()
{
	return 0L;
}

QPtrList<KAction> *Contact::customContextMenuActions( ChatSession * /* manager */ )
{
	return customContextMenuActions();
}


bool Contact::isOnline() const
{
	return onlineStatus().isDefinitelyOnline();
}


bool Contact::isFileCapable() const
{
	return d->fileCapable;
}

void Contact::setFileCapable( bool filecap )
{
	d->fileCapable = filecap;
}


bool Contact::canAcceptFiles() const
{
	return isOnline() && d->fileCapable;
}

unsigned long int Contact::idleTime() const
{
	if(d->idleTime==0)
		return 0;

	return d->idleTime+(d->idleTimer.elapsed()/1000);
}

void Contact::setIdleTime( unsigned long int t )
{
	bool idleChanged = false;
	if(d->idleTime != t)
		idleChanged = true;
	d->idleTime=t;
	if(t > 0)
		d->idleTimer.start();
//FIXME: if t == 0, idleTime() will now return garbage
//	else
//		d->idleTimer.stop();
	if(idleChanged)
		emit idleStateChanged(this);
}


QStringList Contact::properties() const
{
	return d->properties.keys();
}

bool Contact::hasProperty(const QString &key) const
{
	return d->properties.contains(key);
}

const ContactProperty &Contact::property(const QString &key) const
{
	if(hasProperty(key))
		return d->properties[key];
	else
		return Kopete::ContactProperty::null;
}

const Kopete::ContactProperty &Contact::property(
	const Kopete::ContactPropertyTmpl &tmpl) const
{
	if(hasProperty(tmpl.key()))
		return d->properties[tmpl.key()];
	else
		return Kopete::ContactProperty::null;
}


void Contact::setProperty(const Kopete::ContactPropertyTmpl &tmpl,
	const QVariant &value)
{
	if(tmpl.isNull() || tmpl.key().isEmpty())
	{
		kdDebug(14000) << k_funcinfo <<
			"No valid template for property passed!" << endl;
		return;
	}

	if(value.isNull() || value.canCast(QVariant::String) && value.toString().isEmpty())
	{
		removeProperty(tmpl);
	}
	else
	{
		QVariant oldValue = property(tmpl.key()).value();

		if(oldValue != value)
		{
			Kopete::ContactProperty prop(tmpl, value);
			d->properties.insert(tmpl.key(), prop, true);

			emit propertyChanged(this, tmpl.key(), oldValue, value);
		}
	}
}

void Contact::removeProperty(const Kopete::ContactPropertyTmpl &tmpl)
{
	if(!tmpl.isNull() && !tmpl.key().isEmpty())
	{

		QVariant oldValue = property(tmpl.key()).value();
		d->properties.remove(tmpl.key());
		emit propertyChanged(this, tmpl.key(), oldValue, QVariant());
	}
}


QString Contact::toolTip() const
{
	Kopete::ContactProperty p;
	QString tip;
	QStringList shownProps = KopetePrefs::prefs()->toolTipContents();

	// --------------------------------------------------------------------------
	// Fixed part of tooltip

	QString iconName = QString::fromLatin1("kopete-contact-icon:%1:%2:%3")
		.arg( KURL::encode_string( protocol()->pluginId() ),
				KURL::encode_string( account()->accountId() ),
				KURL::encode_string( contactId() ) );

	// TODO:  the nickname should be a configurable properties, like others. -Olivier
	QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
	if ( nick.isEmpty() )
	{
		tip = i18n( "<b>DISPLAY NAME</b><br><img src=\"%2\">&nbsp;CONTACT STATUS",
			"<b><nobr>%3</nobr></b><br><img src=\"%2\">&nbsp;%1" ).
			arg( Kopete::Message::escape( onlineStatus().description() ), iconName,
				Kopete::Message::escape( d->contactId ) );
	}
	else
	{
		tip = i18n( "<b>DISPLAY NAME</b> (CONTACT ID)<br><img src=\"%2\">&nbsp;CONTACT STATUS",
			"<nobr><b>%4</b> (%3)</nobr><br><img src=\"%2\">&nbsp;%1" ).
				arg( Kopete::Message::escape( onlineStatus().description() ), iconName,
					Kopete::Message::escape( contactId() ),
					Kopete::Emoticons::parseEmoticons( Kopete::Message::escape( nick ) ) );
	}

	// --------------------------------------------------------------------------
	// Configurable part of tooltip

	for(QStringList::Iterator it=shownProps.begin(); it!=shownProps.end(); ++it)
	{
		if((*it) == QString::fromLatin1("FormattedName"))
		{
			QString name = formattedName();
			if(!name.isEmpty())
			{
				tip += i18n("<br><b>Full Name:</b>&nbsp;FORMATTED NAME",
							"<br><b>Full Name:</b>&nbsp;<nobr>%1</nobr>").arg(QStyleSheet::escape(name));
			}
		}
		else if ((*it) == QString::fromLatin1("FormattedIdleTime"))
		{
			QString time = formattedIdleTime();
			if(!time.isEmpty())
			{
				tip += i18n("<br><b>Idle:</b>&nbsp;FORMATTED IDLE TIME",
					"<br><b>Idle:</b>&nbsp;<nobr>%1</nobr>").arg(time);
			}
		}
		else if ((*it) == QString::fromLatin1("homePage"))
		{
			QString url = property(*it).value().toString();
			if(!url.isEmpty())
			{
				tip += i18n("<br><b>Home Page:</b>&nbsp;FORMATTED URL",
					"<br><b>Home Page:</b>&nbsp;<a href=\"%1\"><nobr>%2</nobr></a>").
						arg( KURL::encode_string( url ), Kopete::Message::escape( QStyleSheet::escape(url) ) );
			}
		}
		else if ((*it) == QString::fromLatin1("awayMessage"))
		{
			QString awaymsg = property(*it).value().toString();
			if(!awaymsg.isEmpty())
			{
				tip += i18n("<br><b>Away Message:</b>&nbsp;FORMATTED AWAY MESSAGE",
							"<br><b>Away&nbsp;Message:</b>&nbsp;%1").arg ( Kopete::Emoticons::parseEmoticons( Kopete::Message::escape(awaymsg) ) );
			}
		}
		else
		{
			p = property(*it);
			if(!p.isNull())
			{
				QVariant val = p.value();
				QString valueText;

				switch(val.type())
				{
					case QVariant::DateTime:
						valueText = KGlobal::locale()->formatDateTime(val.toDateTime());
						valueText = Kopete::Message::escape( valueText );
						break;
					case QVariant::Date:
						valueText = KGlobal::locale()->formatDate(val.toDate());
						valueText = Kopete::Message::escape( valueText );
						break;
					case QVariant::Time:
						valueText = KGlobal::locale()->formatTime(val.toTime());
						valueText = Kopete::Message::escape( valueText );
						break;
					default:
						if( p.isRichText() )
						{
							valueText = val.toString();
						}
						else
						{
							valueText = Kopete::Message::escape( val.toString() );
						}
				}

				tip += i18n("<br><b>PROPERTY LABEL:</b>&nbsp;PROPERTY VALUE",
					"<br><nobr><b>%2:</b></nobr>&nbsp;%1").
						arg( valueText, QStyleSheet::escape(p.tmpl().label()) );
			}
		}
	}

	return tip;
}

QString Kopete::Contact::formattedName() const
{
	if( hasProperty(QString::fromLatin1("FormattedName")) )
		return property(QString::fromLatin1("FormattedName")).value().toString();

	QString ret;
	Kopete::ContactProperty first, last;

	first = property(QString::fromLatin1("firstName"));
	last = property(QString::fromLatin1("lastName"));
	if(!first.isNull())
	{
		if(!last.isNull()) // contact has both first and last name
		{
			ret = i18n("firstName lastName", "%2 %1")
				.arg(last.value().toString())
				.arg(first.value().toString());
		}
		else // only first name set
		{
			ret = first.value().toString();
		}
	}
	else if(!last.isNull()) // only last name set
	{
		ret = last.value().toString();
	}

	return ret;
}

QString Kopete::Contact::formattedIdleTime() const
{
	QString ret;
	unsigned long int leftTime = idleTime();

	if ( leftTime > 0 )
	{	// FIXME: duplicated from code in kopetecontactlistview.cpp
		unsigned long int days, hours, mins, secs;

		days = leftTime / ( 60*60*24 );
		leftTime = leftTime % ( 60*60*24 );
		hours = leftTime / ( 60*60 );
		leftTime = leftTime % ( 60*60 );
		mins = leftTime / 60;
		secs = leftTime % 60;

		if ( days != 0 )
		{
			ret = i18n( "<days>d <hours>h <minutes>m <seconds>s",
				"%4d %3h %2m %1s" )
				.arg( secs )
				.arg( mins )
				.arg( hours )
				.arg( days );
		}
		else if ( hours != 0 )
		{
			ret = i18n( "<hours>h <minutes>m <seconds>s", "%3h %2m %1s" )
				.arg( secs )
				.arg( mins )
				.arg( hours );
		}
		else
		{
			ret = i18n( "<minutes>m <seconds>s", "%2m %1s" )
				.arg( secs )
				.arg( mins );
		}
	}
	return ret;
}


void Kopete::Contact::slotBlock()
{
	account()->block( d->contactId );
}

void Kopete::Contact::slotUnblock()
{
	account()->unblock( d->contactId );
}

void Kopete::Contact::setNickName( const QString &name )
{
	setProperty( Kopete::Global::Properties::self()->nickName(), name );
}

QString Kopete::Contact::nickName() const
{
	QString nick = property( Kopete::Global::Properties::self()->nickName() ).value().toString();
	if( !nick.isEmpty() )
		return nick;

	return contactId();
}

void Contact::virtual_hook( uint , void * )
{ }


} //END namespace Kopete


#include "kopetecontact.moc"