/*
    jabbercapabilitiesmanager.cpp - Manage entity capabilities(JEP-0115).

    Copyright (c) 2006      by Michaƫl Larouche     <michael.larouche@kdemail.net>

    Kopete    (c) 2001-2006 by the Kopete developers <kopete-devel@kde.org>

    Imported from caps.cpp from Psi:
    Copyright (C) 2005  Remko Troncon

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

#include <tqstring.h>
#include <tqstringlist.h>
#include <tqtimer.h>
#include <tqpair.h>
#include <tqdom.h>
#include <tqtextstream.h>

#include <kstandarddirs.h>
#include <kdebug.h>

#include <xmpp_tasks.h>

#include "jabberaccount.h"
#include "jabberprotocol.h"

using namespace XMPP;

//BEGIN Capabilities
JabberCapabilitiesManager::Capabilities::Capabilities()
{}

JabberCapabilitiesManager::Capabilities::Capabilities(const TQString& node, const TQString& version, const TQString& extensions) 
	: m_node(node), m_version(version), m_extensions(extensions) 
{}

const TQString& JabberCapabilitiesManager::Capabilities::node() const 
{ 
	return m_node; 
}

const TQString& JabberCapabilitiesManager::Capabilities::version() const 
{ 
	return m_version; 
}

const TQString& JabberCapabilitiesManager::Capabilities::extensions() const 
{ 
	return m_extensions; 
}

JabberCapabilitiesManager::CapabilitiesList JabberCapabilitiesManager::Capabilities::flatten() const 
{
	CapabilitiesList capsList;
	capsList.append( Capabilities(node(), version(), version()) );

	TQStringList extensionList = TQStringList::split(" ",extensions());
	TQStringList::ConstIterator it, itEnd = extensionList.constEnd();
	for(it = extensionList.constBegin(); it != itEnd; ++it)
	{
		capsList.append( Capabilities(node(),version(),*it) );
	}

	return capsList;
}

bool JabberCapabilitiesManager::Capabilities::operator==(const Capabilities &other) const 
{
	return (node() == other.node() && version() == other.version() && extensions() == other.extensions());
}

bool JabberCapabilitiesManager::Capabilities::operator!=(const Capabilities &other) const 
{
	return !((*this) == other);
}

bool JabberCapabilitiesManager::Capabilities::operator<(const Capabilities &other) const 
{
	return (node() != other.node() ? node() < other.node() :
			(version() != other.version() ? version() < other.version() : 
			 extensions() < other.extensions()));
}
//END Capabilities

//BEGIN CapabilitiesInformation
JabberCapabilitiesManager::CapabilitiesInformation::CapabilitiesInformation() 
	: m_discovered(false), m_pendingRequests(0)
{
	updateLastSeen();
}

const TQStringList& JabberCapabilitiesManager::CapabilitiesInformation::features() const
{
	return m_features;
}

const DiscoItem::Identities& JabberCapabilitiesManager::CapabilitiesInformation::identities() const
{
	return m_identities;
}

TQStringList JabberCapabilitiesManager::CapabilitiesInformation::jids() const
{
	TQStringList jids;
	
	TQValueList<TQPair<TQString,JabberAccount*> >::ConstIterator it = m_jids.constBegin(), itEnd = m_jids.constEnd();
	for( ; it != itEnd; ++it) 
	{
		TQString jid( (*it).first );
		if( !jids.contains(jid) )
			jids.push_back(jid);
	}

	return jids;
}

bool JabberCapabilitiesManager::CapabilitiesInformation::discovered() const
{
	return m_discovered;
}

int JabberCapabilitiesManager::CapabilitiesInformation::pendingRequests() const
{
	return m_pendingRequests;
}

void JabberCapabilitiesManager::CapabilitiesInformation::reset()
{
	m_features.clear();
	m_identities.clear();
	m_discovered = false;
}

void JabberCapabilitiesManager::CapabilitiesInformation::removeAccount(JabberAccount *account)
{
	TQValueList<TQPair<TQString,JabberAccount*> >::Iterator it = m_jids.begin();
	while( it != m_jids.end() ) 
	{
		if( (*it).second == account) 
		{
			TQValueList<TQPair<TQString,JabberAccount*> >::Iterator otherIt = it;
			it++;
			m_jids.remove(otherIt);
		}
		else 
		{
			it++;
		}
	}
}

void JabberCapabilitiesManager::CapabilitiesInformation::addJid(const Jid& jid, JabberAccount* account)
{
	TQPair<TQString,JabberAccount*> jidAccountPair(jid.full(),account);

	if( !m_jids.contains(jidAccountPair) ) 
	{
		m_jids.push_back(jidAccountPair);
		updateLastSeen();
	}
}

void JabberCapabilitiesManager::CapabilitiesInformation::removeJid(const Jid& jid)
{
	kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unregistering " << TQString(jid.full()).replace('%',"%%") << endl;

	TQValueList<TQPair<TQString,JabberAccount*> >::Iterator it = m_jids.begin();
	while( it != m_jids.end() ) 
	{
		if( (*it).first == jid.full() ) 
		{
			TQValueList<TQPair<TQString,JabberAccount*> >::Iterator otherIt = it;
			it++;
			m_jids.remove(otherIt);
		}
		else 
		{
			it++;
		}
	}
}

TQPair<Jid,JabberAccount*> JabberCapabilitiesManager::CapabilitiesInformation::nextJid(const Jid& jid, const Task* t)
{
	kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Looking for next JID" << endl;

	TQValueList<TQPair<TQString,JabberAccount*> >::ConstIterator it = m_jids.constBegin(), itEnd = m_jids.constEnd();
	for( ; it != itEnd; ++it) 
	{
		if( (*it).first == jid.full() && (*it).second->client()->rootTask() == t) 
		{
			it++;
			if (it == itEnd) 
			{
				kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "No more JIDs" << endl;

				return TQPair<Jid,JabberAccount*>(Jid(),0L);
			}
			else if( (*it).second->isConnected() ) 
			{
				//tqDebug("caps.cpp: Account isn't active");
				kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Account isn't connected." << endl;

				return TQPair<Jid,JabberAccount*>( (*it).first,(*it).second );
			}
		}
	}
	return TQPair<Jid,JabberAccount*>(Jid(),0L);
}

void JabberCapabilitiesManager::CapabilitiesInformation::setDiscovered(bool value)
{
	m_discovered = value;
}

void JabberCapabilitiesManager::CapabilitiesInformation::setPendingRequests(int pendingRequests)
{
	m_pendingRequests = pendingRequests;
}

void JabberCapabilitiesManager::CapabilitiesInformation::setIdentities(const DiscoItem::Identities& identities)
{
	m_identities = identities;
}

void JabberCapabilitiesManager::CapabilitiesInformation::setFeatures(const TQStringList& featureList)
{
	m_features = featureList;
}
	
void JabberCapabilitiesManager::CapabilitiesInformation::updateLastSeen()
{
	m_lastSeen = TQDate::currentDate();
}

TQDomElement JabberCapabilitiesManager::CapabilitiesInformation::toXml(TQDomDocument *doc) const
{
	TQDomElement info = doc->createElement("info");
	//info.setAttribute("last-seen",lastSeen_.toString(Qt::ISODate));

	// Identities
	DiscoItem::Identities::ConstIterator discoIt = m_identities.constBegin(), discoItEnd = m_identities.constEnd();
	for( ; discoIt != discoItEnd; ++discoIt ) 
	{
		TQDomElement identity = doc->createElement("identity");
		identity.setAttribute("category",(*discoIt).category);
		identity.setAttribute("name",(*discoIt).name);
		identity.setAttribute("type",(*discoIt).type);
		info.appendChild(identity);
	}

	// Features
	TQStringList::ConstIterator featuresIt = m_features.constBegin(), featuresItEnd = m_features.constEnd();
	for( ; featuresIt != featuresItEnd; ++featuresIt )
	{
		TQDomElement feature = doc->createElement("feature");
		feature.setAttribute("node",*featuresIt);
		info.appendChild(feature);
	}

	return info;
}

void JabberCapabilitiesManager::CapabilitiesInformation::fromXml(const TQDomElement &element)
{
	if( element.tagName() != "info") 
	{
		kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Invalid info element" << endl;
		return;
	}
	
	//if (!e.attribute("last-seen").isEmpty())
	//	lastSeen_ = TQDate::fromString(e.attribute("last-seen"),Qt::ISODate);

	for(TQDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling()) 
	{
		TQDomElement infoElement = node.toElement();
		if( infoElement.isNull() ) 
		{
			kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Null element" << endl;
			continue;
		}

		if( infoElement.tagName() == "identity") 
		{
			DiscoItem::Identity id;
			id.category = infoElement.attribute("category");
			id.name = infoElement.attribute("name");
			id.type = infoElement.attribute("type");
			m_identities += id;
		}
		else if( infoElement.tagName() == "feature" ) 
		{
			m_features += infoElement.attribute("node");
		}
		else 
		{
			kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unknown element" << endl;
		}

		m_discovered = true;
	}
}
//END CapabilitiesInformation

//BEGIN Private(d-ptr)
class JabberCapabilitiesManager::Private
{
public:
	Private()
	{}

	// Map a full jid to a capabilities
	TQMap<TQString,Capabilities> jidCapabilitiesMap;
	// Map a capabilities to its detail information
	TQMap<Capabilities,CapabilitiesInformation> capabilitiesInformationMap;
};
//END Private(d-ptr)

JabberCapabilitiesManager::JabberCapabilitiesManager()
	: d(new Private)
{
}

JabberCapabilitiesManager::~JabberCapabilitiesManager()
{
	saveInformation();
	delete d;
}

void JabberCapabilitiesManager::removeAccount(JabberAccount *account)
{
	kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Removing account " << account->accountId() << endl;

	TQValueList<CapabilitiesInformation> info = d->capabilitiesInformationMap.values();

	TQValueList<CapabilitiesInformation>::Iterator it, itEnd = info.end();
	for(it = info.begin(); it != info.end(); ++it) 
	{
		(*it).removeAccount(account);
	}
}

void JabberCapabilitiesManager::updateCapabilities(JabberAccount *account, const XMPP::Jid &jid, const XMPP::Status &status )
{
	if( !account->client() || !account->client()->rootTask() )
		return;
	
	
	// Do don't anything if the jid correspond to the account's JabberClient jid.
	// false means that we don't check for resources.
	if( jid.compare(account->client()->jid(), false) )
		return;

	TQString node = status.capsNode(), version = status.capsVersion(), extensions = status.capsExt();
	Capabilities capabilities( node, version, extensions );
	
	// Check if the capabilities was really updated(i.e the content is different)
	if( d->jidCapabilitiesMap[jid.full()] != capabilities) 
	{
		// Unregister from all old caps nodes
		// FIXME: We should only unregister & register from changed nodes
		CapabilitiesList oldCaps = d->jidCapabilitiesMap[jid.full()].flatten();
		CapabilitiesList::Iterator oldCapsIt = oldCaps.begin(), oldCapsItEnd = oldCaps.end();
		for( ; oldCapsIt != oldCapsItEnd; ++oldCapsIt) 
		{
			if( (*oldCapsIt) != Capabilities() ) 
			{
				d->capabilitiesInformationMap[*oldCapsIt].removeJid(jid);
			}
		}

		// Check if the jid has caps in his presence message.
		if( !status.capsNode().isEmpty() && !status.capsVersion().isEmpty() ) 
		{
			// Register with all new caps nodes
			d->jidCapabilitiesMap[jid.full()] = capabilities;
			CapabilitiesList caps = capabilities.flatten();
			CapabilitiesList::Iterator newCapsIt = caps.begin(), newCapsItEnd = caps.end();
			for( ; newCapsIt != newCapsItEnd; ++newCapsIt ) 
			{
				d->capabilitiesInformationMap[*newCapsIt].addJid(jid,account);
			}
			
			emit capabilitiesChanged(jid); 

			// Register new caps and check if we need to discover features
			newCapsIt = caps.begin();
			for( ; newCapsIt != newCapsItEnd; ++newCapsIt ) 
			{
				if( !d->capabilitiesInformationMap[*newCapsIt].discovered() && d->capabilitiesInformationMap[*newCapsIt].pendingRequests() == 0 ) 
				{
					kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << TQString("Sending disco request to %1, node=%2").arg(TQString(jid.full()).replace('%',"%%")).arg(node + "#" + (*newCapsIt).extensions()) << endl;

					d->capabilitiesInformationMap[*newCapsIt].setPendingRequests(1);
					requestDiscoInfo(account, jid, node + "#" + (*newCapsIt).extensions());
				}
			}
		}
		else 
		{
			// Remove all caps specifications
			kdDebug(JABBER_DEBUG_GLOBAL) << TQString("Illegal caps info from %1: node=%2, ver=%3").arg(TQString(jid.full()).replace('%',"%%")).arg(node).arg(version) << endl;

			d->jidCapabilitiesMap.remove( jid.full() );
		}
	}
	else
	{
		// Add to the list of jids
		CapabilitiesList caps = capabilities.flatten();
		CapabilitiesList::Iterator capsIt = caps.begin(), capsItEnd = caps.end();
		for( ; capsIt != capsItEnd; ++capsIt) 
		{
			d->capabilitiesInformationMap[*capsIt].addJid(jid,account);
		}
	}
}

void JabberCapabilitiesManager::requestDiscoInfo(JabberAccount *account, const Jid& jid, const TQString& node) 
{
	if( !account->client()->rootTask() )
		return;
 
	JT_DiscoInfo *discoInfo = new JT_DiscoInfo(account->client()->rootTask());
	connect(discoInfo, TQT_SIGNAL(finished()), TQT_SLOT(discoRequestFinished()));
	discoInfo->get(jid, node);
	//pending_++;
	//timer_.start(REQUEST_TIMEOUT,true);
	discoInfo->go(true);
}

void JabberCapabilitiesManager::discoRequestFinished()
{
	JT_DiscoInfo *discoInfo = (JT_DiscoInfo*)sender();
	if (!discoInfo)
		return;

	DiscoItem item = discoInfo->item();
	Jid jid = discoInfo->jid();
	kdDebug(JABBER_DEBUG_GLOBAL) << TQString("Disco response from %1, node=%2, success=%3").arg(TQString(jid.full()).replace('%',"%%")).arg(discoInfo->node()).arg(discoInfo->success()) << endl;

	TQStringList tokens = TQStringList::split("#",discoInfo->node());

	// Update features
	Q_ASSERT(tokens.count() == 2);
	TQString node = tokens[0];
	TQString extensions = tokens[1];

	Capabilities jidCapabilities = d->jidCapabilitiesMap[jid.full()];
	if( jidCapabilities.node() == node )
	{
		Capabilities capabilities(node, jidCapabilities.version(), extensions);

		if( discoInfo->success() )
		{
			// Save identities & features
			d->capabilitiesInformationMap[capabilities].setIdentities(item.identities());
			d->capabilitiesInformationMap[capabilities].setFeatures(item.features().list());
			d->capabilitiesInformationMap[capabilities].setPendingRequests(0);
			d->capabilitiesInformationMap[capabilities].setDiscovered(true);

			// Save(Cache) information
			saveInformation();
			
			// Notify affected jids.
			TQStringList jids = d->capabilitiesInformationMap[capabilities].jids();
			TQStringList::ConstIterator jidsIt = jids.constBegin(), jidsItEnd = jids.constEnd();
			for( ; jidsIt != jidsItEnd; ++jidsItEnd ) 
			{
				emit capabilitiesChanged(*jidsIt);
			}
		}
		else 
		{
			TQPair<Jid,JabberAccount*> jidAccountPair = d->capabilitiesInformationMap[capabilities].nextJid(jid,discoInfo->parent());
			if( jidAccountPair.second ) 
			{
				kdDebug(JABBER_DEBUG_GLOBAL) << TQString("Falling back on %1.").arg(TQString(jidAccountPair.first.full()).replace('%',"%%")) << endl;
				requestDiscoInfo( jidAccountPair.second, jidAccountPair.first, discoInfo->node() );
			}
			else 
			{
				kdDebug(JABBER_DEBUG_GLOBAL) << "No valid disco request avalable." << endl;
				d->capabilitiesInformationMap[capabilities].setPendingRequests(0);
			}
		}
	}
	else 
		kdDebug(JABBER_DEBUG_GLOBAL) << TQString("Current client node '%1' does not match response '%2'").arg(jidCapabilities.node()).arg(node) << endl;

	//for (unsigned int i = 0; i < item.features().list().count(); i++) 
	//	printf("    Feature: %s\n",item.features().list()[i].latin1());

	// Check pending requests
//	pending_ = (pending_ > 0 ? pending_-1 : 0);
//	if (!pending_) {
//		timer_.stop();
//		updatePendingJIDs();
//	}
}

void JabberCapabilitiesManager::loadCachedInformation()
{
	TQString capsFileName;
	capsFileName = locateLocal("appdata", TQString::fromUtf8("jabber-capabilities-cache.xml"));

	// Load settings
	TQDomDocument doc;
	TQFile cacheFile(capsFileName);
	if( !cacheFile.open(IO_ReadOnly) )
	{
		kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Could not open the Capabilities cache from disk." << endl;
		return;
	}
	if( !doc.setContent(&cacheFile) )
	{
		kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Could not set the Capabilities cache from file." << endl;
		return;
	}
	cacheFile.close();

	TQDomElement caps = doc.documentElement();
	if( caps.tagName() != "capabilities" ) 
	{
		kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Invalid capabilities element." << endl;
		return;
	}
	
	TQDomNode node;	
	for(node = caps.firstChild(); !node.isNull(); node = node.nextSibling()) 
	{
		TQDomElement element = node.toElement();
		if( element.isNull() ) 
		{
			kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Found a null element." << endl;
			continue;
		}

		if( element.tagName() == "info" ) 
		{
			CapabilitiesInformation info;
			info.fromXml(element);
			Capabilities entityCaps( element.attribute("node"),element.attribute("ver"),element.attribute("ext") );
			d->capabilitiesInformationMap[entityCaps] = info;
		}
		else 
		{
			kdDebug(JABBER_DEBUG_GLOBAL) << k_funcinfo << "Unknow element" << endl;
		}
	}
}

bool JabberCapabilitiesManager::capabilitiesEnabled(const Jid &jid) const
{
	return d->jidCapabilitiesMap.contains( jid.full() );	
}

XMPP::Features JabberCapabilitiesManager::features(const Jid& jid) const
{
	TQStringList featuresList;

	if( capabilitiesEnabled(jid) ) 
	{
		CapabilitiesList capabilitiesList = d->jidCapabilitiesMap[jid.full()].flatten();
		CapabilitiesList::ConstIterator capsIt = capabilitiesList.constBegin(), capsItEnd = capabilitiesList.constEnd();
		for( ; capsIt != capsItEnd; ++capsIt) 
		{
			featuresList += d->capabilitiesInformationMap[*capsIt].features();
		}
	}

	return Features(featuresList);
}

TQString JabberCapabilitiesManager::clientName(const Jid& jid) const
{
	if( capabilitiesEnabled(jid) ) 
	{
		Capabilities caps = d->jidCapabilitiesMap[jid.full()];
		TQString name = d->capabilitiesInformationMap[Capabilities(caps.node(),caps.version(),caps.version())].identities().first().name;
		
		// Try to be intelligent about the name
		/*if (name.isEmpty()) {
			name = cs.node();
			if (name.startsWith("http://"))
				name = name.right(name.length() - 7);
				
			if (name.startsWith("www."))
				name = name.right(name.length() - 4);
			
			int cut_pos = name.find(".");
			if (cut_pos != -1) {
				name = name.left(cut_pos);
			}
		}*/

		return name;
	}
	else 
	{
		return TQString();
	}
}

