/***************************************************************************
                          contactlist.cpp  -  description
                             -------------------
    begin                : Sun Jan 5 2003
    copyright            : (C) 2003 by Mike K. Bennett
                           (C) 2005 by Diederik van der Boor
    email                : mkb137b@hotmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "contactlist.h"

#include "../contact/contact.h"
#include "../contact/contactextension.h"
#include "../contact/group.h"
#include "../contact/msnobject.h"
#include "../contact/specialgroups.h"
#include "../utils/kmessconfig.h"
#include "../currentaccount.h"
#include "../kmessdebug.h"

#include <QFile>
#include <QMimeData>

#include <KLocale>


#ifdef KMESSDEBUG_CONTACTLIST
  #define KMESSDEBUG_CONTACTLIST_GENERAL
  #define KMESSDEBUG_CONTACTLIST_TOOLTIP
  #define KMESSDEBUG_CONTACTLIST_DISCONNECTION
#endif



// The constructor
ContactList::ContactList()
: rootNode_(0)
{
}



// The destructor
ContactList::~ContactList()
{
#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  kmDebug() << "DESTROYING...";
#endif

  // Destroy the list's contents, without regenerating the special groups and the model
  reset( false );

#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  kmDebug() << "DESTROYED.";
#endif
}



// Add a contact to the contact list
Contact * ContactList::addContact( const QString& handle, const QString& friendlyName, int lists,
                                    QStringList groupIds, const QString& guid, bool isMessenger )
{
  // Ignore call if we are closing...
  // TODO: prevent this issue from happening in the first place.
  if( KMESS_NULL(rootNode_) ) return 0;

#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  kmDebug() << "Adding to list contact" << handle << " in groups" << groupIds;
#endif

#ifdef KMESSTEST
  KMESS_ASSERT( ! handle.isEmpty() );
#endif

  if( handle.isEmpty() )
  {
    return 0;
  }

  // More consistency checks
  // TODO we could return the Contact* founded ?!
  if( getContactByHandle( handle ) != 0 )
  {
    kmWarning() << "attempted to add contact " << handle << " twice.";
    return 0;
  }


  // Create the contact
  Contact *contact = new Contact( handle, friendlyName, lists, groupIds, guid );
  contact->setMessengerUser( isMessenger );

  contacts_.insert( handle.toLower(), contact );

  // Add the contact to the special groups
  if( contact->isFriend() )
  {
    // Add contacts without group to the Individuals group
    if( groupIds.isEmpty() )
    {
      groupIds << SpecialGroups::INDIVIDUALS;
    }

    // Initially, all friends are Offline (we receive the status for connected ones later)
    groupIds << SpecialGroups::OFFLINE;
  }
  else
  {
    if( contact->isAllowed() )
    {
      groupIds << SpecialGroups::ALLOWED;
    }
    else
    {
      groupIds << SpecialGroups::REMOVED;
    }
  }

  // Add it to the nodes it belongs to
  foreach( const QString &id, groupIds )
  {
    ContactListModelItem *groupNode = rootNode_->childGroup( id );
    if( ! groupNode )
    {
      kmWarning() << "Contact's group" << id << "doesn't exist!";
      continue;
    }

    // form a QModelIndex for the group which will parent this contact
    const QModelIndex groupIdx( index( groupNode->row(), 0, QModelIndex() ) );

    if ( !groupIdx.isValid() )
    {
      kmWarning() << "Formed invalid index for group " << id << " for contact " << handle << ", not adding to group";
      continue;
    }

    beginInsertRows( groupIdx, groupNode->childCount(), groupNode->childCount() );

    new ContactListModelItem( contact, groupNode );

    endInsertRows();
  }

  // Attach the signals
  connect( contact, SIGNAL(                   changedMsnObject(Contact*)      ),   // Contact changed msnobject
           this,    SIGNAL(            contactChangedMsnObject(Contact*)      ));
  connect( contact, SIGNAL(                     contactOffline(Contact*,bool) ),   // Contact went offline
           this,    SLOT  (          slotForwardContactOffline(Contact*,bool) ));
  connect( contact, SIGNAL(                      contactOnline(Contact*,bool) ),   // Contact went online
           this,    SLOT  (           slotForwardContactOnline(Contact*,bool) ));
  connect( contact, SIGNAL(                       changedGroup(Contact*)      ),   // Contact moved
           this,    SLOT  (                   slotContactMoved(Contact*)      ));
  connect( contact, SIGNAL(                        changedList(Contact*)      ),   // Contact was added/removed from certain lists
           this,    SLOT  (                   slotContactMoved(Contact*)      ));

  // Notify the addition to any listener
  emit contactAdded( contact );

  return contact;
}



// Add a group to the contact list
Group* ContactList::addGroup( const QString& groupId, const QString& groupName )
{
  // Ignore call if we are closing...
  // TODO: prevent this issue from happening in the first place.
  if( KMESS_NULL(rootNode_) ) return 0;

#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  kmDebug() << groupName << "(" << groupId << ")";
#endif

  // Check if the group already exists
  if ( groupExists( groupId ) )
  {
    kmDebug() << "Group " << groupId << " already exists.";
    return 0;
  }

  // Create the group
  Group *group = new Group( groupId, groupName );
  groups_.append( group );

  if( group->isSpecialGroup() )
  {
    group->setSortPosition( 9999 );
  }
  else
  {
    // Set a temporary sort position; if we're initializing this will be changed
    // later when reading the contactlist file (see ContactList::readProperties()),
    // otherwise this will be the initial sP for this group so it's added at the end
    group->setSortPosition( groups_.count() - 1 );
    connect( group, SIGNAL( changedPosition( Group*, int, int ) ),
             this,    SLOT(  slotGroupMoved( Group*, int, int ) ) );
  }

  // Insert the new
  beginInsertRows( QModelIndex(), rootNode_->childCount(), rootNode_->childCount() );

  new ContactListModelItem( group, rootNode_ );

  endInsertRows();

  // Signal the UI
  emit groupAdded( group );

  return group;
}



// Change a contact's status (and update the friendly name)
void ContactList::changeContactStatus( const QString& handle, Status status, const QString& friendlyName,
                                       uint capabilities, const QString& msnObjectString, bool showBaloon )
{
  // Ignore call if we are closing...
  // TODO: prevent this issue from happening in the first place.
  if( KMESS_NULL(rootNode_) ) return;

  // Get the contact
  Contact *contact = getContactByHandle( handle );
  if ( contact == 0 )
  {
    kmWarning() << "changeContactStatus - Couldn't find contact " << handle << " in the contact list.";
    return;
  }

  // Store the old online state to detect a change
  bool   wasOnline = contact->isOnline();
  Status oldStatus = contact->getStatus();

  // Update the settings
  if( ! friendlyName.isEmpty() )
  {
    contact->setFriendlyName( friendlyName );
  }
  contact->setStatus( status, showBaloon );

  // If the contact gone offline/online, don't remove the msnobject and leave the current picture
  if( ! msnObjectString.isNull() )
  {
    contact->loadMsnObject( msnObjectString );
  }

  contact->setCapabilities( capabilities );   // also resets clientName if the caps have changed.

  // Read the new state
  bool isOnline = contact->isOnline();

  // Try to detect the client name when it's reset (e.g. connected/reconnected with a new client).
  if( isOnline && contact->getClientName().isEmpty() )
  {
    const MsnObject *msnObject = contact->getMsnObject();
    contact->detectClientName( capabilities, msnObject );
  }

  // The contact is changing status but not switching between online/offline
  if( wasOnline == isOnline || ! contact->isFriend() )
  {
    if( oldStatus != status )
    {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
      kmDebug() << "Changing contact" << handle << "'s status";
#endif
      // Signal that the contact has changed status, but not switched between an online status and the offline one
      emit contactChangedStatus( contact, showBaloon );
    }

#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "Status unchanged, skipping.";
#endif

    forwardDataChanged( rootNode_->childContacts( handle ) );
    return;
  }

  // Switch the contact between the Offline and Online groups, as it has connected or disconnected

  // Get references to both the Online and Offline groups
  ContactListModelItem *sourceGroup, *destinationGroup;
  if( contact->isOnline() )
  {
    // The contact is now online, move it from offline to online
    sourceGroup      = rootNode_->childGroup( SpecialGroups::OFFLINE );
    destinationGroup = rootNode_->childGroup( SpecialGroups::ONLINE  );
  }
  else
  {
    // The contact is now offline, move it from online to offline
    sourceGroup      = rootNode_->childGroup( SpecialGroups::ONLINE  );
    destinationGroup = rootNode_->childGroup( SpecialGroups::OFFLINE );
  }

  if( ! sourceGroup || ! destinationGroup )
  {
    kmWarning() << "The Online or Offline groups are not available!";
    return;
  }

  // Move the contact from the first group and put it in the other one

#ifdef KMESSDEBUG_CONTACTLISTMODEL
  kmDebug() << "Moving contact" << handle << "from" << sourceGroup << "to" << destinationGroup;
#endif

  ContactListModelItem *contactItem = sourceGroup->childContact( handle );
  if( ! contactItem )
  {
    kmWarning() << "Unable to find contact" << handle << "(now online?" << contact->isOnline();
    kmWarning() << "The contact is in groups:" << rootNode_->childContacts( handle );
    return;
  }

  emit layoutAboutToBeChanged();

  // Move the node to the new place
  contactItem->moveTo( destinationGroup );

  emit layoutChanged();
}



/**
 * Get the number of columns present in an index
 *
 * Just returns the root node's number of columns as it doesn't change.
 *
 * @param parent The index to count columns of (ignored)
 * @return int
 */
