/***************************************************************************
                          contact.cpp  -  description
                             -------------------
    begin                : Sun Jan 5 2003
    copyright            : (C) 2003 by Mike K. Bennett
    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 "contact.h"

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


#ifdef KMESSDEBUG_CONTACT
  #define KMESSDEBUG_CONTACT_GENERAL
#endif



// The constructor
Contact::Contact( const QString& handle, const QString& friendlyName, int lists,
                  const QStringList& groupIds, const QString& guid )
 : ContactBase(handle, friendlyName),
   allowed_(false),
   blocked_(false),
   friend_(false),
   groupIds_(groupIds),
   guid_(guid),
   lastStatus_(STATUS_OFFLINE),
   movingFrom_(""),
   msnObject_(0),
   pending_(false),
   reverse_(false),
   status_(STATUS_OFFLINE)
{
#ifdef KMESSTEST
  KMESS_ASSERT( ! handle_.isEmpty() );
  KMESS_ASSERT( ! friendlyName_.getOriginal().isEmpty() );
  KMESS_ASSERT( lists != 0 );
#endif

  if(lists & MSN_LIST_FRIEND)  friend_  = true;
  if(lists & MSN_LIST_ALLOWED) allowed_ = true;
  if(lists & MSN_LIST_BLOCKED) blocked_ = true;
  if(lists & MSN_LIST_REVERSE) reverse_ = true;
  if(lists & MSN_LIST_PENDING) pending_ = true;

  // The extension stores additional properties not found in
  // the official client/protocol, but only found in KMess.
  // Pictures are stored there because you can assign one
  // (KMess implemented this before the official client supported
  // msnobject transfers). Names can be aliased, which is why there're
  // also there.
  extension_ = new ContactExtension( this );

  // Relay some signals needed externally
  connect ( extension_, SIGNAL( changedFriendlyName() ),
            this,       SIGNAL( changedFriendlyName() ) );
  connect ( extension_, SIGNAL(      changedPicture() ),
            this,       SLOT  (  slotChangedPicture() ) );
            
  // need to know when the emoticon theme changes
  // so we can re-parse our friendly name and personal message
  // and update the smileys there.
  connect ( CurrentAccount::instance(), SIGNAL( changedEmoticonSettings() ),
            this,                       SLOT  ( slotEmoticonSettingsChanged() ) );
}



// The destructor
Contact::~Contact()
{
#ifdef KMESSDEBUG_CONTACT_GENERAL
  kDebug() << "DESTROYED. [handle=" << handle_ << "]";
#endif

  delete extension_;
  if(msnObject_) delete msnObject_;
}



// Add the groupID to the list of current groups
void Contact::addGroupId(const QString &groupId)
{
  // Protect against invalid use, causes problems in KMessView.
  if( groupId.isEmpty() )
  {
    kWarning() << "Group-ID is empty (contact=" << handle_ << ")!";
    return;
  }
  // Check whether contact is already added to the group.
  if( groupIds_.contains(groupId) )
  {
    kWarning() << handle_ << " is already a member of group " << groupId << ".";
    return;
  }

  // Append to groups
  groupIds_.append(groupId);

#ifdef KMESSDEBUG_CONTACT_GENERAL
  kDebug() << "groups of " << handle_ << " are '" << groupIds_.join(",") << "'";
#endif

  emit changedGroup(this);
}



// Return the path to the contact's picture
const QString Contact::getContactPicturePath() const
{
  // Check before if there is some alternative contact's picture
  QString picturePath( extension_->getContactAlternativePicturePath() );

  if( ! picturePath.isEmpty() )
  {
    return picturePath;
  }

  // Check for contact picture ( sended by msn object )
  picturePath = extension_->getContactPicturePath();

  if( ! picturePath.isEmpty() )
  {
    return picturePath;
  }

  // else return the default contact picture path
  return getContactDefaultPicturePath();
}



// return the scaled display picture for this contact.
// this is used for display in the contact list.
QPixmap & Contact::getScaledDisplayPicture()
{
  int dpSize = CurrentAccount::instance()->getListPictureSize();
  if ( dpSize == scaledDPSize_ && ! scaledDisplayPicture_.isNull() )
  {
    return scaledDisplayPicture_;
  }
  
  // here either the scaling size has changed or we have no cache.
  scaledDisplayPicture_.load( getContactPicturePath() );
  scaledDisplayPicture_ = scaledDisplayPicture_.scaled( dpSize, dpSize, Qt::KeepAspectRatio, Qt::SmoothTransformation );
  
  scaledDPSize_ = dpSize;
  
  return scaledDisplayPicture_;
}



// Return the current media type
const QString & Contact::getCurrentMediaType() const
{
  return currentMediaType_;
}



// Return the current media type
const QString & Contact::getCurrentMediaString() const
{
  return currentMediaString_;
}



// Return a pointer to the extension class
ContactExtension *Contact::getExtension() const
{
  return extension_;
}


// Return the contact's friendly name
const QString& Contact::getFriendlyName( FormattingMode mode ) const
{
  // Check if alternative friendly name is set
  if( extension_->getUseAlternativeName() )
  {
    return extension_->getAlternativeName( mode );
  }
  // Else return the string contained in friendlyName
  return friendlyName_.getString( mode );
}



// Return the contact's previous status
Status Contact::getLastStatus() const
{
  return lastStatus_;
}



// Return the contact's globally unique identifier
const QString &Contact::getGuid() const
{
  return guid_;
}



// Return the group ID's the contact is added to
const QStringList& Contact::getGroupIds() const
{
  return groupIds_;
}



// Return the contact's information
const QVariant Contact::getInformation( const QString &information ) const
{
  return informations_.value( information, QVariant() );
}


// Return the MSNObject
const MsnObject* Contact::getMsnObject() const
{
  return msnObject_;
}



// Return the contact's personal message
const QString & Contact::getPersonalMessage( FormattingMode mode ) const
{
  return personalMessage_.getString( mode );
}



// Return the contact's status
Status Contact::getStatus() const
{
  return status_;
}



// Return the contact's true friendly name, regardless of extension
const QString& Contact::getTrueFriendlyName( FormattingMode mode ) const
{
  return friendlyName_.getString( mode );
}



// Whether or not the contact is allowed
bool Contact::isAllowed() const
{
  return allowed_;
}



// Whether or not the contact is blocked
bool Contact::isBlocked() const
{
  return blocked_;
}



// Whether or not the contact is on the friends list
bool Contact::isFriend() const
{
  return friend_;
}



// Whether or not the contact is on the pending list
bool Contact::isPending() const
{
  return pending_;
}



// Whether or not the contact is on the reverse list
bool Contact::isReverse() const
{
  return reverse_;
}



// Setup and load an MSN Object
void Contact::loadMsnObject( QString msnObject )
{
  // Contact removed his picture.
  if( msnObject.isEmpty() )
  {
#ifdef KMESSDEBUG_CONTACT_GENERAL
    kDebug() << "Contact " << getHandle() << " - MSN Object Removed!";
#endif

    delete msnObject_;
    msnObject_ = 0;
    emit changedMsnObject( this );
  }

  // Contact set his picture, or changed it.
  if( msnObject_ )
  {
    // Actually not changed...
    if( ! msnObject_->hasChanged( msnObject ) )
    {
      return;
    }

    // Delete previous object.
    delete msnObject_;
    msnObject_= 0;
  }

#ifdef KMESSDEBUG_CONTACT_GENERAL
  kDebug() << "Contact " << getHandle() << " - MSN Object Changed!";
#endif

  msnObject_ = new MsnObject( msnObject );
  emit changedMsnObject( this );
}



// clear contact dp cache.
void Contact::slotChangedPicture()
{
  scaledDisplayPicture_ = QPixmap();
  emit changedPicture();
}



// re-parse the friendly name and personal message
void Contact::slotEmoticonSettingsChanged()
{
  friendlyName_.setString( friendlyName_.getOriginal() );
  personalMessage_.setString( personalMessage_.getOriginal() );
}



// Load extension settings for a contact
void Contact::readProperties( const KConfigGroup &config )
{
  // Get the configuration group where this contact's data is stored
  KConfigGroup contactConfig = config.group( handle_ );

  // Restore cached state of this class
  
  // the data that comes from the address book service (as of 14 Oct 09) 
  // is rubbish - the friendly name is totally out of date or missing.
  // cache it here. it will get overwritten the next time the contact comes online,
  // then saved upon kmess close.
  QString cachedFriendlyName = contactConfig.readEntry( "lastFriendlyName", QString() );
  if ( ! cachedFriendlyName.isEmpty() )
  {
    friendlyName_           = cachedFriendlyName;
  }
  
  capabilities_             = contactConfig.readEntry( "capabilities", 0 );
  clientName_               = contactConfig.readEntry( "clientName", QString() );
  clientFullName_           = contactConfig.readEntry( "clientFullName", QString() );
  personalMessage_.setString( contactConfig.readEntry( "lastPersonalMessage", "" ) );
  emoticonBlackList_        = contactConfig.readEntry( "emoticonBlackList", QStringList() );

  // TODO: restore more state variables here, to implement contact list caching.

  // Restore extension
  extension_->readProperties( contactConfig );
}



// Remove the group from the list of group IDs
void Contact::removeGroupId( const QString &groupId )
{
  int removeCount = groupIds_.removeAll( groupId );
  if(removeCount == 0)
  {
    kWarning() << handle_ << " was not registered to group " << groupId << ".";
    return;
  }

#ifdef KMESSDEBUG_CONTACT_GENERAL
  kDebug() << "groups of " << handle_ << " are '" << groupIds_.join(",") << "'";
#endif

  emit changedGroup( this );
}



// Save extension settings for a contact
void Contact::saveProperties( KConfigGroup &config )
{
  // Get the configuration group where this contact's data is stored
  KConfigGroup contactConfig = config.group( handle_ );

  // Save state of this class
  contactConfig.writeEntry( "lastFriendlyName",     friendlyName_.getOriginal() );
  contactConfig.writeEntry( "capabilities",         getCapabilities() );
  contactConfig.writeEntry( "clientName",           getClientName( false ) );
  contactConfig.writeEntry( "clientFullName",       getClientFullName() );
  contactConfig.writeEntry( "lastPersonalMessage",  getPersonalMessage( STRING_ORIGINAL ) );
  contactConfig.writeEntry( "emoticonBlackList",    getEmoticonBlackList() );

  // store extension
  extension_->saveProperties( contactConfig );
}



// Set whether or not the contact is allowed
void Contact::setAllowed( bool allowed )
{
  if( allowed_ != allowed )
  {
    allowed_ = allowed;
    emit changedList(this);
  }
}



// Set whether or not the contact is blocked
void Contact::setBlocked( bool blocked )
{
  if( blocked_ != blocked )
  {
    blocked_ = blocked;
    emit changedList(this);
  }
}



// Set whether or not the contact is on the friends list
void Contact::setFriend( bool isFriend )
{
  if( friend_ != isFriend )
  {
    friend_ = isFriend;

    // If the contact is no longer at the FL, remove from all groups
    if(! isFriend)
    {
      groupIds_.clear();
      setStatus( STATUS_OFFLINE, false );
    }

    emit changedList(this);
  }
}



// Change the contact's friendly name
void Contact::setFriendlyName( QString newName )
{
  if( newName.isEmpty() )
  {
    kWarning() << "Not setting the friendly name of " << handle_ << " to an empty value (previously: " << friendlyName_.getCleaned() << ")";
    return;
  }

  friendlyName_.setString( newName );

  emit changedFriendlyName();
}



// Change the GUID of the contact, this is only known when the contact is added to the friends list.
void Contact::setGuid( const QString &guid )
{
  guid_ = guid;
}



// Set the extra information retrieved by soap response
void Contact::setInformations( const QHash<QString, QVariant> &informations )
{
  informations_ = informations;
}



// Set wether or not the contact is on pending list
void Contact::setPending( bool isPending )
{
  pending_ = isPending;
}



// Set the personal message of the contact
void Contact::setPersonalStatus( const QString &message, const QString &mediaType, const QString &mediaString )
{
  // If no information is changed
  if( personalMessage_.getOriginal() == message
  &&  currentMediaType_              == mediaType
  &&  currentMediaString_            == mediaString )
  {
    return;
  }

  currentMediaType_   = mediaType;
  currentMediaString_ = mediaString;
  personalMessage_.setString( message );

  emit changedPersonalMessage(this);
}



// Set whether or not the contact is on the reverse list
void Contact::setReverse( bool reverse )
{
  if( reverse_ != reverse )
  {
    reverse_ = reverse;
    emit changedList(this);
  }
}



// Set the contact's status
void Contact::setStatus( const Status newStatus, bool showBaloon )
{
  if( status_ == newStatus )
  {
    return;
  }

  lastStatus_ = status_;
  status_     = newStatus;

#ifdef KMESSDEBUG_CONTACT_GENERAL
  kDebug() << "Contact " << handle_ << " went from " << MsnStatus::getCode( lastStatus_ )
           << " to " << MsnStatus::getCode( status_ ) << ".";
#endif

  // Signal the UI
  emit changedStatus();

  // Check if the contact went offline
  if( status_ == STATUS_OFFLINE && lastStatus_ != STATUS_OFFLINE )
  {
#ifdef KMESSDEBUG_CONTACT_GENERAL
    kDebug() << "Contact went offline.";
#endif
    emit contactOffline( this, showBaloon );
  }
  // Check if the contact went online
  else if( status_ != STATUS_OFFLINE && lastStatus_ == STATUS_OFFLINE )
  {
    // showBaloon is here to avoid showing notification when we connect
#ifdef KMESSDEBUG_CONTACT_GENERAL
    kDebug() << "Contact went online.";
#endif
    emit contactOnline( this, showBaloon );
  }
}

#include "contact.moc"
