/*    
	kimproxy.cpp
	
	IM service library for KDE
	
	Copyright (c) 2004 Will Stephenson   <lists@stevello.free-online.co.uk>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <qglobal.h>
#include <qpixmapcache.h>
#include <dcopclient.h>
#include <kapplication.h>
#include <kdcopservicestarter.h> 
#include <kdebug.h>
#include <kmessagebox.h>
#include <ksimpleconfig.h>
#include <kiconloader.h>
#include <kservice.h>
#include <kservicetype.h>

#include "kimiface_stub.h"

#include "kimproxy.h"

static KStaticDeleter<KIMProxy> _staticDeleter;

KIMProxy * KIMProxy::s_instance = 0L;

struct AppPresenceCurrent
{
	QCString appId;
	int presence;
};

class ContactPresenceListCurrent : public QValueList<AppPresenceCurrent>
{
	public:
		// return value indicates if the supplied parameter was better than any existing presence
		bool update( const AppPresenceCurrent );
		AppPresenceCurrent best();
};


struct KIMProxy::Private
{
	DCOPClient * dc;
	// list of the strings in use by KIMIface
	QStringList presence_strings;
	// list of the icon names in use by KIMIface
	QStringList presence_icons;
	// map of presences
	PresenceStringMap presence_map;
};

bool ContactPresenceListCurrent::update( AppPresenceCurrent ap )
{
	if ( isEmpty() )
	{
		append( ap );
		return true;
	}
	
	bool bestChanged = false;
	AppPresenceCurrent best;
	best.presence = -1;
	ContactPresenceListCurrent::iterator it = begin();
	const ContactPresenceListCurrent::iterator itEnd = end();
	ContactPresenceListCurrent::iterator existing = itEnd;

	while ( it != itEnd )
	{
		if ( (*it).presence > best.presence )
			best = (*it);
		if ( (*it).appId == ap.appId )
			existing = it;
		++it;
	}
	
	if ( ap.presence > best.presence ||
      best.appId == ap.appId )
		bestChanged = true;
	
	if ( existing != itEnd )
	{
		remove( existing );
		append( ap );
	}
	return bestChanged;
}

AppPresenceCurrent ContactPresenceListCurrent::best()
{
	AppPresenceCurrent best;
	best.presence = -1;
	ContactPresenceListCurrent::iterator it = begin();
	const ContactPresenceListCurrent::iterator itEnd = end();
	while ( it != itEnd )
	{
		if ( (*it).presence > best.presence )
			best = (*it);
		++it;
	}
	// if it's still -1 here, we have no presence data, so we return Unknown
	if ( best.presence == -1 )
		best.presence = 0;
	return best;
}

// int bestPresence( AppPresence* ap )
// {
// 	Q_ASSERT( ap );
// 	AppPresence::const_iterator it;
// 	it = ap->begin();
// 	int best = 0; // unknown
// 	if ( it != ap->end() )
// 	{
// 		best = it.data();
// 		++it;
// 		for ( ; it != ap->end(); ++it )
// 		{
// 			if ( it.data() > best )
// 				best = it.data(); 
// 		}
// 	}
// 	return best;
// }
// 
// QCString bestAppId( AppPresence* ap )
// {
// 	Q_ASSERT( ap );
// 	AppPresence::const_iterator it;
// 	QCString bestAppId;
// 	it = ap->begin();
// 	if ( it != ap->end() )
// 	{
// 		int best = it.data();
// 		bestAppId = it.key();
// 		++it;
// 		for ( ; it != ap->end(); ++it )
// 		{
// 			if ( it.data() > best )
// 			{
// 				best = it.data();
// 				bestAppId = it.key();
// 			}
// 		}
// 	}
// 	return bestAppId;
// }

KIMProxy * KIMProxy::instance( DCOPClient * client ) 
{
	if ( client )
	{
		if ( !s_instance )
			_staticDeleter.setObject( s_instance, new KIMProxy( client ) );
		return s_instance;
	}
	else
		return 0L;
}

KIMProxy::KIMProxy( DCOPClient* dc ) : DCOPObject( "KIMProxyIface" ), QObject(), d( new Private )
{
	m_im_client_stubs.setAutoDelete( true );

	d->dc = dc;
	m_initialized = false;
	connect( d->dc, SIGNAL( applicationRemoved( const QCString& ) ) , this, SLOT( unregisteredFromDCOP( const QCString& ) ) );
	connect( d->dc, SIGNAL( applicationRegistered( const QCString& ) ) , this, SLOT( registeredToDCOP( const QCString& ) ) );
	d->dc->setNotifications( true );

	d->presence_strings.append( "Unknown" );
	d->presence_strings.append( "Offline" );
	d->presence_strings.append( "Connecting" );
	d->presence_strings.append( "Away" );
	d->presence_strings.append( "Online" );
	
	d->presence_icons.append( "presence_unknown" );
	d->presence_icons.append( "presence_offline" );
	d->presence_icons.append( "presence_connecting" );
	d->presence_icons.append( "presence_away" );
	d->presence_icons.append( "presence_online" );
	
	//QCString senderApp = "Kopete";
	//QCString senderObjectId = "KIMIface";
	QCString method = "contactPresenceChanged( QString, QCString, int )";
	//QCString receiverObjectId = "KIMProxyIface";
	
	// FIXME: make this work when the sender object id is set to KIMIFace
	if ( !connectDCOPSignal( 0, 0, method, method, false ) )
		KMessageBox::information( 0, QString( "Couldn't connect DCOP signal.\nWon't receive any status notifications!" ) );
}

KIMProxy::~KIMProxy( )
{
	//d->dc->setNotifications( false );
}

bool KIMProxy::initialize()
{
	if ( !m_initialized )
	{
		m_initialized = true; // we should only do this once, as registeredToDCOP() will catch any new starts
		// So there is no error from a failed query when using kdelibs 3.2, which don't have this servicetype
		if ( KServiceType::serviceType( IM_SERVICE_TYPE ) ) 
		{
			//kdDebug( 790 ) << k_funcinfo << endl;
			QCString dcopObjectId = "KIMIface";
	
			// see what apps implementing our service type are out there
			KService::List offers = KServiceType::offers( IM_SERVICE_TYPE );
			KService::List::iterator offer;
			typedef QValueList<QCString> QCStringList;
			QCStringList registeredApps = d->dc->registeredApplications();
			QCStringList::iterator app;
			const QCStringList::iterator end = registeredApps.end();
			// for each registered app
			for ( app = registeredApps.begin(); app != end; ++app )
			{
				//kdDebug( 790 ) << " considering: " << *app << endl;
				//for each offer
				for ( offer = offers.begin(); offer != offers.end(); ++offer )
				{
					QCString dcopService = (*offer)->property("X-DCOP-ServiceName").toString().latin1();
					if ( !dcopService.isEmpty() )
					{
						//kdDebug( 790 ) << " is it: " << dcopService << "?" << endl;
						// get the application name ( minus any process ID )
						QCString instanceName =  (*app).left( dcopService.length() );
						// if the application implements the dcop service, add it 
						if ( instanceName == dcopService )
						{
							m_apps_available = true;
							//kdDebug( 790 ) << " app name: " << (*offer)->name() << ", has instance " << *app << ", dcopService: " << dcopService << endl;
							if ( !m_im_client_stubs.find( dcopService ) )
							{
								kdDebug( 790 ) << "App " << *app << ", dcopObjectId " << dcopObjectId << " found, using it for presence info." << endl;
								m_im_client_stubs.insert( *app, new KIMIface_stub( d->dc, *app, dcopObjectId ) );
								pollApp( *app );
							}
						}
					}
				}
			}
		}
	}
	return !m_im_client_stubs.isEmpty();
}

void KIMProxy::registeredToDCOP( const QCString& appId )
{
	//kdDebug( 790 ) << k_funcinfo << " appId '" << appId << "'" << endl;
	// check that appId implements our service
	// if the appId ends with a number, i.e. a pid like in foobar-12345,
	if ( appId.isEmpty() )
		return;

	bool newApp = false;
	// get an up to date list of offers in case a new app was installed
	// and check each of the offers that implement the service type we're looking for,
	// to see if any of them are the app that just registered
	const KService::List offers = KServiceType::offers( IM_SERVICE_TYPE );
	KService::List::const_iterator it;
	for ( it = offers.begin(); it != offers.end(); ++it )
	{
		QCString dcopObjectId = "KIMIface";
		QCString dcopService = (*it)->property("X-DCOP-ServiceName").toString().latin1();
		if ( appId.left( dcopService.length() ) == dcopService )
		{
			// if it's not already known, insert it
			if ( !m_im_client_stubs.find( appId ) )
			{
				newApp = true;
				kdDebug( 790 ) << "App: " << appId << ", dcopService: " << dcopService << " started, using it for presence info."<< endl;
				m_im_client_stubs.insert( appId, new KIMIface_stub( d->dc, appId, dcopObjectId ) );
			}
		}
		//else
		//	kdDebug( 790 ) << "App doesn't implement our ServiceType" << endl;
	}
	//if ( newApp )
	//	emit sigPresenceInfoExpired();
}

void KIMProxy::unregisteredFromDCOP( const QCString& appId )
{
	//kdDebug( 790 ) << k_funcinfo << appId << endl;
	if ( m_im_client_stubs.find( appId ) )
	{
		kdDebug( 790 ) << appId << " quit, removing its presence info." << endl;
		
		PresenceStringMap::Iterator it = d->presence_map.begin();
		const PresenceStringMap::Iterator end = d->presence_map.end();
		for ( ; it != end; ++it )
		{
			ContactPresenceListCurrent list = it.data();
			ContactPresenceListCurrent::iterator cpIt = list.begin();
			while( cpIt != list.end() )
			{
				ContactPresenceListCurrent::iterator gone = cpIt++;
				if ( (*gone).appId == appId )
				{
					list.remove( gone );
				}
			}
		}
		m_im_client_stubs.remove( appId );
		emit sigPresenceInfoExpired();
	}
}

void KIMProxy::contactPresenceChanged( QString uid, QCString appId, int presence )
{
	// update the presence map
	//kdDebug( 790 ) << k_funcinfo << "uid: " << uid << " appId: " << appId << " presence " << presence << endl;
	ContactPresenceListCurrent current;
	current = d->presence_map[ uid ];
  //kdDebug( 790 ) << "current best presence from : " << current.best().appId << " is: " << current.best().presence << endl;
	AppPresenceCurrent newPresence;
	newPresence.appId = appId;
	newPresence.presence = presence;

	if ( current.update( newPresence ) )
	{
		d->presence_map.insert( uid, current );
		emit sigContactPresenceChanged( uid );
	}
}

int KIMProxy::presenceNumeric( const QString& uid )
{
	AppPresenceCurrent ap;
	ap.presence = 0;
	if ( initialize() )
	{
		ContactPresenceListCurrent presence = d->presence_map[ uid ];
		ap = presence.best();
	}
	return ap.presence;
}

QString KIMProxy::presenceString( const QString& uid )
{
	AppPresenceCurrent ap;
	ap.presence = 0;
	if ( initialize() )
	{
		ContactPresenceListCurrent presence = d->presence_map[ uid ];
		ap = presence.best();
	}
	if ( ap.appId.isEmpty() )
		return QString::null;
	else
		return d->presence_strings[ ap.presence ];
}

QPixmap KIMProxy::presenceIcon( const QString& uid )
{
	AppPresenceCurrent ap;
	ap.presence = 0;
	if ( initialize() )
	{
		ContactPresenceListCurrent presence = d->presence_map[ uid ];
		ap = presence.best();
	}
	if ( ap.appId.isEmpty() )
	{
		//kdDebug( 790 ) << k_funcinfo << "returning a null QPixmap because we were asked for an icon for a uid we know nothing about" << endl;
		return QPixmap();
	}
	else
	{
		//kdDebug( 790 ) << k_funcinfo << "returning this: " << d->presence_icons[ ap.presence ] << endl;
		return SmallIcon( d->presence_icons[ ap.presence ]);
	}
}

QStringList KIMProxy::allContacts()
{
	QStringList value = d->presence_map.keys();
	return value;
}

QStringList KIMProxy::reachableContacts()
{
	QStringList value;
	
	if ( initialize() )
	{
		QDictIterator<KIMIface_stub> it( m_im_client_stubs );
		for ( ; it.current(); ++it )
		{
			value += it.current()->reachableContacts( );
		}
	}
	return value;
}

QStringList KIMProxy::onlineContacts()
{
	QStringList value;
	PresenceStringMap::iterator it = d->presence_map.begin();
	const PresenceStringMap::iterator end= d->presence_map.end();
	for ( ; it != end; ++it )
		if ( it.data().best().presence > 2 /*Better than Connecting, ie Away or Online*/ )
			value.append( it.key() );
		
	return value;
}

