/*
	Kopete Oscar Protocol
	ssimanager.cpp - SSI management

	Copyright ( c ) 2004 Gustavo Pichorim Boiko <gustavo.boiko@kdemail.net>
	Copyright ( c ) 2004 Matt Rogers <mattr@kde.org>

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

	based on ssidata.h and ssidata.cpp ( c ) 2002 Tom Linsky <twl6@po.cwru.edu>

	*************************************************************************
	*                                                                       *
	* 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 "ssimanager.h"
#include <kdebug.h>
#include "oscarutils.h"

// -------------------------------------------------------------------

class SSIManagerPrivate
{
public:
	TQValueList<Oscar::SSI> SSIList;
	TQValueList<WORD> groupIdList;
	TQValueList<WORD> itemIdList;
	bool complete;
	DWORD lastModTime;
	WORD maxContacts;
	WORD maxGroups;
	WORD maxVisible;
	WORD maxInvisible;
	WORD maxIgnore;
	WORD nextContactId;
	WORD nextGroupId;
};

SSIManager::SSIManager( TQObject *parent, const char *name )
 : TQObject( parent, name )
{
	d = new SSIManagerPrivate;
	d->complete = false;
	d->lastModTime = 0;
	d->nextContactId = 0;
	d->nextGroupId = 0;
	d->maxContacts = 999;
	d->maxGroups = 999;
	d->maxIgnore = 999;
	d->maxInvisible = 999;
	d->maxVisible = 999;
}


SSIManager::~SSIManager()
{
	clear();
	delete d;
}

void SSIManager::clear()
{
	//delete all SSIs from the list
	if ( d->SSIList.count() > 0 )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Clearing the SSI list" << endl;
		TQValueList<Oscar::SSI>::iterator it = d->SSIList.begin();
		
		while ( it != d->SSIList.end() && d->SSIList.count() > 0 )
			it = d->SSIList.remove( it );
	};
	
	d->itemIdList.clear();
	d->groupIdList.clear();
	d->complete = false;
	d->lastModTime = 0;
	d->nextContactId = 0;
	d->nextGroupId = 0;
}

WORD SSIManager::nextContactId()
{
	if ( d->nextContactId == 0 )
		d->nextContactId++;
	
	d->nextContactId = findFreeId( d->itemIdList, d->nextContactId );
	
	if ( d->nextContactId == 0xFFFF )
	{
		kdWarning(OSCAR_RAW_DEBUG) << k_funcinfo << "No free id!" << endl;
		return 0xFFFF;
	}
	
	if ( d->itemIdList.contains( d->nextContactId ) == 0 )
		d->itemIdList.append( d->nextContactId );
		
	return d->nextContactId++;
}

WORD SSIManager::nextGroupId()
{
	if ( d->nextGroupId == 0 )
		d->nextGroupId++;
	
	d->nextGroupId = findFreeId( d->groupIdList, d->nextGroupId );
	
	if ( d->nextGroupId == 0xFFFF )
	{
		kdWarning(OSCAR_RAW_DEBUG) << k_funcinfo << "No free group id!" << endl;
		return 0xFFFF;
	}
	
	if ( d->groupIdList.contains( d->nextGroupId ) == 0 )
		d->groupIdList.append( d->nextGroupId );
	
	return d->nextGroupId++;
}

WORD SSIManager::numberOfItems() const
{
	return d->SSIList.count();
}

DWORD SSIManager::lastModificationTime() const
{
	return d->lastModTime;
}

void SSIManager::setLastModificationTime( DWORD lastTime )
{
	d->lastModTime = lastTime;
}

void SSIManager::setParameters( WORD maxContacts, WORD maxGroups, WORD maxVisible, WORD maxInvisible, WORD maxIgnore )
{
	//I'm not using k_funcinfo for these debug statements because of
	//the function's long signature
	TQString funcName = TQString::fromLatin1( "[void SSIManager::setParameters] " );
	kdDebug(OSCAR_RAW_DEBUG) << funcName << "Max number of contacts allowed in SSI: "
		<< maxContacts << endl;
	kdDebug(OSCAR_RAW_DEBUG) << funcName << "Max number of groups allowed in SSI: "
		<< maxGroups << endl;
	kdDebug(OSCAR_RAW_DEBUG) << funcName << "Max number of contacts allowed on visible list: "
		<< maxVisible << endl;
	kdDebug(OSCAR_RAW_DEBUG) << funcName << "Max number of contacts allowed on invisible list: "
		<< maxInvisible << endl;
	kdDebug(OSCAR_RAW_DEBUG) << funcName << "Max number of contacts allowed on ignore list: "
		<< maxIgnore << endl;
	
	d->maxContacts = maxContacts;
	d->maxGroups = maxGroups;
	d->maxInvisible = maxInvisible;
	d->maxVisible = maxVisible;
	d->maxIgnore = maxIgnore;
}

void SSIManager::loadFromExisting( const TQValueList<Oscar::SSI*>& newList )
{
	Q_UNUSED( newList );
	//FIXME: NOT Implemented!
}

bool SSIManager::hasItem( const Oscar::SSI& item ) const
{
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
	{
		Oscar::SSI s = ( *it );
		if ( s == item )
			return true;
	}
	
	return false;
}

Oscar::SSI SSIManager::findGroup( const TQString &group ) const
{
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
		if ( ( *it ).type() == ROSTER_GROUP && (*it ).name().lower() == group.lower() )
			return ( *it );
	
	
	return m_dummyItem;
}

Oscar::SSI SSIManager::findGroup( int groupId ) const
{
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
		if ( ( *it ).type() == ROSTER_GROUP && (*it ).gid() == groupId )
			return ( *it );
	
	return m_dummyItem;
}

Oscar::SSI SSIManager::findContact( const TQString &contact, const TQString &group ) const
{
	
	if ( contact.isNull() || group.isNull() )
	{
		kdWarning(OSCAR_RAW_DEBUG) << k_funcinfo <<
			"Passed NULL name or group string, aborting!" << endl;
		
		return m_dummyItem;
	}
	
	Oscar::SSI gr = findGroup( group ); // find the parent group
	if ( gr.isValid() )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "gr->name= " << gr.name() <<
			", gr->gid= " << gr.gid() <<
			", gr->bid= " << gr.bid() <<
			", gr->type= " << gr.type() << endl;
	
		TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
		
		for ( it = d->SSIList.begin(); it != listEnd; ++it )
		{
			if ( ( *it ).type() == ROSTER_CONTACT && (*it ).name() == contact && (*it ).gid() == gr.gid() )
			{
				//we have found our contact
				kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo <<
					"Found contact " << contact << " in SSI data" << endl;
				 return ( *it );
			}
		}
	}
	else
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo <<
			"ERROR: Group '" << group << "' not found!" << endl;
	}
	return m_dummyItem;
}

Oscar::SSI SSIManager::findContact( const TQString &contact ) const
{
	
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
		if ( ( *it ).type() == ROSTER_CONTACT && (*it ).name() == contact )
			return ( *it );
	
	return m_dummyItem;
}

Oscar::SSI SSIManager::findContact( int contactId ) const
{
	TQValueList<Oscar::SSI>::const_iterator it,  listEnd = d->SSIList.end();
	
	for ( it = d->SSIList.begin(); it!= listEnd; ++it )
		if ( ( *it ).type() == ROSTER_CONTACT && ( *it ).bid() == contactId )
			return ( *it );
	
	return m_dummyItem;
}

Oscar::SSI SSIManager::findItemForIcon( TQByteArray iconHash ) const
{
	TQValueList<Oscar::SSI>::const_iterator it,  listEnd = d->SSIList.end();
	
	for ( it = d->SSIList.begin(); it!= listEnd; ++it )
	{
		if ( ( *it ).type() == ROSTER_BUDDYICONS )
		{
			TLV t = Oscar::findTLV( ( *it ).tlvList(), 0x00D5 );
			Buffer b(t.data);
			b.skipBytes(1); //don't care about flags
			BYTE iconSize = b.getByte();
			TQByteArray hash( b.getBlock( iconSize ) );
			if ( hash == iconHash )
			{
				Oscar::SSI s = ( *it );
				return s;
			}
		}
	}
	return m_dummyItem;
}

Oscar::SSI SSIManager::findItemForIconByRef( int ref ) const
{
	TQValueList<Oscar::SSI>::const_iterator it,  listEnd = d->SSIList.end();
	
	for ( it = d->SSIList.begin(); it!= listEnd; ++it )
	{
		if ( ( *it ).type() == ROSTER_BUDDYICONS )
		{
			if ( ( *it ).name().toInt() == ref )
			{
				Oscar::SSI s = ( *it );
				return s;
			}
		}
	}
	return m_dummyItem;	
}

Oscar::SSI SSIManager::findItem( const TQString &contact, int type ) const
{
	TQValueList<Oscar::SSI>::const_iterator it,  listEnd = d->SSIList.end();
	
	for ( it = d->SSIList.begin(); it!= listEnd; ++it )
		if ( ( *it ).type() == type && ( *it ).name() == contact )
			return ( *it );
	
	return m_dummyItem;
}

TQValueList<Oscar::SSI> SSIManager::groupList() const
{
	TQValueList<Oscar::SSI> list;
	
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
		if ( ( *it ).type() == ROSTER_GROUP  )
			list.append( ( *it ) );
	
	return list;
}

TQValueList<Oscar::SSI> SSIManager::contactList() const
{
	TQValueList<Oscar::SSI> list;
	
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
		if ( ( *it ).type() == ROSTER_CONTACT  )
			list.append( ( *it ) );
	
	return list;
}

TQValueList<Oscar::SSI> SSIManager::visibleList() const
{
	TQValueList<Oscar::SSI> list;
	
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
		if ( ( *it ).type() == ROSTER_VISIBLE  )
			list.append( ( *it ) );
	
	return list;
}

TQValueList<Oscar::SSI> SSIManager::invisibleList() const
{
	TQValueList<Oscar::SSI> list;
	
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
		if ( ( *it ).type() == ROSTER_INVISIBLE  )
			list.append( ( *it ) );
	
	return list;
}

TQValueList<Oscar::SSI> SSIManager::contactsFromGroup( const TQString &group ) const
{
	TQValueList<Oscar::SSI> list;
	
	Oscar::SSI gr = findGroup( group );
	if ( gr.isValid() )
	{
		TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
		for ( it = d->SSIList.begin(); it != listEnd; ++it )
			if ( ( *it ).type() == ROSTER_CONTACT && (*it ).gid() == gr.gid() )
				list.append( ( *it ) );
	}
	return list;
}

TQValueList<Oscar::SSI> SSIManager::contactsFromGroup( int groupId ) const
{
	TQValueList<Oscar::SSI> list;
	
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
		if ( ( *it ).type() == ROSTER_CONTACT && (*it ).gid() == groupId  )
			list.append( ( *it ) );
	
	return list;
}

Oscar::SSI SSIManager::visibilityItem() const
{
	Oscar::SSI item = m_dummyItem;
	TQValueList<Oscar::SSI>::const_iterator it, listEnd = d->SSIList.end();
	for ( it = d->SSIList.begin(); it != listEnd; ++it )
	{
		if ( ( *it ).type() == 0x0004 )
		{
			kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Found visibility setting" << endl;
			item = ( *it );
			return item;
		}
	}
	
	return item;
}

void SSIManager::setListComplete( bool complete )
{
	d->complete = complete;
}

bool SSIManager::listComplete() const
{
	return d->complete;
}

bool SSIManager::newGroup( const Oscar::SSI& group )
{
	//trying to find the group by its ID
	TQValueList<Oscar::SSI>::iterator it, listEnd = d->SSIList.end();
	if ( findGroup( group.name() ).isValid() )
		return false;

	if ( !group.name().isEmpty() ) //avoid the group with gid 0 and bid 0
	{	// the group is really new
		kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Adding group '" << group.name() << "' to SSI list" << endl;
		
		d->SSIList.append( group );
		addID( group );
		emit groupAdded( group );
		return true;
	}
	return false;
}

bool SSIManager::updateGroup( const Oscar::SSI& group )
{
	Oscar::SSI oldGroup = findGroup( group.name() );

	if ( oldGroup.isValid() )
	{
		removeID( oldGroup );
		d->SSIList.remove( oldGroup );
	}

	if ( d->SSIList.findIndex( group ) != -1 )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "New group is already in list." << endl;
		return false;
	}

	kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Updating group '" << group.name() << "' in SSI list" << endl;
	d->SSIList.append( group );
	addID( group );
	emit groupUpdated( group );

	return true;
}

bool SSIManager::removeGroup( const Oscar::SSI& group )
{
	TQString groupName = group.name();
	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Removing group " << group.name() << endl;
	int remcount = d->SSIList.remove( group );
	removeID( group );
	
	if ( remcount == 0 )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "No groups removed" << endl;
		return false;
	}
	
	emit groupRemoved( groupName );
	return true;
}

bool SSIManager::removeGroup( const TQString &group )
{
	Oscar::SSI gr = findGroup( group );
	
	if ( gr.isValid() && removeGroup( gr )  )
	{
		return true;
	}
	else
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Group " << group << " not found." << endl;

	return false;
}

bool SSIManager::newContact( const Oscar::SSI& contact )
{
	if ( d->SSIList.findIndex( contact ) == -1 )
	{
		kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Adding contact '" << contact.name() << "' to SSI list" << endl;
		addID( contact );
		d->SSIList.append( contact );
		emit contactAdded( contact );
	}
	else
		return false;
	return true;
}

bool SSIManager::updateContact( const Oscar::SSI& contact )
{
	Oscar::SSI oldContact = findContact( contact.name() );

	if ( oldContact.isValid() )
	{
		removeID( oldContact );
		d->SSIList.remove( oldContact );
	}

	if ( d->SSIList.findIndex( contact ) != -1 )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "New contact is already in list." << endl;
		return false;
	}

	kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Updating contact '" << contact.name() << "' in SSI list" << endl;
	addID( contact );
	d->SSIList.append( contact );
	emit contactUpdated( contact );
	
	return true;
}

bool SSIManager::removeContact( const Oscar::SSI& contact )
{
	TQString contactName = contact.name();
	int remcount = d->SSIList.remove( contact );
	removeID( contact );
	
	if ( remcount == 0 )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "No contacts were removed." << endl;
		return false;
	}
	
	emit contactRemoved( contactName );
	return true;
}

bool SSIManager::removeContact( const TQString &contact )
{
	Oscar::SSI ct = findContact( contact );
	
	if ( ct.isValid() && removeContact( ct ) )
		return true;
	else
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Contact " << contact << " not found." << endl;
	
	return false;
}

bool SSIManager::newItem( const Oscar::SSI& item )
{
	if ( d->SSIList.findIndex( item ) != -1 )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Item is already in list." << endl;
		return false;
	}

	kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "Adding item " << item.toString() << endl;
	d->SSIList.append( item );
	addID( item );
	return true;
}

bool SSIManager::updateItem( const Oscar::SSI& item )
{
	Oscar::SSI oldItem = findItem( item.name(), item.type() );

	if ( oldItem.isValid() )
	{
		removeID( oldItem );
		d->SSIList.remove( oldItem );
	}

	if ( d->SSIList.findIndex( item ) != -1 )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "New item is already in list." << endl;
		return false;
	}

	kdDebug( OSCAR_RAW_DEBUG ) << k_funcinfo << "Updating item in SSI list" << endl;
	addID( item );
	d->SSIList.append( item );
	return true;
}

bool SSIManager::removeItem( const Oscar::SSI& item )
{
	int remcount = d->SSIList.remove( item );
	removeID( item );

	if ( remcount == 0 )
	{
		kdDebug(OSCAR_RAW_DEBUG) << k_funcinfo << "No items were removed." << endl;
		return false;
	}
	
	return true;
}

void SSIManager::addID( const Oscar::SSI& item )
{
	if ( item.type() == ROSTER_GROUP )
	{
		if ( d->groupIdList.contains( item.gid() ) == 0 )
			d->groupIdList.append( item.gid() );
	}
	else
	{
		if ( d->itemIdList.contains( item.bid() ) == 0 )
			d->itemIdList.append( item.bid() );
	}
}

void SSIManager::removeID( const Oscar::SSI& item )
{
	if ( item.type() == ROSTER_GROUP )
	{
		d->groupIdList.remove( item.gid() );
	
		if ( d->nextGroupId > item.gid() )
			d->nextGroupId = item.gid();
	}
	else
	{
		d->itemIdList.remove( item.bid() );
		
		if ( d->nextContactId > item.bid() )
			d->nextContactId = item.bid();
	}
}

WORD SSIManager::findFreeId( const TQValueList<WORD>& idList, WORD fromId ) const
{
	for ( WORD id = fromId; id < 0x8000; id++ )
	{
		if ( idList.contains( id ) == 0 )
			return id;
	}
	
	return 0xFFFF;
}

#include "ssimanager.moc"