int ContactList::columnCount( const QModelIndex &/*parent*/ ) const
{
  if( rootNode_ == 0 )
  {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmWarning() << "Called columnCount() without a root node!";
#endif
    return 0;
  }

  return rootNode_->columnCount();
}



// Return whether or not the contact with the given handle exists
bool ContactList::contactExists( QString handle )
{
  return getContactByHandle( handle ) != 0;
}



/**
 * Return the data contained by an index in the model
 *
 * @param index  Points to the actual data to be returned
 * @param role   Indicates what data from the index needs to be obtained.
 * @see http://doc.trolltech.com/4.3/qt.html#ItemDataRole-enum for available roles
 */
QVariant ContactList::data( const QModelIndex &index, int role ) const
{
  const ContactListModelItem *item = static_cast<ContactListModelItem*>( index.internalPointer() );

  // If the index isn't valid or contained non valid data, do nothing
  if( ! index.isValid() || ! item )
  {
    return QVariant();
  }

  return item->data( role );
}



// Insert dropped data in the model (move/copy stuff around, in fact)
bool ContactList::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
{
  Q_UNUSED( row );

  if( action == Qt::IgnoreAction )
  {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "Ignore action";
#endif
    return true;
  }

  // Only accept our special mimetype
  if( ! data->hasFormat( "application/kmess.list.item" ) )
  {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "Invalid format:" << data->formats();
#endif
    return false;
  }

  if ( column > 0 )
  {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "Invalid column:" << column;
#endif
    return false;
  }

#ifdef KMESSDEBUG_CONTACTLISTMODEL
  kmDebug() << "Drop info - parent:" << parent << "Data:" << parent.data();
#endif

  QByteArray encodedData( data->data( "application/kmess.list.item" ) );
  QDataStream stream( &encodedData, QIODevice::ReadOnly );

  while( ! stream.atEnd() )
  {
    quint64 itemObject; // It will contain a pointer
    ContactListModelItem *sourceItem, *targetItem;

    stream >> itemObject;
    sourceItem = (ContactListModelItem*)itemObject;

    if( sourceItem == 0 )
    {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
      kmDebug() << "Invalid item extracted from mime data!";
#endif
      continue;
    }

    targetItem = static_cast<ContactListModelItem*>( parent.internalPointer() );

    if( targetItem == 0 )
    {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
      kmDebug() << "Invalid target item!";
#endif
      continue;
    }

#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "Extracted item:" << sourceItem;
#endif

    // Take action, depending on the type of dropped item
    switch( sourceItem->getType() )
    {
      case ContactListModelItem::ItemContact:
      {
        /**
         * A Contact dropped to...
         * - root: no action
         * - another group: will be moved there
         * - its own group: no action
         * - a contact in another group: will be moved to that contact's group
         * - a contact in its own group: no action
         */
        const Contact *contact = static_cast<Contact*>( sourceItem->object() );

        Group *sourceGroup = static_cast<Group*>( sourceItem->parent()->object() );
        if( sourceGroup->isSpecialGroup() )
        {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
          kmDebug() << "Cannot move a contact from within a special group.";
#endif
          continue;
        }

        switch( targetItem->getType() )
        {
          // Contact dropped to root
          case ContactListModelItem::ItemRoot:
#ifdef KMESSDEBUG_CONTACTLISTMODEL
            kmDebug() << "Cannot move a contact to root.";
#endif
            continue;

          // Contact dropped to a group
          case ContactListModelItem::ItemGroup:
          {
            if( targetItem == sourceItem->parent() )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move a contact to its own group.";
#endif
              continue;
            }

            Group *targetGroup = static_cast<Group*>( targetItem->object() );

            // Avoid moving the contact where it already is
            if( contact->getGroupIds().contains( targetGroup->getId() ) )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move a contact where it already is.";
#endif
              continue;
            }

            // Avoid moving contacts to special groups - which have no counterpart on the server
            if( targetGroup->isSpecialGroup() )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move a contact to a special group.";
#endif
              continue;
            }

#ifdef KMESSDEBUG_CONTACTLIST
            kmDebug() << "Moving contact from group" << sourceGroup->getId() << "to" << targetGroup->getId();
#endif
            emit dndMoveContactToGroup( contact->getHandle(), sourceGroup->getId(), targetGroup->getId() );
            break;
          }

          // Contact dropped to another contact
          case ContactListModelItem::ItemContact:
          {
            if( targetItem == sourceItem )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move a contact to itself.";
#endif
              continue;
            }

            const Group *targetGroup = static_cast<Group*>( targetItem->parent()->object() );

            // Avoid moving the contact where it already is
            if( contact->getGroupIds().contains( targetGroup->getId() ) )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move a contact where it already is.";
#endif
              continue;
            }

            // Avoid moving contacts to special groups - which have no counterpart on the server
            if( targetGroup->isSpecialGroup() )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move a contact to a special group.";
#endif
              continue;
            }