QStringList KIMProxy::fileTransferContacts()
{
	QStringList value;
	
	if ( initialize() )
	{
		QDictIterator<KIMIface_stub> it( m_im_client_stubs );
		for ( ; it.current(); ++it )
		{
			value += it.current()->fileTransferContacts( );
		}
	}
	return value;
}

bool KIMProxy::isPresent( const QString& uid )
{
	return ( !d->presence_map[ uid ].isEmpty() );
}

QString KIMProxy::displayName( const QString& uid )
{
	QString name;
	if ( initialize() )
	{
		if ( KIMIface_stub* s = stubForUid( uid ) )
			name = s->displayName( uid );
	}
	//kdDebug( 790 ) << k_funcinfo << name << endl;
    return name;
}

bool KIMProxy::canReceiveFiles( const QString & uid )
{
	if ( initialize() )
	{
		if ( KIMIface_stub* s = stubForUid( uid ) )
			return s->canReceiveFiles( uid );
	}
	return false;
}

bool KIMProxy::canRespond( const QString & uid )
{
	if ( initialize() )
	{
		if ( KIMIface_stub* s = stubForUid( uid ) )
			return s->canRespond( uid );
	}
	return false;
}

QString KIMProxy::context( const QString & uid )
{
	if ( initialize() )
	{
		if ( KIMIface_stub* s = stubForUid( uid ) )
			return s->context( uid );
	}
	return QString::null;
}
	