TQString JabberCapabilitiesManager::clientVersion(const Jid& jid) const
{
	return (capabilitiesEnabled(jid) ? d->jidCapabilitiesMap[jid.full()].version() : TQString());
}

void JabberCapabilitiesManager::saveInformation()
{
	TQString capsFileName;
	capsFileName = locateLocal("appdata", TQString::fromUtf8("jabber-capabilities-cache.xml"));

	// Generate XML
	TQDomDocument doc;
	TQDomElement capabilities = doc.createElement("capabilities");
	doc.appendChild(capabilities);
	TQMap<Capabilities,CapabilitiesInformation>::ConstIterator it = d->capabilitiesInformationMap.constBegin(), itEnd = d->capabilitiesInformationMap.constEnd();
	for( ; it != itEnd; ++it ) 
	{
		TQDomElement info = it.data().toXml(&doc);
		info.setAttribute("node",it.key().node());
		info.setAttribute("ver",it.key().version());
		info.setAttribute("ext",it.key().extensions());
		capabilities.appendChild(info);
	}

	// Save
	TQFile capsFile(capsFileName);
	if( !capsFile.open(IO_WriteOnly) ) 
	{
		kdDebug(JABBER_DEBUG_GLOBAL	) << k_funcinfo << "Error while opening Capabilities cache file." << endl;
		return;
	}

	TQTextStream textStream;
	textStream.setDevice(TQT_TQIODEVICE(&capsFile));
	textStream.setEncoding(TQTextStream::UnicodeUTF8);
	textStream << doc.toString();
	textStream.unsetDevice();
	capsFile.close();
}

#include "jabbercapabilitiesmanager.moc"