#ifdef KMESSDEBUG_CONTACTLIST
            kmDebug() << "Moving contact from group" << sourceGroup->getId() << "to" << targetGroup->getId();
#endif
            emit dndMoveContactToGroup( contact->getHandle(), sourceGroup->getId(), targetGroup->getId() );
            break;
          }
        }

        break;
      }
      // End of handling of dropped contacts

      case ContactListModelItem::ItemGroup:
      {
        /**
         * A Group dropped to...
         * - root: no action
         * - another group: will be moved above it
         * - a contact within another group: will be moved above it
         * - itself: no action
         * - a contact within itself: no action
         */
        Group *sourceGroup = static_cast<Group*>( sourceItem->object() );

        if( sourceGroup->isSpecialGroup() )
        {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
          kmDebug() << "Cannot move a special group.";
#endif
          continue;
        }

        switch( targetItem->getType() )
        {
          // Group dropped to the root
          case ContactListModelItem::ItemRoot:
#ifdef KMESSDEBUG_CONTACTLISTMODEL
            kmDebug() << "Cannot move a group to root.";
#endif
            continue;

          // Group dropped to another group
          case ContactListModelItem::ItemGroup:
          {
            if( targetItem == sourceItem )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move a group to itself.";
#endif
              continue;
            }

            Group *targetGroup = static_cast<Group*>( targetItem->object() );

            // Avoid moving contacts to special groups - which have no counterpart on the server
            if( targetGroup->isSpecialGroup() )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move to a special group.";
#endif
              continue;
            }

#ifdef KMESSDEBUG_CONTACTLIST
            kmDebug() << "Swapping groups" << sourceGroup->getName() << "and" << targetGroup->getName();
#endif
            // Get the group sorting position for both groups
            int oldGroupPos = targetGroup->getSortPosition();
            int newGroupPos = sourceGroup->getSortPosition();

            // Swap them
            targetGroup->setSortPosition( newGroupPos );
            sourceGroup->setSortPosition( oldGroupPos );

            // Save the changed setting
            saveProperties( targetGroup );
            saveProperties( sourceGroup );

            // Reset the oldSortPosition (XXX HACK FIXME OMG)
            targetGroup->setOldSortPosition( 0 );
            sourceGroup->setOldSortPosition( 0 );
            break;
          }

          // Group dropped to a contact
          case ContactListModelItem::ItemContact:
          {
            Group *targetGroup = static_cast<Group*>( targetItem->parent()->object() );

            if( targetItem->parent() == sourceItem )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move a group to itself.";
#endif
              continue;
            }

            if( targetGroup->isSpecialGroup() )
            {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
              kmDebug() << "Cannot move to a special group.";
#endif
              continue;
            }

#ifdef KMESSDEBUG_CONTACTLIST
            kmDebug() << "Swapping groups" << sourceGroup->getName() << "and" << targetGroup->getName();
#endif

            // Get the group sorting position for both groups
            int oldGroupPos = targetGroup->getSortPosition();
            int newGroupPos = sourceGroup->getSortPosition();

            // Swap them
            targetGroup->setSortPosition( newGroupPos );
            sourceGroup->setSortPosition( oldGroupPos );

            // Save the changed setting
            saveProperties( targetGroup );
            saveProperties( sourceGroup );

            // Reset the oldSortPosition (XXX HACK FIXME OMG)
            targetGroup->setOldSortPosition( 0 );
            sourceGroup->setOldSortPosition( 0 );
            break;
          }
        }

        break;
      }
      // End of handling of dropped groups

      case ContactListModelItem::ItemRoot:
      {
        /**
         * Root can't be moved, let alone dropped anywhere :)
         */
        break;
      }
    }
  }

  return true;
}



/**
 * Dumps the contact list contents to the debug output
 *
 * This method is exclusively meant for debugging purposes.
 * This method is also recursive. Do not call with any arguments, they are for internal use only.
 *
 * @param start Item in the model where to start from
 * @param depth Current search depth
 */