void KIMProxy::chatWithContact( const QString& uid )
{
	if ( initialize() )
	{
		if ( KIMIface_stub* s = stubForUid( uid ) )
		{
			kapp->updateRemoteUserTimestamp( s->app() );
			s->chatWithContact( uid );
		}
	}
	return;
}

void KIMProxy::messageContact( const QString& uid, const QString& message )
{
	if ( initialize() )
	{
		if ( KIMIface_stub* s = stubForUid( uid ) )
		{
			kapp->updateRemoteUserTimestamp( s->app() );
			s->messageContact( uid, message );
		}
	}
	return;
}

void KIMProxy::sendFile(const QString &uid, const KURL &sourceURL, const QString &altFileName, uint fileSize )
{
	if ( initialize() )
	{
		QDictIterator<KIMIface_stub> it( m_im_client_stubs );
		for ( ; it.current(); ++it )
		{
			if ( it.current()->canReceiveFiles( uid ) )
			{
				kapp->updateRemoteUserTimestamp( it.current()->app() );
				it.current()->sendFile( uid, sourceURL, altFileName, fileSize );
				break;
			}
		}	
	}
	return;
}

bool KIMProxy::addContact( const QString &contactId, const QString &protocol )
{
	if ( initialize() )
	{
		if ( KIMIface_stub* s = stubForProtocol( protocol ) )
		return s->addContact( contactId, protocol );
	}
	return false;
}

QString KIMProxy::locate( const QString & contactId, const QString & protocol )
{
	if ( initialize() )
	{
		if ( KIMIface_stub* s = stubForProtocol( protocol ) )
		return s->locate( contactId, protocol );
	}
	return QString::null;
}

bool KIMProxy::imAppsAvailable()
{
	return ( !m_im_client_stubs.isEmpty() );
}

bool KIMProxy::startPreferredApp()
{
	QString preferences = QString("[X-DCOP-ServiceName] = '%1'").arg( preferredApp() );
	// start/find an instance of DCOP/InstantMessenger
	QString error;
	QCString dcopService;
	// Get a preferred IM client.
	// The app will notify itself to us using registeredToDCOP, so we don't need to record a stub for it here
	// FIXME: error in preferences, see debug output
	preferences = QString::null;
	int result = KDCOPServiceStarter::self()->findServiceFor( IM_SERVICE_TYPE, QString::null, preferences, &error, &dcopService );

	kdDebug( 790 ) << k_funcinfo << "error was: " << error << ", dcopService: " << dcopService << endl;

	return ( result == 0 );
}


void KIMProxy::pollAll( const QString &uid )
{
/*	// We only need to call this function if we don't have any data at all
	// otherwise, the data will be kept fresh by received presence change
	// DCOP signals
	if ( !d->presence_map.contains( uid ) )
	{
		AppPresence *presence = new AppPresence();
		// record current presence from known clients
		QDictIterator<KIMIface_stub> it( m_im_client_stubs );
		for ( ; it.current(); ++it )
		{
			presence->insert( it.currentKey().ascii(), it.current()->presenceStatus( uid ) ); // m_im_client_stubs has qstring keys...
		}
		d->presence_map.insert( uid, presence );
	}*/
}