void ContactList::dump( ContactListModelItem *start, int depth ) const
{
#ifdef KMESSDEBUG_CONTACTLISTMODEL
  if( ! rootNode_ )
  {
    kmDebug() << "Contact List model is empty!";
    return;
  }

  bool started = false;
  if( ! start )
  {
    start = rootNode_;
    kmDebug() << "Contact List model stats";
    kmDebug() << "--------------------------------------";
    kmDebug() << "Columns:"            << columnCount();
    kmDebug() << "Rows:"               << rowCount();
    kmDebug() << "Root group Columns:" << rootNode_->columnCount();
    kmDebug() << "Root group Rows:"    << rootNode_->childCount();
    kmDebug();
    kmDebug() << "Contact List model dump";
    kmDebug() << "--------------------------------------";
    kmDebug() << "Root @" << (void*)rootNode_;
    started = true;
  }

  QString spacer( QString().fill( '-', 2 * depth ) + ">" );
  int sortRole = ContactListModelItem::SortRole;

  // Print the current node's contents, and manage children recursively
  for( int i = 0; i < start->childCount(); i++ )
  {
    ContactListModelItem* item = start->child( i );
    int type = item->getType();

    switch( type )
    {
      case ContactListModelItem::ItemContact:
        kmDebug() << spacer << (i+1) << ") Contact:"
                           << item->data( 0 ).toMap()["handle"].toString()
                           << "(" << item->data( sortRole ).toInt() << ") "
                           "@" << (void*)item;
        break;

      case ContactListModelItem::ItemGroup:
        kmDebug() << spacer << (i+1) << ") Group:"
                           << item->data( 0 ).toMap()["id"].toString()
                           << "(" << item->data( sortRole ).toInt() << ") "
                           "@" << (void*)item;
        dump( item, depth + 1 );
        break;

      default:
        kmDebug() << spacer << "Unknown type of item: " + type << "@" << (void*)item;
        break;
    }
  }

  if( started )
  {
    kmDebug() << "--------------------------------------";
  }
#else
  Q_UNUSED( start );
  Q_UNUSED( depth );
#endif
}



// Remove gaps and duplicates in the groups list
// This is necessary to keep all group moving code working
void ContactList::ensureGroupListValid()
{
  QList<Group*> newGroupList;
  QList<Group*> matches;
  Group *group;
  int fillOffset = 0;

#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  kmDebug() << "Checking the group list against duplicates and gaps...";
  int gaps = 0, duplicates = 0;
#endif

  // fix any non-special groups with a position below 5 to have a position above 5
  // we will assume the special groups (pos 0 .. 4) are correct, since they are set by the code itself.
  for( int j = 0; j < groups_.count(); ++j )
  {
    group = groups_.at( j );
    if( group->getSortPosition() < 5 && ! group->isSpecialGroup() )
    {
#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
      kmWarning() << "Found a non-special group with sortPosition < 5, resetting to " << group->getSortPosition() + 5;
#endif
      // the part below will automatically fix any duplicates caused by this:
      group->setSortPosition( group->getSortPosition() + 5, false );
    }
    else if( group->isSpecialGroup() )
    {
      // add it to the list
      newGroupList << group;
    }
  }

  // Find the highest position (this is *not* groups_.count(), because the list can be invalid at this point)
  // highestPosition + fillOffset, below, is the highest position in the new groups list, so we can use that
  // to check if we're done below.
  int highestPosition = 0;
  for( int i = 0; i < groups_.count(); ++i)
  {
    if( !groups_.at( i )->isSpecialGroup() && groups_.at( i )->getSortPosition() > highestPosition)
    {
      highestPosition = groups_.at( i )->getSortPosition();
    }
  }

  // check for duplicates and gaps (only check non-special groups)
  for( int pos = 5; pos <= highestPosition; ++pos )
  {
    matches.clear();

    // Check sortPosition pos, is there exactly *one* group with this sortPosition?
    for( int j = 0; j < groups_.count(); ++j )
    {
      group = groups_.at( j );
      if( group->isSpecialGroup() )
      {
        // Skip special groups
        continue;
      }
      if( newGroupList.contains( group ) )
      {
        // Skip already handled groups
        continue;
      }

      if( group->getSortPosition() == pos )
      {
        matches << group;
      }
    }

    if( matches.count() == 1 )
    {
      // it's OK, just copy this group
      matches.at( 0 )->setSortPosition( pos + fillOffset, false );
      newGroupList << matches.at( 0 );
    }
    else if( matches.count() == 0 )
    {
#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
      kmDebug() << "Gap found in group list for position " << pos << ", fixing.";
      gaps++;
#endif
      // there's a gap, lower all other indices in the group list
      fillOffset--;
    }
    else
    {
#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
      kmDebug() << "Duplicate found in group list for position " << pos << ", fixing.";
      duplicates++;
#endif

      // don't increase the fillOffset for the first one
      matches.at( 0 )->setSortPosition( pos + fillOffset, false );
      newGroupList << matches.at( 0 );

      // add all the others, incrementing the index fix for every add
      for( int dup = 1; dup < matches.count(); ++dup )
      {
        highestPosition++;
        fillOffset++;
        matches.at( dup )->setSortPosition( pos + fillOffset, false );
        newGroupList << matches.at( dup );
      }
    }
  }

#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  kmDebug() << "Done checking the contact list. " << duplicates << " duplicates, " << gaps << " gaps fixed.";
#endif

  groups_.clear();
  groups_ << newGroupList;
}



// Find out if a model index contains other indices
bool ContactList::hasChildren( const QModelIndex &parent ) const
{
  if( parent.column() > 0 )
  {
    return false;
  }

  if( ! parent.isValid() )
  {
    if( rootNode_ == 0 )
    {
      return false;
    }

    return ( rootNode_->childCount() > 0 );
  }

  const ContactListModelItem *item = static_cast<ContactListModelItem*>( parent.internalPointer() );

  if( ! item )
  {
    return false;
  }

  return ( item->childCount() > 0 );
}



// Find out if the model contains a valid index at the specified position
bool ContactList::hasIndex( int row, int column, const QModelIndex &parent ) const
{
  if( ! parent.isValid() )
  {
    if( rootNode_ == 0 )
    {
      return false;
    }

    return (    ( row    >= 0 && row    < rootNode_-> childCount() )
             && ( column >= 0 && column < rootNode_->columnCount() ) );
  }

  if( parent.column() > 0 )
  {
    return false;
  }

  const ContactListModelItem *item = static_cast<ContactListModelItem*>( parent.internalPointer() );

  if( ! item )
  {
    return false;
  }

  return (    ( row    >= 0 && row    < item-> childCount() )
           && ( column >= 0 && column < item->columnCount() ) );
}



// Find out if a group contains contacts
bool ContactList::isGroupEmpty( const QString &groupId ) const
{
  ContactListModelItem *item = rootNode_->childGroup( groupId );
  if( item == 0 )
  {
    kmWarning() << "Cannot find group" << groupId;
    return false;
  }

  return ( item->childCount() <= 0 );
}



/**
 * Return the flags which characterize the index
 *
 * @param index  Points to the data to get flags of
 * @return The index's flags
 */
Qt::ItemFlags ContactList::flags( const QModelIndex &index ) const
{
  if( ! index.isValid() )
  {
    return 0;
  }

  ContactListModelItem *item = static_cast<ContactListModelItem*>( index.internalPointer() );

  if( item == 0 )
  {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "Invalid target item!";
#endif
    return 0;
  }

  const Group *group;
  bool canBeDragged = false;
  bool canBeDropped = false;
  Qt::ItemFlags indexFlags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;

  // Enable d&d only for certain kinds of items
  switch( item->getType() )
  {
    // You can d&d contacts in normal groups
    case ContactListModelItem::ItemContact:
      group = static_cast<Group*>( item->parent()->object() );

      // Dragging check: can be dragged only contacts in normal groups and in the individuals group
      if( group->isSpecialGroup() == false || group->getId() == SpecialGroups::INDIVIDUALS )
      {
        canBeDragged = true;
      }

      // Dropping check: only contacts in normal groups can be dropped on
      if( group->isSpecialGroup() == false )
      {
        canBeDragged = true;
      }
    break;

    case ContactListModelItem::ItemGroup:
      group = static_cast<Group*>( item->object() );

      // Dragging and dropping are disabled for all special groups
      if( group->isSpecialGroup() == false )
      {
        canBeDragged = true;
        canBeDropped = true;
      }
    break;

    default:
      break;
  }

  // Add the d&d flags if necessary
  if( canBeDragged )
  {
    indexFlags |= Qt::ItemIsDragEnabled;
  }
  if( canBeDropped )
  {
    indexFlags |= Qt::ItemIsDropEnabled;
  }

  return indexFlags;
}



/**
 * Emits the Data Changed signal on one item
 *
 * This is only an overloaded version for the list forwardDataChanged() method
 *
 * @param item The item which changed
 */
void ContactList::forwardDataChanged( ContactListModelItem *item )
{
  forwardDataChanged( ModelItemList() << item );
}



/**
 * Emits the Data Changed signal on some items
 *
 * @param itemList  The list of items which changed
 */
void ContactList::forwardDataChanged( ModelItemList itemList )
{
  if( itemList.isEmpty() )
  {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "Called with an empty list!";
#endif
    return;
  }

  foreach( ContactListModelItem *item, itemList )
  {
    QModelIndex changedIndex;

    if( item->parent() == rootNode_ )
    {
      changedIndex = index( item->row(), 0, QModelIndex() );
    }
    else
    {
      changedIndex = createIndex( item->row(), 0, item );
    }

/*
    // Useful debugging statements, but way too verbose
#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "--------------------------------------------------";
    kmDebug() << "Item                  :" << item;
    kmDebug() << "Root item             :" << rootNode_;
    kmDebug() << "Parent item           :" << item->parent();
    kmDebug() << "Index                 :" << changedIndex;
    kmDebug() << "Parent index          :" << changedIndex.parent();
    if( changedIndex.parent().isValid() )
      kmDebug() << "Parent index refers to:" << static_cast<ContactListModelItem*>( changedIndex.parent().internalPointer() );
    else
      kmDebug() << "Parent index refers to: Root, or a broken item";
    kmDebug() << "Parent index's parent :" << changedIndex.parent().parent();
#endif
*/

    emit dataChanged( changedIndex, changedIndex );
  }
}



// Provide a MIME representation of some of the model's indexes
QMimeData *ContactList::mimeData( const QModelIndexList &indexes ) const
{
  QStringList  addresses;
  QByteArray   encodedData;
  QMimeData   *mimeData = new QMimeData();

  QDataStream stream( &encodedData, QIODevice::WriteOnly );

  // Encode into a byte array the details about dragged items
  foreach( const QModelIndex &index, indexes )
  {
    if( ! index.isValid() )
    {
      continue;
    }

    const ContactListModelItem *item = static_cast<ContactListModelItem*>( index.internalPointer() );

    if( item == 0 )
    {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
      kmDebug() << "Invalid item!";
#endif
      continue;
    }

    ContactListModelItem::ItemType itemType = item->getType();

    if( itemType != ContactListModelItem::ItemContact
    &&  itemType != ContactListModelItem::ItemGroup )
    {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
      kmDebug() << "Non draggable item!";
#endif
      continue;
    }

#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "Preparing drag data for item" << item;
#endif

    // Save the pointer as a 64-bit unsigned integer
    stream << (quint64) item;

    if( itemType == ContactListModelItem::ItemContact )
    {
      const Contact *contact = static_cast<Contact*>( item->object() );
      if( contact )
      {
        addresses.append( contact->getHandle() );
      }
    }

    continue;
  }

  mimeData->setData( "application/kmess.list.item", encodedData );

  // External apps may want these. A better format should be used, if needed
  if( addresses.count() > 0 )
  {
    mimeData->setText( addresses.join( "; " ) );
  }

  return mimeData;
}



// Return the supported drag&drop mimetypes
QStringList ContactList::mimeTypes() const
 {
  return QStringList() << "application/kmess.list.item";
 }



// Return the contact with the given guid
Contact* ContactList::getContactByGuid( QString guid ) const
{
  // No need to optimize, only called for ADC en REM now.
  foreach( Contact *contact, contacts_ )
  {
    if(contact->getGuid() == guid)
    {
      return contact;
    }
  }

  return 0;
}



// Return the contact with the given handle
Contact* ContactList::getContactByHandle( QString handle ) const
{
  // Optimized with hashing because it's called a lot
  return contacts_.value( handle.toLower(), 0 );
}



// Return the list of contacts
const QHash<QString,Contact*>& ContactList::getContactList() const
{
  return contacts_;
}



// Return the group with the given id
Group* ContactList::getGroupById( QString groupId ) const
{
  foreach( Group *group, groups_ )
  {
    if( group->getId() == groupId )
    {
      return group;
    }
  }

  return 0;
}



// Return the group with the given name
Group* ContactList::getGroupByName( QString name ) const
{
  foreach( Group *group, groups_ )
  {
    if( group->getName() == name )
    {
      return group;
    }
  }

  return 0;
}