void KIMProxy::pollApp( const QCString & appId )
{
	//kdDebug( 790 ) << k_funcinfo << endl;
	KIMIface_stub * appStub = m_im_client_stubs[ appId ];
	QStringList contacts = m_im_client_stubs[ appId ]->allContacts();
	QStringList::iterator it = contacts.begin();
	QStringList::iterator end = contacts.end();
	for ( ; it != end; ++it )
	{
		ContactPresenceListCurrent current = d->presence_map[ *it ];
		AppPresenceCurrent ap;
		ap.appId = appId;
		ap.presence = appStub->presenceStatus( *it );
		current.append( ap );

		d->presence_map.insert( *it, current );
		if ( current.update( ap ) )
			emit sigContactPresenceChanged( *it );
		//kdDebug( 790 ) << " uid: " << *it << " presence: " << ap.presence << endl;
	}
}

KIMIface_stub * KIMProxy::stubForUid( const QString &uid )
{
	// get best appPresence
	AppPresenceCurrent ap = d->presence_map[ uid ].best();
	// look up the presence string from that app
		return m_im_client_stubs.find( ap.appId );
}

KIMIface_stub * KIMProxy::stubForProtocol( const QString &protocol)
{
	KIMIface_stub * app;
	// see if the preferred client supports this protocol
	QString preferred = preferredApp();
	if ( ( app = m_im_client_stubs.find( preferred ) ) )
	{
		if ( app->protocols().grep( protocol ).count() > 0 )
			return app;
	}
	// preferred doesn't do this protocol, try the first of the others that says it does
	QDictIterator<KIMIface_stub> it( m_im_client_stubs );
	for ( ; it.current(); ++it )
	{
		if ( it.current()->protocols().grep( protocol ).count() > 0 )
			return it.current();
	}	
	return 0L;
}

QString KIMProxy::preferredApp()
{
	KConfig *store = new KSimpleConfig( IM_CLIENT_PREFERENCES_FILE );
	store->setGroup( IM_CLIENT_PREFERENCES_SECTION );
	QString preferredApp = store->readEntry( IM_CLIENT_PREFERENCES_ENTRY );
	//kdDebug( 790 ) << k_funcinfo << "found preferred app: " << preferredApp << endl;
	return preferredApp;
}	

#include "kimproxy.moc"