// Return the group with the given sort position
// because of ensureGroupListValid(), there will be no gaps, so there will always
// be a group with given sort position.
Group* ContactList::getGroupBySortPosition( int sortPosition, bool next ) const
{
  Q_UNUSED( next );

  foreach( Group *group, groups_ )
  {
    if ( group->getSortPosition() == sortPosition )
    {
      return group;
    }
  }

  return 0;
}



// Return the list of groups
const QList<Group*>& ContactList::getGroupList() const
{
  return groups_;
}



// Return whether or not a group with the given id exists
bool ContactList::groupExists( QString groupId )
{
  return getGroupById( groupId ) != 0;
}



/**
 * Return the text for a list or tree column header.
 *
 * Currently unused. We don't use headers in our model - we may end up needing them
 * when another model view will show up.
 *
 * @see http://doc.trolltech.com/4.3/qt.html#ItemDataRole-enum for available roles
 *
 * @param section Row or column of the header text to return
 * @param orientation Whether the headers are horizontal or vertical
 * @param role   Indicates what type of text needs to be obtained.
 * @return A QVariant version of the text
 */
QVariant ContactList::headerData( int section, Qt::Orientation orientation, int role ) const
{
  Q_UNUSED( section );
  Q_UNUSED( orientation );
  Q_UNUSED( role );

  return QVariant();
}



/**
 * Obtain a model index for a specifically located item
 *
 * @param row     Row of the item
 * @param column  Column of the item
 * @param parent  Parent of the model which contains the row and column. If invalid, the root group is used.
 * @return        An index for the specified item, or an invalid index if it hasn't been found
 */
QModelIndex ContactList::index( int row, int column, const QModelIndex &parent ) const
{
  if( ! hasIndex( row, column, parent ) || ! rootNode_ )
  {
    return QModelIndex();
  }

  ContactListModelItem *parentItem;

  if( ! parent.isValid() )
  {
    parentItem = rootNode_;
  }
  else
  {
    parentItem = static_cast<ContactListModelItem*>( parent.internalPointer() );
  }

  ContactListModelItem *childItem = parentItem->child( row );
  if( childItem )
  {
    return createIndex( row, column, childItem );
  }
  else
  {
    return QModelIndex();
  }
}



/**
 * Obtain a model index's parent index
 *
 * @param index  Points to the item to find the parent of
 * @return       An index for the parent of the specified item, or an invalid index if it hasn't been found
 */
QModelIndex ContactList::parent( const QModelIndex &index ) const
{
  if( ! index.isValid() )
  {
    return QModelIndex();
  }

  ContactListModelItem *item = static_cast<ContactListModelItem*>( index.internalPointer() );

  if( ! item || item == rootNode_ )
  {
    return QModelIndex();
  }

  ContactListModelItem *parentItem = item->parent();

  if( ! parentItem || parentItem == rootNode_ )
  {
    return QModelIndex();
  }

  return createIndex( parentItem->row(), 0, parentItem );
}



/**
 * Apply to the model a contact's movement through lists and groups
 *
 * This method is called when a contact changes between two lists, for example from blocked to allowed;
 * and when changes group, for example when it is moved or copied from a group to another.
 * First it identifies lists and groups where the contact was and will be; then the changes are applied.
 * The contact's list items are then iteratively moved from the old to the new lists/groups, and any
 * remaining addition and removal is performed.
 *
 * @param contact  The contact to update in the model
 */
void ContactList::slotContactMoved( Contact *contact )
{
  const QStringList& groupIds = contact->getGroupIds();
  const QString& handle = contact->getHandle();

  // Add the current groups to the nodes in which the contact should now be visible
  ModelItemList newNodes;
  foreach( const QString &groupId, groupIds )
  {
    ContactListModelItem *item = rootNode_->childGroup( groupId );
    if( item )
    {
      newNodes << item;
    }
  }

  // Add the current lists to the nodes in which the contact should now be visible
  if( contact->isFriend() )
  {
    // Friends with no groups are displayed in the Individuals group
    if( groupIds.isEmpty() )
    {
      newNodes << rootNode_->childGroup( SpecialGroups::INDIVIDUALS );
    }

    if( contact->isOnline() )
    {
      newNodes << rootNode_->childGroup( SpecialGroups::ONLINE );
    }
    else
    {
      newNodes << rootNode_->childGroup( SpecialGroups::OFFLINE );
    }
  }
  else
  {
    if( contact->isAllowed() )
    {
      newNodes << rootNode_->childGroup( SpecialGroups::ALLOWED );
    }
    else
    {
      newNodes << rootNode_->childGroup( SpecialGroups::REMOVED );
    }
  }

  ModelItemList allGroups( rootNode_->childGroups() );
  foreach( ContactListModelItem *node, allGroups )
  {
    // If new nodes doesn't contain current node, remove contact from it if it's in.
    if( ! newNodes.contains( node ) )
    {
      // If we found the contact, remove it
      if( node->childContact( handle ) )
      {
        removeContactFromGroup( handle, node );
      }
    }
    else
    {
      if( ! node->childContact( handle ) )
      {
        const QModelIndex groupIndex( index( node->row(), 0, QModelIndex() ) );

        beginInsertRows( groupIndex, node->childCount(), node->childCount() );

        new ContactListModelItem( contact, node );

        endInsertRows();
      }
      // else leave it
    }
  }
}



// Forward from a group that it has moved
void ContactList::slotGroupMoved( Group *group, int oldPosition, int newPosition )
{
  if( oldPosition == newPosition )
  {
#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
    kmDebug() << "A group moved to the same location, skipping handling.";
#endif
    return;
  }

  // Find out which group was replaced by this one
  // Can't use getGroupBySortPosition, because the group list contains a duplicate at this point;
  // this is a FIXME/XXX/TODO/HACK/OMG for 2.1, when we split up ContactList!
  Group *oldGroup = 0;
  for( int i = 0; i < groups_.size(); ++i )
  {
    Group *itGroup = groups_.at(i);
    if( itGroup->getSortPosition() == newPosition
     && itGroup != group )
    {
      oldGroup = itGroup;
      break;
    }
  }

  // If this group doesn't exist, maybe it was just swapped?
  if( oldGroup == 0 )
  {
    for( int i = 0; i < groups_.size(); ++i )
    {
      Group *itGroup = groups_.at(i);
      if( itGroup->getOldSortPosition() == newPosition
       && itGroup != group )
      {
        oldGroup = itGroup;
        break;
      }
    }
  }

  if( KMESS_NULL( oldGroup ) )
  {
    // This happens when newPosition > groups_.count(), i.e., invalid moving code
    kmWarning() << "Couldn't find a group with index " << newPosition << "!";
    return;
  }

#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  kmDebug() << "Moving group" << group->getName() << "(position" << oldPosition << ")"
    << "to position" << newPosition << "(currently group" << oldGroup->getName() << ")";
#endif

  // Signal that the groups have changed
  ContactListModelItem *newPosItem = rootNode_->childGroup(    group->getId() );
  ContactListModelItem *oldPosItem = rootNode_->childGroup( oldGroup->getId() );

  int newPosRow = newPosItem->row();
  int oldPosRow = oldPosItem->row();

  QModelIndex newPosIndex( index( newPosRow, 0, QModelIndex() ) );
  QModelIndex oldPosIndex( index( oldPosRow, 0, QModelIndex() ) );

  if( oldPosRow < newPosRow )
  {
    emit dataChanged( oldPosIndex, newPosIndex );
  }
  else
  {
    emit dataChanged( newPosIndex, oldPosIndex );
  }
}



// Specify which drag and drop operations are supported
Qt::DropActions ContactList::supportedDropActions() const
{
  return Qt::CopyAction | Qt::MoveAction;
}



// Forward from a contact that it is offline
void ContactList::slotForwardContactOffline(Contact *contact, bool showBaloon)
{
#ifdef KMESSTEST
  KMESS_ASSERT( contact != 0 );
#endif

#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  if ( contact != 0 )
  {
    kmDebug() << "Forwarding that " << contact->getHandle() << " went offline.";
  }
#endif

  emit contactOffline( contact, showBaloon );
}



// Forward from a contact that it is online
void ContactList::slotForwardContactOnline(Contact *contact, bool showBaloon)
{
#ifdef KMESSTEST
  KMESS_ASSERT( contact != 0 );
#endif

#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  if ( contact != 0 )
  {
    kmDebug() << "Forwarding that " << contact->getHandle() << " went online.";
  }
#endif

  emit contactOnline( contact, showBaloon );
}



// Remove a contact from the list completely
void ContactList::removeContact( QString contactId )
{
  Contact *contact = getContactByGuid( contactId );

  if( contact == 0 )
  {
    kmWarning() << "Contact" << contactId << "doesn't exist in the contact list.";
    return;
  }

  // Go through the contact's groups and remove the associated
  // modelitem for the contact.
  foreach( const QString& groupId, contact->getGroupIds() )
  {
    removeContactFromGroup( contact->getHandle(), rootNode_->childGroup( groupId ) );
  }

  // remove from internal list of contacts.
  contacts_.remove( contact->getHandle() );

  // Signal the deletion to the UI
  emit contactRemoved( contact );

  // When all receivers have deleted the contact from their lists, we delete it too
  delete contact;
}


// remove a contact from a group properly.
void ContactList::removeContactFromGroup ( const QString& handle, ContactListModelItem *groupItem )
{
  if ( ! groupItem )
  {
    kmWarning() << "Invalid group";
    return;
  }

  const QModelIndex groupIndex( index(groupItem->row(), 0, QModelIndex() ) );
  if ( !groupIndex.isValid() )
  {
    kmWarning() << "Group does not exist in the contact list";
    return;
  }

  // Find the child contact
  ContactListModelItem *contactItem = groupItem->childContact( handle );

  if ( !contactItem )
  {
    kmWarning() << "Contact/group mismatch";
    return;
  }

  // Now we can remove the contact
  beginRemoveRows( groupIndex, contactItem->row(), contactItem->row() );

  delete contactItem;

  endRemoveRows();
}



// Remove a group
void ContactList::removeGroup( const QString& groupId )
{
  Group *group = getGroupById( groupId );
  ContactListModelItem *nodeToRemove = rootNode_->childGroup( groupId );

  if ( group == 0 || nodeToRemove == 0 )
  {
    kmWarning() << "Group" << groupId << "doesn't exist in the contact list.";
    return;
  }

  if( nodeToRemove->childCount() )
  {
    kmWarning() << "Group" << groupId << "was not empty!";
    return;
  }

  ContactListModelItem *gropNode = rootNode_->childGroup( groupId );
  if ( ! gropNode )
  {
    kmWarning() << "Group " << groupId << " does not exist in the model!";
    return;
  }

  beginRemoveRows( QModelIndex(), gropNode->row(), gropNode->row() );

  // Remove the group
  delete nodeToRemove;

  // Signal the group has been removed.
  endRemoveRows();

  // Also delete it from the internal list of groups
  groups_.removeAll( group );

  // Signal the deletion to the UI
  emit groupRemoved( group );

  // When all receivers have deleted the group from their lists, we delete it too
  delete group;
}



// Change a contact's name
void ContactList::renameContact( QString handle, QString newName )
{
  Contact *contact;

  contact = getContactByHandle( handle );
  if ( contact == 0 )
  {
    kmDebug() << "Couldn't find contact " << handle << " in the contact list.";
    return;
  }

  contact->setFriendlyName( newName );

  // If the contact exists in the list, signal that it's changed
  forwardDataChanged( rootNode_->childContacts( handle ) );
}



// Change a group's name
void ContactList::renameGroup( const QString& groupId, const QString& groupName )
{
  Group *group = getGroupById( groupId );
  if( group == 0 )
  {
    kmWarning() << "Group" << groupId << "not found in the contact list.";
    return;
  }

  group->setName( groupName );

  // If the group exists in the list, signal that it's changed
  forwardDataChanged( rootNode_->childGroup( groupId ) );

  emit groupChanged( group ); // Signal the UI
}



/**
 * Empty the contact list
 *
 * Deletes the contact list's contents and regenerates the special groups and the model.
 * When exiting, the regeneration part can be skipped.
 */
void ContactList::reset( bool restore )
{
  // Empty the model structure and reset it
  if( rootNode_ )
  {
#ifdef KMESSDEBUG_CONTACTLISTMODEL
    kmDebug() << "Resetting model tree...";
#endif

    // Delete everything from the model tree
    int childItemsCount = rootNode_->childCount();

    emit layoutAboutToBeChanged();
    for( int row = 0; row < childItemsCount; row++ )
    {
      ContactListModelItem *item = rootNode_->child( row );
      if( item != 0 )
      {
        continue;
      }
      int childItemsCount2 = item->childCount();
      for( int row2 = 0; row2 < childItemsCount2; row2++ )
      {
        delete item->child( row2 );
      }
    }
    emit layoutChanged();

    // Delete the root node
    delete rootNode_;
    rootNode_ = 0;
  }

  if( contacts_.count() || groups_.count() )
  {
#ifdef KMESSDEBUG_CONTACTLIST_DISCONNECTION
    kmDebug() << "Resetting" << contacts_.count() << "contacts...";
#endif

    // Delete all contacts
    qDeleteAll( contacts_ );
    contacts_.clear();

#ifdef KMESSDEBUG_CONTACTLIST_DISCONNECTION
    kmDebug() << "Resetting" << groups_.count() << "groups...";
#endif

    // Delete all groups
    qDeleteAll( groups_ );
    groups_.clear();
  }

  // Do not recreate the groups
  if( ! restore )
  {
    return;
  }

  // Regenerate the model's root node, the only one which contains groups and contacts
  rootNode_ = new ContactListModelItem();

  // Create the special groups
  addGroup( SpecialGroups::INDIVIDUALS, i18n("Individuals") );
  addGroup( SpecialGroups::ONLINE,      i18n("Online")      );
  addGroup( SpecialGroups::OFFLINE,     i18n("Offline")     );
  addGroup( SpecialGroups::ALLOWED,     i18n("Allowed")     );
  addGroup( SpecialGroups::REMOVED,     i18n("Removed")     );

#ifdef KMESSTEST
  KMESS_ASSERT( groups_.count() == 5 );
  KMESS_ASSERT( rootNode_->childCount() == 5 );
#endif
}



// Read groups and contacts properties
void ContactList::readProperties()
{
#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  kmDebug() << "Loading properties for the contact list";
#endif

  const QString& handle = CurrentAccount::instance()->getHandle();
  KConfigGroup config( KMessConfig::instance()->getContactListConfig( handle, "Groups" ) );

  // Load the settings for all groups
  for( int i = 0; i < groups_.count(); i++ )
  {
    groups_.value( i )->readProperties( config );
  }

  ensureGroupListValid();
  // From this point on, we can assume there are *no* duplicate sortPositions
  // and *no* gaps in the sortPositions in groups_.

  // Flush any changes to disk
  config.sync();
  config = KMessConfig::instance()->getContactListConfig( handle, "Contacts" );

  // Load all contact extensions
  foreach( Contact *contact, contacts_ )
  {
    contact->readProperties( config );
  }

  config.sync();
}



/**
 * Get the number of rows present in an index
 *
 * @param parent The index to count rows of
 * @return int
 */
int ContactList::rowCount( const QModelIndex &parent ) const
{
  ContactListModelItem *parentItem = 0;

  if( rootNode_ == 0 )
  {
    return 0;
  }
  if( ! parent.isValid() )
  {
    parentItem = rootNode_;
  }
  else
  {
    parentItem = static_cast<ContactListModelItem*>( parent.internalPointer() );
  }

  if( ! parentItem )
  {
    return 0;
  }

  return parentItem->childCount();
}



// Save groups and contacts properties
void ContactList::saveProperties()
{
#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  kmDebug() << "Saving properties for the contact list";
#endif

  // Do not save account properties when it's a temporary account
  if( CurrentAccount::instance()->isGuestAccount() )
  {
    kmWarning() << "Requested to save properties, but this is a guest account!";
    return;
  }

  // Get the position of the contacts settings of this account
  const QString& handle = CurrentAccount::instance()->getHandle();

  // There always are at least some special groups to be saved: we can never skip saving
  // like it's done with contacts below
  KConfigGroup config( KMessConfig::instance()->getContactListConfig( handle, "Groups" ) );

  // Save all groups
  foreach( Group *group, groups_ )
  {
    group->saveProperties( config );
  }

  // Write the data now!
  config.sync();

  // Skip saving contacts if there aren't any, as above
  if( ! contacts_.isEmpty() )
  {
    config = KMessConfig::instance()->getContactListConfig( handle, "Contacts" );

    // We want to delete unused contact info; so here we get a list of all contacts and remove
    // the used ones. Then we remove what's left in this list
    QStringList pendingRemoval( config.groupList() );

    // Save all contact extensions
    foreach( Contact *contact, contacts_ )
    {
      pendingRemoval.removeAll( contact->getHandle() );
      contact->saveProperties( config );
    }

    // Delete the remaining contacts info, which at this point are only old contacts not in list anymore
    QFile picture;
    KConfigGroup contactConfig;
    foreach( const QString &unusedContact, pendingRemoval )
    {
  #ifdef KMESSDEBUG_CONTACTLIST_GENERAL
      kmDebug() << "Deleting unused contact info for " << unusedContact;
  #endif

      // Remove the custom picture associated with this contact, if there is any
      contactConfig = config.group( unusedContact );
      picture.setFileName( contactConfig.readEntry( "pictureFile", "" ) );
      if( picture.exists() )
      {
        picture.remove();
      }
      config.deleteGroup( unusedContact );
    }

    // Write the data now!
    config.sync();
  }
#ifdef KMESSDEBUG_CONTACTLIST_GENERAL
  else
  {
  kmDebug() << "No contacts to save";
  }
#endif
}



// Save the properties of a group
void ContactList::saveProperties( const Group *group ) const
{
  // Do not save account properties when it's a temporary account
  if( CurrentAccount::instance()->isGuestAccount() )
  {
    return;
  }

  const QString& handle = CurrentAccount::instance()->getHandle();
  KConfigGroup config( KMessConfig::instance()->getContactListConfig( handle, "Groups" ) );

  // Save the group, by removing the const
  Group *internalGroup = const_cast<Group*>( group );
  internalGroup->saveProperties( config );

  // Write the data now!
  config.sync();
}



// Save the properties of a contact
void ContactList::saveProperties( const Contact *contact ) const
{
  // Do not save account properties when it's a temporary account
  if( CurrentAccount::instance()->isGuestAccount() )
  {
    return;
  }

  const QString& handle = CurrentAccount::instance()->getHandle();
  KConfigGroup config( KMessConfig::instance()->getContactListConfig( handle, "Contacts" ) );

  // Save the contact
  Contact *internalContact = const_cast<Contact*>( contact );
  internalContact->saveProperties( config );

  // Write the data now!
  config.sync();
}



#include "contactlist.moc"
