/***************************************************************************
                          msnnotificationconnection.cpp  -  description
                             -------------------
    begin                : Thu Jan 23 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 "msnnotificationconnection.h"

#include "../contact/contact.h"
#include "../model/contactlist.h"
#include "../utils/kmessshared.h"
#include "../currentaccount.h"
#include "../kmess.h"
#include "../kmessapplication.h"
#include "../kmessdebug.h"
#include "../kmessinterface.h"
#include "soap/addressbookservice.h"
#include "soap/passportloginservice.h"
#include "soap/offlineimservice.h"
#include "soap/roamingservice.h"
#include "utils/kmessconfig.h"
#include "chatmessage.h"
#include "mimemessage.h"
#include "msnchallengehandler.h"

#include <QTextDocument>  // for Qt::escape()
#include <QUrl>
#include <QtEndian>
#include <QDateTime>
#include <QNetworkProxy>

#include <KDialog>
#include <KLocale>
#include <KMessageBox>

#include <gcrypt.h>

#ifdef KMESSDEBUG_CONTACTLISTMODELTEST
  #include "../../tests/modeltest/modeltest.h"
#endif

#ifdef KMESSDEBUG_NOTIFICATION
  #define KMESSDEBUG_NOTIFICATION_GENERAL
//   #define KMESSDEBUG_NOTIFICATION_AUTH
//   #define KMESSDEBUG_NOTIFICATION_MESSAGES
#endif

// Settings for debugging.
#define NOTIFICATION_HIDE_P2P_SUPPORT 0

/**
 * Maximum allowed time between unique server responses
 *
 * KMess will wait at most this number of milliseconds after receiving a server command
 * (any command, in fact). If nothing arrives and this timeout expires, the login process
 * or the connection will be aborted.
 */
#define NOTIFICATION_COMMAND_INTERVAL_TIMEOUT     30000

/**
 * @brief The constructor
 *
 * Initializes the default groups of the ContactList class.
 */
MsnNotificationConnection::MsnNotificationConnection()
 : MsnConnection( MsnSocketBase::SERVER_NOTIFICATION ),
   addressBookService_(0),
   connected_(false),
   currentAccount_(0),
   enableNotifications_(false),
   goneOnline_(false),
   initialized_(false),
   isInitiatingConnection_(false),
   lastStatus_(-1),
   offlineImService_(0),
   passportLoginService_(0),
   roamingService_(0),
   initialStatusSent_( false )
{
  setObjectName( "MsnNotificationConnection" );

  // We manage the contact list
  contactList_ = new ContactList();
  // TODO post 2.0: replace with proper impl
  connect( contactList_, SIGNAL( dndMoveContactToGroup(QString,QString,QString) ),
           this,         SLOT  (           moveContact(QString,QString,QString) ) );

#ifdef KMESSDEBUG_CONTACTLISTMODELTEST
  // Start the Contact List model tests
  new ModelTest( contactList_ );
#endif

  // Periodically close expired switchboard requests
  connect( this,         SIGNAL( pingSent() ),
           this,         SLOT  ( checkSwitchboardsTimeout() ) );

  // Attach a timer to watch over the login process
  loginTimer_.stop();
  loginTimer_.setSingleShot( true );
  connect( &loginTimer_, SIGNAL( timeout() ),
           this,         SLOT  ( slotAuthenticationFailed() ) );

  // Setup the timer to log in immediately if the SOAP requests take too long
  friendlyNameWaitTimer_.stop();
  friendlyNameWaitTimer_.setSingleShot( true );
  connect( &friendlyNameWaitTimer_, SIGNAL( timeout() ),
           this,                    SLOT(  goOnline() ) );

  notificationUnlockTimer_.setSingleShot( true );
  connect( &notificationUnlockTimer_, SIGNAL( timeout() ), this, SLOT( unlockNotifications() ) );
}



/**
 * @brief The destructor
 *
 * Deletes the contact list.
 * The base class closes the connection.
 */
MsnNotificationConnection::~MsnNotificationConnection()
{
  delete contactList_;

  // Remove all open requests.
  qDeleteAll( openRequests_ );
  openRequests_.clear();

  // Remove all offline im messages
  qDeleteAll( offlineImMessages_ );
  offlineImMessages_.clear();

  // closeConnection() also cleans up a lot too.

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



/**
 * @brief Add a contact to another group.
 *
 * It's possible to add a contact to multiple groups,
 * typically referred to as "copy contact to group..".
 *
 * @param  handle   The contact handle.
 * @param  groupId  The group ID.
 */
void MsnNotificationConnection::addContactToGroup(QString handle, QString groupId)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "adding '" << handle << " to '" << groupId << "'.";
#endif

  AddressBookService *addressBook = createAddressBookService();
  Contact *contact = contactList_->getContactByHandle( handle );
  if( contact == 0 )
  {
    return;
  }

  addressBook->addContactToGroup( contact->getGuid(), groupId );
}



/**
 * @brief Re-add a known contact to the friends list
 *
 * The contact will no longer appear in a "allowed contacts"
 * or "removed contacts" group, but appear at the friends list again.
 * The contact should exist already at some list
 * before (e.g. the block list or friends list).
 *
 *
 * @param  handle  Email address of the contact.
 * @param  groupsId Groups where to add the contact.
 */
void MsnNotificationConnection::addExistingContact( QString handle, const QStringList& groupsId )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Re-adding contact " << handle;
#endif

  Contact *c = contactList_->getContactByHandle( handle );
  if ( c && c->isFriend () )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kDebug () << handle << "is already on the address book; ignoring";
#endif
    return;
  }

  // Request the re-adding of contact to AB
  AddressBookService *addressBook = createAddressBookService();
  addressBook->addContact( handle, groupsId , true );
}



/**
 * @brief Add a new contact to the list.
 *
 * This is the standard method to add a contact. Optionally, it can also add it to a specific group.
 *
 * @param  handle   Email address of the contact.
 * @param  groupsId groupId's of the groups where to place the new contact.
 */
void MsnNotificationConnection::addNewContact( QString handle, const QStringList& groupsId )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Adding contact " << handle;
#endif

  AddressBookService *addressBook = createAddressBookService();
  Contact *c = contactList_->getContactByHandle( handle );

  // if they already exist, ensure we tell AB service of this.
  if ( c && c->isFriend() )
  {
    // now move them from the pending list onto the allow list.
    if ( c->isPending() )
    {
      
      // mark them as a messenger user first if they aren't already!
      if ( ! c->isMessengerUser() )
      {
        addressBook->markMessengerUser( c->getGuid() );
        c->setMessengerUser( true );
      }

      // this is a bit of a hack.
      addressBook->removePending( handle );
      addressBook->unblockContact( handle );
    }
    else
    {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      kmDebug() << handle << "is already on the address book; ignoring.";
#endif
      return;
    }
  }

  addressBook->addContact( handle, groupsId );
}



/**
 * @brief Adds the given contact to the allow list only.
 *
 * It allows the contact to see the online state of the user.
 * The contact will not appear in the main window
 * until it's also added to the friends list using addExistingContact().
 *
 * This method can only be called when the contact exists
 * in the reverse list only. This happens when the contact is new to the user,
 * or it was completely removed. Call unblockContact() instead when the
 * contact needs to be unblocked.
 *
 * This method is used when a new contact appears:
 * - to add the contact to the friends list too, call addNewContact()
 * - to allow the contact only, call this method.
 * - to block the contact, call blockContact()
 *
 * Internally, this method runs the required putAdc() calls to change the contact lists:
 * - the contact is added to the allow list (<code>AL</code>).
 *
 * @param handle  Email address of the contact.
 */
void MsnNotificationConnection::allowContact(QString handle)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Allowing contact " << handle;
#endif

  putAdl( handle, Contact::MSN_LIST_ALLOWED );

  AddressBookService *addressBook = createAddressBookService();
  addressBook->unblockContact( handle );
}



/**
 * @brief Add a new group
 *
 * This sends an <code>ADG</code> command to the server.
 * When gotAdg() receives the server response, the ContactList object is updated.
 *
 * @param  name  Name of the group.
 */
void MsnNotificationConnection::addGroup(QString name)
{
  AddressBookService *addressBook = createAddressBookService();
  addressBook->addGroup( name );
}



/**
 * @brief Block the given contact
 *
 * When the contact also exists in the friends list,
 * it will remain there with a blocked status icon.
 * When the contact only exists in the "allowed contacts" group,
 * it moves to the "removed contacts" group with a blocked status icon.
 *
 * This method can also be used when a new contact appears:
 * - to add the contact to the friends list too, call addNewContact()
 * - to allow the contact only, call allowContact()
 * - to block the contact, call this method.
 *
 * Internally, this method runs the required putAdc() calls to change the contact lists:
 * - the contact is added to the block list (<code>BL</code>).
 *
 * @param handle  Email address of the contact.
 */
void MsnNotificationConnection::blockContact(QString handle)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Blocking contact " << handle;
#endif

  putRml( handle, Contact::MSN_LIST_ALLOWED );
  putAdl( handle, Contact::MSN_LIST_BLOCKED );

  AddressBookService *addressBook = createAddressBookService();
  addressBook->blockContact( handle );
}



/**
 * @brief Change the current media of the user.
 *
 * This is used to exchange the "now playing" information.
 * It sends an <code>UUX</code> command to the server.
 * More information about this command can be found
 * at: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#UUX
 *
 * @param  application    Name of the media application, typically empty. iTunes and Winamp are known to be accepted.
 * @param  mediaType      Type of the media, either "Music", "Games" or "Office".
 * @param  enabled        Whether the media setting is enabled or not.
 * @param  formatString   A .Net style formatting string, e.g. "{0} - {1}".
 * @param  arguments      Replacements for the placeholders in the formatStrirng.
 */
void MsnNotificationConnection::changeCurrentMedia( const QString &application, const QString &mediaType,
                                                    bool enabled, const QString &formatString,
                                                    const QStringList arguments )
{
  // Update current media string
  QString media( application + "\\0" + mediaType + "\\0" + ( enabled ? "1" : "0" ) + "\\0" +
                 formatString + "\\0" + arguments.join( "\\0" ) );
  lastCurrentMedia_ = Qt::escape( media );

  // Send personal status and current media
  putUux();
}



/**
 * @brief Called when the MSN Object of the current user changed.
 *
 * When this method is called, contacts will be informed the user has a new display picture.
 * Some clients automatically initiate a background chat to retrieve the new display picture.
 * Other clients wait until a real chat is initiated, to request the display picture at that moment.
 *
 * Internally, this method calls changeStatus( Status ) because
 * that same command is used to notify about MsnObject changes.
 * The MsnObject is extracted from the CurrentAccount class.
 */
void MsnNotificationConnection::changedMsnObject()
{
  if( isConnected() && goneOnline_ )
  {
    // Send the same status message, with a new msn object
    changeStatus(currentAccount_->getStatus());
  }

  RoamingService *roamingService = createRoamingService();
  roamingService->updateDisplayPicture();
}



/**
 * @brief Change the friendly name of the user.
 *
 * Internally, this method uses changeProperty() to set the <code>MFN</code> property.
 * In MSNP15 the command PRP must be send with SOAP request to avoid nickname will disappear
 * the next time we sign in.
 *
 * If the initial status change isn't sent yet, this signal comes from the RoamingService and
 * means we now have enough information to goOnline().
 *
 * @param  newName  The new friendly name.
 */
void MsnNotificationConnection::changeFriendlyName( QString newName )
{
  if ( newName.trimmed().isEmpty() )
  {
    kmWarning() << "Tried to set an empty friendly name; ignoring.";
    return;
  }

  if( ! currentAccount_->isVerified() )
  {
    return;
  }

  currentAccount_->setFriendlyName( newName );

  changeProperty("MFN", newName);

  AddressBookService *addressBook = createAddressBookService();
  addressBook->contactUpdate( AddressBookService::PROPERTY_FRIENDLYNAME, newName );

  RoamingService *roaming = createRoamingService();
  roaming->updateProfile();

  if( !initialStatusSent_ )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Got Roaming friendly name, going online...";
#endif
    goOnline();
  }
}



// Change the personal properties
void MsnNotificationConnection::changePersonalProperties( const QString& cid, int blp )
{
  // Send BLP command for privacy settings, please refer to:
  // http://msnpiki.msnfanatic.com/index.php/Command:BLP
  const QString blpToSend( ( blp == 0 ) ? "BL" : "AL" );
  sendCommand( "BLP", blpToSend );

  // Check if necessary to retrieve information ( like pm, picture ) from msn storage
  // using roaming service
  RoamingService *roamingService = createRoamingService();
  roamingService->getProfile( cid );
}



/**
 * @brief Change the personal message of the user.
 *
 * The personal mesage typically contains a short status message
 * displayed next to the contact.
 *
 * This method sends the <code>UUX</code> payload command to the server.
 *
 * @param  newMessage  The new status message.
 */
void MsnNotificationConnection::changePersonalMessage( QString newMessage )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Changing personal message to " << newMessage;
#endif

  lastPsm_ = Qt::escape(newMessage);
  putUux();

  RoamingService *roamingService = createRoamingService();
  roamingService->updateProfile();
}



/**
 * @brief Change a user property
 *
 * It sends the <code>PRP</code> (likely short for property) command to the server.
 *
 * Valid property types are:
 * - <code>MFN</code>: MSN Friendly name
 * - <code>PHH</code>: Phone home
 * - <code>PHW</code>: Phone work
 * - <code>PHM</code>: Phone mobile
 * - <code>MOB</code>: MSN Mobile authorised for others: Y or N
 * - <code>MBE</code>: MSN Mobile enabled
 * - <code>WWE</code>: MSN direct?
 * - <code>HSB</code>: Has blog: <code>1</code> or <code>0</code>
 *
 * This function is also used internally by other methods like changeFriendlyName()
 *
 * @param  type   The property type.
 * @param  value  The new property value, can also be empty.
 */
void MsnNotificationConnection::changeProperty( QString type, QString value )
{
  if( value.isEmpty() )
  {
    sendCommand( "PRP", type );
  }
  else
  {
    value = QUrl::toPercentEncoding( value );

    // To avoid problem with a very long nick
    value.truncate( 387 );

    sendCommand( "PRP", type + " " + value );
  }
}



/**
 * @brief Change a contact property
 *
 * It sends the <code>SBP</code> (likely short for set-buddy-property) command to the server.
 *
 * @param  handle  Email address of the contact.
 * @param  type    The property type, e.g. <code>MFN</code>, see the other changeProperty() method.
 * @param  value   The new property value, can also be empty.
 */
void MsnNotificationConnection::changeProperty( QString handle, QString type, QString value )
{
  // Get contact
  const Contact *contact = contactList_->getContactByHandle(handle);
  if(KMESS_NULL(contact)) return;

  // Avoid disconnects by the server
  if( contact->getGuid().isEmpty() )
  {
    kmWarning() << "Can't change property, contact GUID is empty"
                << " (contact=" << handle << " property=" << type << ")." << endl;
    return;
  }

  // First guess is that "SBP" means set-buddy-property
  if( value.isEmpty() )
  {
    sendCommand( "SBP", contact->getGuid() + " " + type );
  }
  else
  {
    value = QUrl::toPercentEncoding( value );

    // To avoid problem with a very long nick
    value.truncate( 387 );

    sendCommand( "SBP", contact->getGuid() + " " + type + " " + value );
  }
}



/**
 * @brief Change the status of the user.
 *
 * This sends the <code>CHG</code> command to the user.
 * This command also informs contacts the user's MsnObject and client capabilities.
 * The MsnObject contains the meta-data about the user's display picture.
 * The display picture itself is transferred in a chat session.
 *
 * Note this method advertises the supported client features, like 'winks' and 'webcam'.
 *
 * @param  newStatus  The new user status.
 * @todo Possibly rename this to putChg() instead, especially when webcam support makes the capabilities dynamic.
 */
void MsnNotificationConnection::changeStatus( const Status newStatus )
{
  const QString statusCode( MsnStatus::getCode( newStatus ) );

  if( newStatus == STATUS_OFFLINE )
  {
    kmWarning() << "Cannot change status to Offline!";
    return;
  }

  quint32 capabilities =
    Contact::MSN_CAP_INK_GIF  // If Isf-Qt is disabled, we can still receive GIF images
  | Contact::MSN_CAP_WINKS
  | Contact::MSN_CAP_MULTI_PACKET;

  // We can choose not to advertise any protocol-related caps, for p2p testing
#if NOTIFICATION_HIDE_P2P_SUPPORT == 0
  capabilities |=  Contact::MSN_CAP_MSN75;
#endif

  // ISF support can be disabled at compile time
#if KMESS_ENABLE_INK == 1
  capabilities |= Contact::MSN_CAP_INK_ISF;
#endif

  const QString objStr( QUrl::toPercentEncoding( currentAccount_->getMsnObjectString() ) );

  if( objStr.isEmpty() )
  {
    // Just don't send it if we don't have it.
    sendCommand( "CHG", statusCode + " " + QString::number(capabilities) );
  }
  else
  {
    sendCommand( "CHG", statusCode + " " + QString::number(capabilities) + " " + objStr );
  }
}



// Removes any expired switchboard requests, so new ones can be made
void MsnNotificationConnection::checkSwitchboardsTimeout()
{
  // There are no pending switchboard requests, bail out
  if( openRequests_.count() < 1 )
  {
    return;
  }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Removing expired switchboard requests.";
#endif

  // Delete any pending switchboard
  uint now = QDateTime::currentDateTime().toTime_t();
  foreach( ChatInformation *switchboardInfo, openRequests_ )
  {
    if( ( now - switchboardInfo->getTime() ) > 60 )
    {
      openRequests_.removeAll( switchboardInfo );
      delete switchboardInfo;
    }
  }
}



/**
 * @brief Close the connection with the server
 *
 * This disconnects from the server. It also cleans up the
 * state variables and objects associated with the session,
 * e.g. contactlist data, open chat invitations, pending offline-im messages and SOAP clients.
 *
 * The ContactList object is saved and reset as well.
 */
void MsnNotificationConnection::closeConnection()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Close the connection.";
#endif

  // Disconnect from the server first.
  // emits the signals so other classes can clean up / save properties.
  disconnectFromServer();

  // Stop the waiting timers
  loginTimer_.stop();
  friendlyNameWaitTimer_.stop();

  // Delete all SOAP clients, and unset local references here.
  deleteSoapClients();
  offlineImService_       = 0;
  passportLoginService_   = 0;
  isInitiatingConnection_ = false;
  initialStatusSent_      = false;
  goneOnline_             = false;
  lastStatus_             = -1;
  roamingService_         = 0;

  // Remove contacts and non-special groups from the contact list
  if ( contactList_ != 0 )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Resetting contactlist.";
#endif

    // Save the contact list settings
    saveProperties();

    // The contact list is not deleted, like this class it's being
    // shared through the entire application, and attached to slots.
    contactList_->reset( false );
  }

  // Clear all pending offline messages
  pendingOfflineImMessages_.clear();

  // Delete all offline im messages
  qDeleteAll( offlineImMessages_ );
  offlineImMessages_.clear();

  // Delete all open requets
  qDeleteAll( openRequests_ );
  openRequests_.clear();

  // Reset current account
  // TODO: use different connection for this?
  if( currentAccount_ != 0 )
  {
    currentAccount_->setNoEmails( 0 );
  }
}



/**
 * @brief Create and return one pointer to address book service.
 *
 */
AddressBookService* MsnNotificationConnection::createAddressBookService()
{
  if( addressBookService_ == 0 )
  {
    addressBookService_ = new AddressBookService( this );
    addSoapClient( addressBookService_ );

    // Contact actions signals/slots
    connect( addressBookService_, SIGNAL(      contactAdded( const QString&, const QString&, const QStringList&  ) ),
             this,                  SLOT(  slotContactAdded( const QString&, const QString&, const QStringList&  ) ) );

    connect( addressBookService_, SIGNAL(      contactDeleted( const QString&, const QString& ) ),
             this,                  SLOT(  slotContactDeleted( const QString& ) ) );

    connect( addressBookService_, SIGNAL(        contactBlocked( const QString& ) ),
             this,                  SLOT(    slotContactBlocked( const QString& ) ) );
    connect( addressBookService_, SIGNAL(      contactUnblocked( const QString& ) ),
             this,                  SLOT(  slotContactUnblocked( const QString& ) ) );

    // Contact added/remove to/from group signals/slots
    connect( addressBookService_, SIGNAL(           contactAddedToGroup( const QString&, const QString& ) ),
             this,                  SLOT(       slotContactAddedToGroup( const QString&, const QString& ) ) );
    connect( addressBookService_, SIGNAL(       contactDeletedFromGroup( const QString&, const QString& ) ),
             this,                  SLOT(   slotContactDeletedFromGroup( const QString&, const QString& ) ) );

    // Group actions signals/slots
    connect( addressBookService_, SIGNAL(   groupAdded( const QString&, const QString&  ) ),
             contactList_,          SLOT(     addGroup( const QString&, const QString& ) ) );
    connect( addressBookService_, SIGNAL( groupDeleted( const QString&, const QString& ) ),
             contactList_,          SLOT(  removeGroup( const QString&                 ) ) );
    connect( addressBookService_, SIGNAL( groupRenamed( const QString&, const QString& ) ),
             contactList_,          SLOT(  renameGroup( const QString&, const QString& ) ) );

    // Membership and AddressBook signals/slots
    connect( addressBookService_, SIGNAL(     gotMembershipLists(const QString&,const QHash<QString,int>&) ),
             this,                  SLOT( slotGotMembershipLists(const QString&,const QHash<QString,int>&) ) );
    connect( addressBookService_, SIGNAL(  gotAddressBookList( const QList< QHash<QString,QVariant> >& ) ),
             this,                  SLOT( slotGotAddressBookList( const QList< QHash<QString,QVariant> >& )));

    connect( addressBookService_, SIGNAL(     gotGroup( const QString&, const QString& ) ),
             this,                  SLOT( slotGotGroup( const QString&, const QString& ) ) );

    connect( addressBookService_, SIGNAL(   gotPersonalInformation(const QString&,int) ),
             this,                  SLOT( changePersonalProperties(const QString&,int) ) );
    connect( addressBookService_, SIGNAL(     soapWarning(const QString&,bool) ),
             this,                SLOT  (     slotWarning(const QString&,bool) ) );

    connect( addressBookService_, SIGNAL( addressBookChanged( const QString&, AddressBookService::AddressBookUpdate ) ),
            this,                 SIGNAL( addressBookChanged( const QString&, AddressBookService::AddressBookUpdate ) ));
  }
  return addressBookService_;
}



/**
 * @brief Internal function to create the Offline-IM service
 *
 * This service is used to retrieve the Offline-IM messages.
 * The signals are attached to receivedOfflineIm() and receivedMailData()
 *
 * @returns  The Offline-IM webservice handler
 */
OfflineImService* MsnNotificationConnection::createOfflineImService()
{
  // Extract the 't' and 'p' values from the current authentication ticket string
  // This is done beacuse sendMessage() could has requested one new ticket
  const QStringList fields( currentAccount_->getToken( "Messenger" ).split("&") );
  QString authT, authP;

  foreach( const QString &field, fields )
  {
    if( field.startsWith("t=") )
    {
      authT = field.mid(2);
    }
    else if( field.startsWith("p=") )
    {
      authP = field.mid(2);
    }
#ifdef KMESSDEBUG_NOTIFICATION
    else
    {
      kmWarning() << "Could not parse authentication field:" << field;
    }
#endif
  }

  // Request the first offline-im message
  // TODO reuse the pointer, and set the token like addressbook
  OfflineImService *oimService = new OfflineImService( authT, authP, this );

  // Add the soap client.
  addSoapClient( oimService );

  // Register signals
  connect( oimService, SIGNAL(   messageReceived( const QString&,   const QString&, const QString&,
           const QDateTime&, const QString&, const QString&, int ) ),
           this,       SLOT  ( receivedOfflineIm( const QString&,   const QString&, const QString&,
                               const QDateTime&, const QString&, const QString&, int ) ));
  connect( oimService, SIGNAL( metaDataReceived( QDomElement ) ),
           this,       SLOT  ( receivedMailData( QDomElement ) ));

  return oimService;
}



/**
 * @brief Internal function to create the passport login handler.
 *
 * This service is used by gotUsr() to retrieve the passport authentication token.
 *
 * @returns  The Passport login webservice handler
 */
PassportLoginService* MsnNotificationConnection::createPassportLoginService()
{
  // TODO reuse the pointer
  // Create the login handler.
  PassportLoginService *loginService = new PassportLoginService();

  // Add the soap client.
  addSoapClient( loginService );

  // Connect all login notifications
  connect( loginService, SIGNAL( loginIncorrect() ),
           this,         SLOT  ( loginIncorrect() ) );
  connect( loginService, SIGNAL( loginSucceeded() ),
           this,         SLOT  ( loginSucceeded() ) );

  return loginService;
}



/**
 * @brief Internal function to create the Roaming Service
 *
 * This service is used to fetch the personal message and picture from the server.
 *
 * @returns  The RoamingService handler
 */
RoamingService* MsnNotificationConnection::createRoamingService()
{
  // Create the login handler.
  if( roamingService_ == 0 )
  {
    roamingService_ = new RoamingService( this );

    // Add the soap client.
    addSoapClient( roamingService_ );

    // Connect user details updates
    connect( roamingService_, SIGNAL( receivedFriendlyName(QString)    ),
             this,            SLOT  (   changeFriendlyName(QString)    ) );
    connect( roamingService_, SIGNAL( receivedDisplayPicture(QString)  ),
             this,            SLOT  (       changedMsnObject()         ) );
    connect( roamingService_, SIGNAL( receivedPersonalMessage(QString) ),
             this,            SLOT  (   changePersonalMessage(QString) ) );
  }

  return roamingService_;
}



/**
 * @brief Compute a triple des encryption.
 *
 * Used to compute the value of string to send to server for SSO.
 *
 * @param  key     The key for compute the encryption
 * @param  secret  The secret data to encryption
 * @param  iv      The initial vector for cbc mode
 */
QByteArray MsnNotificationConnection::createTripleDes ( const QByteArray &key, const QByteArray &secret, const QByteArray &iv )
{
  // Initialize a triple-DES CBC encryption
  gcry_cipher_hd_t handle;
  gcry_cipher_open( &handle, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_CBC, 0 );

  // Set key and IV
  gcry_cipher_setkey( handle, key.constData(), key.size() );
  gcry_cipher_setiv( handle, iv.constData(), iv.size() );

  // Encrypt the data. The out buffer has to have the same size as the in buffer.
  char *encrypted = new char[ secret.size() ];
  gcry_cipher_encrypt( handle, (void*) encrypted, secret.size(), secret.constData(), secret.size() );

  // Clean up the cipher handle
  gcry_cipher_close( handle );

  QByteArray encryptedByteArray( encrypted, secret.size() );
  delete [] encrypted;
  return encryptedByteArray;
}



/**
 * @brief Return the contact list model
 *
 * @returns  The model from the contact list managed by this class.
 */
QAbstractItemModel* MsnNotificationConnection::getContactListModel()
{
  return contactList_;
}



/**
 * @brief  Called when the user is ready to go online.
 *
 * This method is called by gotSyn() or gotLst() when
 * the entire contact list data is received.
 * It changes some final things before the user goes online:
 * - set the initial user status, e.g. "online" or "invisible".
 * - ses the initial personal message.
 * - requests other server data, like Hotmail email URL's.
 * Finally, the connected() signal is fired.
 *
 * Because we ignore the NS name because it's incorrect nowadays, KMess
 * can load its friendlyname from the local storage or from SOAP. At the
 * first login, there is no friendlyname, so KMess falls back to starting out
 * with the handle. Just after initial login, the SOAP request finishes and
 * correctly changes the friendlyname. Because we want this to happen *before*
 * the initial login, the signal from the RoamingService is connected to this
 * method and there's a timer that will call it after five seconds. This ensures
 * that this method is always called, and usually after the real friendlyname
 * is known. The variable initialStatusSent_ is used to protect this function
 * against being called twice.
 */
void MsnNotificationConnection::goOnline()
{
  int ack;

  if(KMESS_NULL(currentAccount_)) return;

  if( initialStatusSent_ )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Initial status already sent, skipping call.";
#endif
    return;
  }
  initialStatusSent_ = true;

  if( goneOnline_ )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Already gone online, skipping call.";
#endif
    return;
  }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Going online!";
#endif

  // Stop the authentication timer, we're done with that part
  loginTimer_.stop();

  // Start the ping timer: from now on, it's its duty to verify that the connection is still active
  setSendPings( true );

  // Load the contact list settings
  readProperties();

  // Get the initial status to use: if we were previously disconnected by an error or network
  // issue, use the last status instead
  int initialStatus = currentAccount_->getInitialStatus();
  if( lastStatus_ != -1 && lastStatus_ != STATUS_OFFLINE && lastStatus_ != STATUS_IDLE )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Last status was set:" << MsnStatus::getCode( (Status)lastStatus_ );
#endif
    initialStatus = lastStatus_;
    lastStatus_ = -1;
  }

  // Set the default friendly name. May be overridden later on
  changeFriendlyName( currentAccount_->getFriendlyName( STRING_ORIGINAL ) );

  // Change to the initial status
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Changing initial status to" << MsnStatus::getCode( (Status)initialStatus );
#endif
  changeStatus( (Status)initialStatus );

  // Reset status fields.
  lastCurrentMedia_ = QString::null;
  lastPsm_ = QString::null;

  // Start with default personal message (may be empty)
  changePersonalMessage( currentAccount_->getPersonalMessage( STRING_ORIGINAL ) );

  // When the account supports email (e.g. Hotmail), request the URL's.
  // This didn't give problems with non-email accounts,
  // but restricted accounts return a "710" error for this command.
  if( currentAccount_->getEmailSupported() )
  {
    // The ACK is temporary stored as inbox-command,
    // so the response can be mapped back to the requested folder.
    QHash<QString,QString>& hash = currentAccount_->getUrlInformation();
    // Ask for inbox URL
    ack = sendCommand("URL", "INBOX");
    hash.insert( QString::number(ack), "INBOX" );

    // Ask for compose URL
    ack = sendCommand("URL", "COMPOSE some.invalid@kmess.email" );
    hash.insert( QString::number(ack), "COMPOSE" );

    // Ask the personal profile URL
    ack = sendCommand("URL", "PROFILE 0x" + QString::number( CurrentAccount::instance()->getLanguageCode().toInt(), 8 ) );
    hash.insert( QString::number(ack), "PROFILE" );
  }

  // Notify observers that the server is connected
  emit connected();

  // start a sliding timer: whilst we're receiving the first ILNs do not display notifications.
  // when 10sec have elapsed after the last ILN then we unlock the notifications.
  notificationUnlockTimer_.start( 10000 );

  goneOnline_ = true;

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Gone online!";
#endif
}



// Received response to adl command
void MsnNotificationConnection::gotAdl( const QStringList& command, const QString& payload )
{
  // The server responds to a successful ADL command
  if( command[2] == "OK" )
  {
    // Check if already connect
    if( ! connected_ )
    {
      // Go online (goOnline() emits the connected() signal)
      // We want to wait for the SOAP friendlyname response before we goOnline.
      // See the docs for goOnline() for more information.
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      kmDebug() << "Scheduling goOnline()....";
#endif
      friendlyNameWaitTimer_.start();
      connected_ = true;
    }

    return;
  }

  if( ! payload.isEmpty() )
  {
    QRegExp regexp( ".*n=\"([^\"]+)\".*n=\"([^\"]+)\".*l=\"([^\"]+)\"" );

    int pos = regexp.indexIn( payload );
    int lists = -1;
    QString handle;

    if( pos != -1 )
    {
      handle = regexp.cap(2) + "@" + regexp.cap(1);
      lists = QVariant( regexp.cap(3) ).toInt();
    }

    if( lists == -1 || handle.isEmpty() )
    {
      return;
    }

    Contact *contact = contactList_->getContactByHandle( handle );
    if( contact != 0 )
    {
      if( lists & Contact::MSN_LIST_FRIEND )  contact->setFriend ( true );
      if( lists & Contact::MSN_LIST_ALLOWED ) contact->setAllowed( true );
      if( lists & Contact::MSN_LIST_BLOCKED ) contact->setBlocked( true );
      if( lists & Contact::MSN_LIST_REVERSE ) contact->setReverse( true );
      if( lists & Contact::MSN_LIST_PENDING ) contact->setPending( true );
    }
    else
    {
      // Else if the contact isn't present, ask to user what he want to do.
      kmDebug() << "The contact: " << handle << " not present in contact list added user";
      emit contactAddedUser( handle );
    }
  }
}



// Received confirmation of the user's status change
void MsnNotificationConnection::gotChg( const QStringList& command )
{
  if(KMESS_NULL(currentAccount_)) return;

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "User's status changed to " << command[2] << ".";
#endif

  // Change the user's status
  currentAccount_->setStatus( MsnStatus::codeToStatus( command[2] ) );
}



// Received a challenge from the server
void MsnNotificationConnection::gotChl( const QStringList& command )
{
  // Handle challenge query
  MSNChallengeHandler handler;
  const QString response( handler.computeHash( command[2] ) );

  // Send response
  sendPayloadMessage( "QRY", handler.getProductId(), response );
}



// Received a version update from the server
void MsnNotificationConnection::gotCvr( const QStringList& command )
{
  Q_UNUSED( command );
  if(KMESS_NULL(currentAccount_)) return;

  // Set the connected status to false
  connected_ = false;
  initialStatusSent_ = false;

  // Send the USR command
  sendCommand( "USR", "SSO I " + currentAccount_->getHandle() );
}



// Received notice that a contact went offline
void MsnNotificationConnection::gotFln( const QStringList& command )
{
  if(KMESS_NULL(contactList_)) return;

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << command[1] << " went offline.";
#endif

  contactList_->changeContactStatus( command[1].toLower(), STATUS_OFFLINE );
}



// Received notice that a contact is already online
void MsnNotificationConnection::gotIln( const QStringList& command )
{
  if(KMESS_NULL(contactList_)) return;

  // reset the notification unlock timer if it hasn't already fired.
  if ( notificationUnlockTimer_.isActive() ) notificationUnlockTimer_.start( 2000 );

  // Get the contact info from the message
  const QString handle       ( command[3].toLower() );
  const QString friendlyName ( QUrl::fromPercentEncoding( command[5].toUtf8() ) );
  QString       msnObject    ( QUrl::fromPercentEncoding( command[7].toUtf8() ) );

  uint capabilities = command[6].toUInt();
  Status     status = MsnStatus::codeToStatus( command[2] );

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << handle << " is initially " << command[2] << ".";
#endif

  // Check if the received MSN object is empty, a "0" (Kopete), or shorter than the string "<msnobject/>"
  if( msnObject.isEmpty() || msnObject.compare( QString( "0" ) ) <= 0 || msnObject.length() < 12 )
  {
    msnObject = QString::null;
  }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  if( ! msnObject.isEmpty() )
  {
    kmDebug() << "This person has an msn6 picture: " << msnObject;
  }
#endif

  contactList_->changeContactStatus( handle, status, friendlyName, capabilities, msnObject, enableNotifications_ );
}



/**
 * @brief Handle the <code>NLN</code> command; a contact changed it's status.
 *
 * When a user updates it's status with the <code>CHG</code> command,
 * all it's contact receive a <code>NLN</code> command.
 * KMess sends the <code>CHG</code> command with the changeStatus() method.
 * The <code>CHG</code> allows a contact to change it's name, client capabilities or MsnObject.
 *
 * The capabilities are a bitwise flag. The list of known values
 * are described in the ContactBase::MsnClientCapabilities enum.
 *
 * The MsnObject is an identifier for the display picture.
 * To download the actual picture, a client needs to initiate a chat,
 * and send an invitation there.
 *
 * @code
<<< NLN NLN user@kmessdemo.org KMess%20Demo 1342210080 %3Cmsnobj%20Creator%3D%22user%40kmessdemo.org%22%20Size%3D%2211581%22%20Type%3D%223%22%20Location%3D%22KMess.tmp%22%20Friendly%3D%22AA%3D%3D%22%20SHA1D%3D%223VfOMCTTkuYHDjJhPx4sSlPiM%2Bs%3D%22%20SHA1C%3D%22ULeT2WGsdabLGNP3RgbWYw7ve80%3D%22/%3E
@endcode
 *
 * @param  command  The command arguments.
 *                  - <code>command[1]</code> is the status code, see changeStatus()
 *                  - <code>command[2]</code> is the contact handle.
 *                  - <code>command[3]</code> networkid.
 *                  - <code>command[4]</code> is the contact name, url encoded.
 *                  - <code>command[5]</code> is the client-capabilities flag.
 *                  - <code>command[6]</code> is the url-encoded MsnObject XML code.
 */
void MsnNotificationConnection::gotNln( const QStringList& command )
{
  if(KMESS_NULL(contactList_)) return;

  // Get the contact info from the message
  const QString handle       ( command[2].toLower() );
  const QString friendlyName ( QUrl::fromPercentEncoding( command[4].toUtf8() ) );
  QString       msnObject    ( QUrl::fromPercentEncoding( command[6].toUtf8() ) );

  Status status        = MsnStatus::codeToStatus( command[1] );
  uint   capabilities  = command[5].toUInt();

  // Check if the contact removes his picture
  if( msnObject == "0" )
  {
    msnObject = "";
  }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << handle << " is now " << MsnStatus::getCode( status ) << ".";
#endif

  // Update contact status.
  contactList_->changeContactStatus( handle, status, friendlyName, capabilities, msnObject, enableNotifications_ );
}



// Received notice of disconnection from the server
void MsnNotificationConnection::gotOut( const QStringList& command )
{
  // If the first command parameter is "OTH", it means that this account has connected from elsewhere.
  if( command[1] == "OTH" )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Got notice that this account has connected from elsewhere.";
#endif

    // No need to reconnect with the last status if we were kicked out
    lastStatus_ = -1;

    slotError( "Connected from another location",
               MsnSocketBase::ERROR_CONNECTION_OTHER );
  }
  else
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Received notice of disconnection from the server.";
#endif

    // Save the status, to use it again when reconnecting
    lastStatus_ = currentAccount_->getStatus();

    closeConnection();

    // Try connecting again
    emit reconnect( currentAccount_->getHandle() );
  }
}



// Received a property change (friendly name, phone number, etc..)
void MsnNotificationConnection::gotPrp( const QStringList& command )
{
  const QString propertyName ( command[1] );
  const QString propertyValue( QUrl::fromPercentEncoding( command[2].toUtf8() ) );

  if( propertyName == "MFN" )
  {
    currentAccount_->setFriendlyName( propertyValue );
  }
  else if( propertyName == "MBE" )
  {
    // Unimplemented.
    // indicates whether MSN Mobile is enabled.
  }
  else if( propertyName == "WWE" )
  {
    // Unimplemented
    // indicates whether MSN Direct is enabled.
  }
  else
  {
//    kmWarning() << "Received unsupported property name/value: " << propertyName << " " << propertyValue << "!";
  }
}



void MsnNotificationConnection::gotRml( const QStringList &command )
{
   // The server responds to a successful RML command
  if( command[2] == "OK" )
  {
  }
}



/**
 * @brief Received a connection request from a contact
 *
 * Format at MSNP15:
 * RNG 1295917726 65.54.228.15:1863 CKI 104194101.9520122 email@domain.com Friendly%20Name U messenger.msn.com 1
*/
void MsnNotificationConnection::gotRng( const QStringList& command )
{
  // Pull the data from the message
  const QString& chatId        = command[1];
  const QString& serverAndPort = command[2];
  const QString& auth          = command[4];
  const QString& handle        = command[5].toLower();

  const QString server    ( serverAndPort.section(':', 0, 0) );
  const QString portString( serverAndPort.section(':', 1, 1) );

  bool goodPort;
  quint16 port = (quint16) portString.toUInt( &goodPort );
  if( ! goodPort )
  {
    kmWarning() << "Couldn't get port from string " << portString;
    return;
  }
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << handle << " is requesting a chat at: "
            << server << ":" << port << " id=" << chatId << " auth=" << auth;
#endif

  // Send connection information object
  emit startSwitchboard( ChatInformation( this, handle, server, port, auth, chatId, ChatInformation::CONNECTION_BACKGROUND ) );
}



// Received information about a contact's personal message
void MsnNotificationConnection::gotUbx( const QStringList &command, const QByteArray &payload )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Got UBX information.";
#endif

  Contact *contact = contactList_->getContactByHandle( command[1].toLower() );
  if(KMESS_NULL(contact)) return;

  if( command[3].toInt() == 0 )
  {
    // Size is 0, no personal message
    contact->setPersonalStatus( QString::null );
  }
  else
  {
    // Load XML parser
    QDomDocument xml;
    if( ! xml.setContent(payload) )
    {
      kmWarning() << "Could not parse personal status message (invalid XML, contact=" << command[1] << ")!";
      kmWarning() << "The invalid XML was:" << payload;
    }

    // Parse payload
    const QDomElement root        ( xml.namedItem("Data").toElement() );
    const QString     psm         ( root.namedItem("PSM").toElement().text() );
    QString           currentMedia( root.namedItem("CurrentMedia").toElement().text() );
    QString mediaType;

    // Append current media if set.
    if( currentMedia.length() > 0 )
    {
      // From http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes:
      //
      // Media string can be something like:
      //  <CurrentMedia>\0Music\01\0{0} - {1}\0Title\0Artist\0Album\0\0</CurrentMedia>
      //  <CurrentMedia>\0Games\01\0Playing {0}\0Game Name\0</CurrentMedia>
      //  <CurrentMedia>\0Office\01\0Office Message\0Office App Name\0</CurrentMedia>
      //
      // The literal "\0" is the separator char.
      // Fields are: application, mediatype, enabled, formatstring, arg1, arg2, arg3

      // Split media string
      const QStringList media( currentMedia.split( "\\0" ) );
      mediaType = media[1];

      // Check if media is enabled, to find out what this means.
      if( media[2] != "1" )
      {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
        kmDebug() << "CurrentMedia is set, but not enabled.";
#endif
        currentMedia = QString::null;
      }
      else
      {
        // Replace fields in .Net style format string
        currentMedia = media[3];  // format
        for( int i = 4; i < media.count(); i++ )
        {
          currentMedia = currentMedia.replace( "{" + QString::number(i - 4) + "}", media[i] );
        }
      }
    }

    // Store personal message
    contact->setPersonalStatus( psm, mediaType, currentMedia );
  }
}



// Received the folder and command info for Hotmail's inbox or compose
// The response from ns is like to:
// URL 68 /cgi-bin/compose https://login.live.com/ppsecure/md5auth.srf?lc=1040 2
// 68 is an ack number, /cgi-bin/compose is the ru ( url command ) for the specify service
// and the last url is the site to send the query. Note: KMess use sha1auth instead md5
// so the store of this last url is useless!
void MsnNotificationConnection::gotUrl( const QStringList& command )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Got URL information.";
#endif
  if(KMESS_NULL(currentAccount_)) return;

  // Grep the hash where store the informations
  QHash<QString,QString>& hash( currentAccount_->getUrlInformation() );

  // Check if in the hash there is already the ack request
  // Remove the item with the key "ack" and value "SERVICE" and switch the element
  // so the key will be the "SERVICE" and the value will be the URL.
  const QString service( hash.take( command[1] ) );

  if( ! service.isEmpty() )
  {
    hash.insert( service, command[2] );
  }
}



/**
 * @brief Received a USR command from the server.
 *
 * USR command for the login. At first we must send the handle with wich we want to log on.
 * When the server response to first USR, we take the nonce value used for compute the return value.
 * The key used for encrypt the data ( 3DES ) must be taken from Passport SOAP request.
 * The computed value ( encryption of nonce with secret key taken by SOAP ) must be sended to server.
 *
 */
void MsnNotificationConnection::gotUsr( const QStringList& command )
{
  if(KMESS_NULL(currentAccount_)) return;

  const QString& step = command[2];

  // This is either asking for authentication or confirming it
  if( step == "SSO" )
  {
    // The initial connection process (network connection establish and version exchange) is over.
    // The MSN servers sometimes fail to warn the clients that something's wrong by their side and
    // they close the connection before the USR. This is a nice workaround: if the connection gets
    // closed before this point, we just try again with HTTP, which usually works.
    isInitiatingConnection_ = false;

    const QString& policy = command[4];           // Policy
    nonceBase64_          = command[5].toAscii();   // Base64 nonce

    // Change statusbar
    emit statusMessage( i18n("Authenticating..."), false );

    // Start the passport based login
    passportLoginService_ = createPassportLoginService();
    passportLoginService_->login( policy );
  }
  else if( step == "OK" )
  {
    // This is a confirmation message.  Get the user's friendly name from the message.
    bool isVerified = ( command[4].toInt() == 1 );
    currentAccount_->setVerified( isVerified );

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    if( ! isVerified )
    {
      kmWarning() << "Passport account of the user is not verified yet.";
    }
#endif

    // When users choose an existing account in the InitialView dialog,
    // they may alter the password. This value is stored as temporary password.
    // The password may be overwritten now, because the login was succesful.
    currentAccount_->saveTemporaryPassword();

    emit statusMessage( i18n( "Authenticated" ), false );

    // Retrieve from with one SOAP request the membership list ( where there are the lists for
    // FL AL BL contacts )
    AddressBookService *addressBook = createAddressBookService();
    addressBook->retrieveMembershipLists();
  }
  else
  {
    kmWarning() << "Received unexpected USR step:" << step;
  }
}



// Received version information
void MsnNotificationConnection::gotVer( const QStringList& command )
{
  Q_UNUSED( command ); // Avoid compiler warning

  if(KMESS_NULL(currentAccount_)) return;

  // Send some fake info about the current version
  // First parameter is the locale-id (0x0409 is U.S. English).
  sendCommand( "CVR", "0x0409 winnt 5.1 i386 MSNMSGR 7.5.0324 msmsgs " + currentAccount_->getHandle() );
}



/**
 * @brief Received server transfer information
 *
 * Format at MSNP15:
 * XFR 32 SB 65.54.171.31:1863 CKI 1743299383.52212212.219110167 U messenger.msn.com 1
 */
void MsnNotificationConnection::gotXfr(const QStringList& command)
{
  int transactionId = command[1].toInt();

  // Get informations about the server
  const QString& serverType    = command[2];
  const QString& serverAndPort = command[3];
  const QString  server    ( serverAndPort.section( ':', 0, 0 ) );
  const QString  portString( serverAndPort.section( ':', 1, 1 ) );

  // Convert the port to an integer
  bool goodPort;
  quint16 port = (quint16) portString.toUInt( &goodPort );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Got " << serverType << " transfer to " << server << ":" << port << ".";
#endif
  if( ! goodPort )
  {
    kmDebug() << "Got bad port number : " << portString << ".";
    return;
  }

  if( serverType == "NS" )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Disconnecting from old server.";
#endif
    // Disconnect from the existing server.
    disconnectFromServer( true );

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Connecting to new server.";
#endif

#ifdef KMESS_NETWORK_WINDOW
    KMESS_NET_INIT( this, "NS " + server );
#endif

    // Stop the login timer: it'll be restarted while connecting to the new server
    loginTimer_.stop();

    // Set the connection as initiating, connectToServer() is from the parent class
    // and can't do it itself
    isInitiatingConnection_ = true;

    // Connect to the new server.
    connectToServer( server, port );
  }
  else if( serverType == "SB" )
  {
#ifdef KMESSTEST
    KMESS_ASSERT( openRequests_.count() > 0 );
    int noChatsBefore = openRequests_.count();
#endif

    // This is a switchboard server.  This message should be in response to
    //  a user-requested chat.  Look for the pending chat information.
    ChatInformation *switchboardInfo = 0;
    foreach( ChatInformation *chatInformation, openRequests_ )
    {
      if( chatInformation->getTransactionId() == transactionId )
      {
        switchboardInfo = chatInformation;
        break;
      }
    }

    if( switchboardInfo == 0 )
    {
      kmWarning() << "Got an XFR response, but there are no corresponding open requests.";
      return;
    }

    // Update the server information
    switchboardInfo->setServerInformation( server, port, command[5] );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "emitting startSwitchboard() signal.";
#endif

    // Signal the new connection
    emit startSwitchboard( *switchboardInfo );

    // Remove the first item from the active requests list
    openRequests_.removeAll( switchboardInfo );
    delete switchboardInfo;

#ifdef KMESSTEST
    KMESS_ASSERT( openRequests_.count() == ( noChatsBefore - 1 ) );
#endif
  }
}



/**
 * @brief Send the ADL command.
 *
 * ADL: 'Add List' command, uses XML to identify each contact, and works as a payload message.
 * Each ADL command may contain up to 150 contacts (a payload of roughly 7500 bytes).
 *
 */
void MsnNotificationConnection::putAdl( const QString &handle, int list )
{
  // If handle is present then the adl isn't initial ADL command, so use command for add someone into one list
  if( ! handle.isEmpty() )
  {
    const QStringList splittedHandle( handle.split( "@" ) );

    const QString payload( "<ml>"
                            "<d n=\"" + splittedHandle[1] + "\">"
                              "<c n=\"" + splittedHandle[0] + "\" l=\"%1\" t=\"1\" />"
                            "</d>"
                           "</ml>" );

    sendPayloadMessage( "ADL", QString::null, payload.arg( QString::number( list ) ) );
    return;
  }

  // Use the current contact list to make the string to send with initial ADL command
  const QHash< QString, Contact* >& contactList( contactList_->getContactList() );

  // If the list is empty, send an empty ADL
  if( contactList.isEmpty() )
  {
    sendPayloadMessage( "ADL", QString(), QByteArray( "<ml l=\"1\"></ml>" ) );
    return;
  }

  QHashIterator< QString,Contact* > i( contactList );
  const Contact *currentContact = 0;

  QString adlList;
  QString currentAdl;
  bool send = false;
  int userType = 1;

  while( i.hasNext() )
  {
    i.next();

    currentContact = i.value();
    if( currentContact == 0 )
    {
      continue;
    }

    list = 0;
    if( currentContact->isFriend()  )
    {
      list |= Contact::MSN_LIST_FRIEND;
    }

    // The contact may be or blocked or allowed, if it is both the ADL command fails
    if( currentContact->isBlocked() )
    {
      list |= Contact::MSN_LIST_BLOCKED;
    }
    else if( currentContact->isAllowed() )
    {
      list |= Contact::MSN_LIST_ALLOWED;
    }

    // Add to ADL command only user in at least one list
    if( list != 0 )
    {
      // Split the domain from handle
      const QStringList splittedHandle( currentContact->getHandle().split( "@" ) );

      // Set the user type
      userType = 1;
      if( currentContact->getInformation( "isMessenger3" ).toBool() )
      {
        userType = 2;
      }

      currentAdl = "<d n=\"" + splittedHandle[1] + "\"><c n=\"" + splittedHandle[0] + "\" l=\"" +
                   QString::number( list ) + "\" t=\"" + QString::number( userType ) + "\" /></d>";

      // Check if the buffer is too large ( 7500 bytes maximum )
      if( currentAdl.toLatin1().size() + adlList.toLatin1().size() >= 7450 )
      {
        // Switch to true the send variable and go back to previous element
        send = true;
        i.previous();
      }
      else
      {
        adlList += currentAdl;
      }
    }

    // contact is in the pending list, so get user confirmation.
    if ( currentContact->isPending() )
    {
      // The user should decide in which list add it
      emit contactAddedUser( currentContact->getHandle() );
    }

    // Send the buffer if the send variable is true or the current contact is the last of the list
    if( send || ! i.hasNext() )
    {
      sendPayloadMessage( "ADL", QString::null, "<ml l=\"1\">" + adlList + "</ml>" );
      adlList = "";
      send = false;
    }
  }
}



// Send the RML command
void MsnNotificationConnection::putRml( const QString &handle, int list )
{
  const QStringList splittedHandle( handle.split( "@" ) );

  const QString payload( "<ml>"
                          "<d n=\"" + splittedHandle[1] + "\">"
                            "<c n=\"" + splittedHandle[0] + "\" l=\"%1\" t=\"1\" />"
                          "</d>"
                         "</ml>" );

  sendPayloadMessage( "RML", QString::null, payload.arg( QString::number( list ) ) );
}



// Initialize the object
bool MsnNotificationConnection::initialize()
{
  if ( initialized_ )
  {
    kmDebug() << "already initialized!";
    return false;
  }
  if ( ! MsnConnection::initialize() )
  {
    kmDebug() << "Couldn't initialize base class.";
    return false;
  }

  // Determine which payload commands can be received by the Notification Connection
  QStringList payloadCommands;
  payloadCommands << "MSG"   // Server message
      << "UBX"   // contact personal status message
      << "UBN"
      << "GCF"   // server config file
      << "NOT"   // msn alerts, msn calendar
      << "IPG"   // contact page
      << "ADL"
      ;

  setAcceptedPayloadCommands( payloadCommands );

  // Pass the current contactlist reference to the currentaccount
  // so other classes can access contacts from a central point.
  currentAccount_ = CurrentAccount::instance();
  currentAccount_->setContactList( contactList_ );

  // Connect the activation signal to send notification events
  connect( NotificationManager::instance(), SIGNAL(          eventActivated(NotificationManager::EventSettings,NotificationManager::Buttons) ),
           this,                            SLOT  ( slotErrorEventActivated(NotificationManager::EventSettings,NotificationManager::Buttons) ) );

  initialized_ = true;
  return initialized_;
}



/**
 * @brief The passport login succeeded.
 *
 * When this method gets called, CurrentAccount will contain all
 * the needed passport security tokens.
 */
void MsnNotificationConnection::loginSucceeded()
{
  // Delete the login handler
  // Delay deletion so it can complete the method.
  deleteSoapClient( passportLoginService_ );
  passportLoginService_ = 0;

  // Initialize the GCrypt library if it wasn't done yet
  if( !gcry_control( GCRYCTL_INITIALIZATION_FINISHED_P ) )
  {
    gcry_check_version( 0 );

    // We don't need secure memory for this, I guess... Passwords and
    // everything have already been sent.
    gcry_control( GCRYCTL_DISABLE_SECMEM, 0 );
    gcry_control( GCRYCTL_INITIALIZATION_FINISHED, 0 );

    if( !gcry_control( GCRYCTL_INITIALIZATION_FINISHED_P ) )
    {
      qWarning() << "Failed to initialize GCrypt.";
      return;
    }
  }

  // Create the IV key for triple des encryption
  const unsigned short ivLength = 8;
  char *rawIv = new char[ ivLength ];
  gcry_create_nonce( rawIv, ivLength );
  QByteArray iv( rawIv, ivLength );
  delete [] rawIv;

  // Create the keys and hashes needed
  QByteArray key1, key2, key3;
  QByteArray hash, des3hash;
  QByteArray nonce( nonceBase64_ );

  // Compute the value to send with USR SSO command
  QString magic1( "WS-SecureConversationSESSION KEY HASH"       );
  QString magic2( "WS-SecureConversationSESSION KEY ENCRYPTION" );

  key1 = QByteArray::fromBase64( currentAccount_->getToken( "MessengerClearProof" ).toLatin1() );
  key2 = KMessShared::deriveKey( key1, magic1.toLatin1() );
  key3 = KMessShared::deriveKey( key1, magic2.toLatin1() );
  hash = KMessShared::createHMACSha1( key2, nonce );

  // The windows api always pads using \x08 so we pad 8 bytes.
  nonce += QByteArray ( 8, '\x08' );

  // Compute the 3des encryption
  des3hash = createTripleDes ( key3, nonce, iv );
  if( des3hash.isEmpty() )
  {
    return;
  }

  // Create the required 128 byte padded struct
  MSNPMSNG blob;
  blob.headerSize   = qToLittleEndian( 28 );
  blob.cryptMode    = qToLittleEndian( 1 ); // CRYPT_MODE_CBC
  blob.cipherType   = qToLittleEndian( 0x6603 ); // Triple DES
  blob.hashType     = qToLittleEndian( 0x8004 ); // SHA-1
  blob.ivLength     = qToLittleEndian( iv.size() );
  blob.hashLength   = qToLittleEndian( 20 ); // Length of the HMAC hash
  blob.cipherLength = qToLittleEndian( 72 );

  // Copy the IV Bytes, the hash and chiper value in blob struct
  memcpy( (char*) blob.ivBytes, iv.constData(), iv.size() );
  memcpy( (char*) blob.hashBytes, hash.constData(), hash.size() );
  memcpy( (char*) blob.cipherBytes, des3hash.constData(), des3hash.size() );

  // Create the byte arrays and send for login
  QByteArray blobs( (char*) &blob, 128 );

  sendCommand( "USR", "SSO S " + currentAccount_->getToken( "MessengerClear" ) + " " + blobs.toBase64() );
}


// The passport login failed, username/password was incorrect
void MsnNotificationConnection::loginIncorrect()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Passport login was incorrect.";
#endif

  // NOTE: a similar thing also happens in soapRequestFailed(),
  // but for fatal SOAP errors.

  slotError( "Authentication failed. Wrong login credentials.", MsnSocketBase::ERROR_AUTH_LOGIN );

  // Delete the login handler
  // Delay deletion so it can complete the method.
  deleteSoapClient( passportLoginService_ );
  passportLoginService_ = 0;

}



// Move the contact to another group.
void MsnNotificationConnection::moveContact(QString handle, QString fromGroupId, QString toGroupId)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Moving '" << handle << "' from '" << fromGroupId << "' to '" << toGroupId << "'.";
#endif

  AddressBookService *addressBook = createAddressBookService();
  Contact *contact = contactList_->getContactByHandle( handle );
  if( contact == 0 )
  {
    return;
  }

  addressBook->addContactToGroup( contact->getGuid(), toGroupId );

  // Check if the "from group" is empty or not exists.
  if( fromGroupId == "0" || fromGroupId.isEmpty() )
  {
    return;
  }

  addressBook->deleteContactFromGroup( contact->getGuid(), fromGroupId );
}



// Open a connection to the server
bool MsnNotificationConnection::openConnection()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Opening connection.";
#endif
  if ( !initialized_ )
  {
    kmWarning() << "Notification should be initialized before attempting to connect.";
    return false;
  }

  enableNotifications_ = false;

  // If the last connection was using HTTP, try again using TCP first.
  switchToTcpSocket();

  // Initialize the contact list
  contactList_->reset( true );

#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_INIT(this, "NS messenger.hotmail.com");
#endif

  emit statusMessage( i18n( "Connecting..." ), false );

  // Set the flag to avoid an MSN server problem. See gotUsr()
  isInitiatingConnection_ = true;

  // Attempt to connect to the default MSN server
  connectToServer();

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Socket started connecting to the server. Started timer.";
#endif

  return true;
}



// Parse a regular command
void MsnNotificationConnection::parseCommand( const QStringList& command )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Parsing command '" << command[0] << "'.";
#endif

  // Allow some time until the next message is received
  if( loginTimer_.isActive() )
  {
    loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT );
  }

  // Find out what kind of command we've received. Numbers are errors, the others server commands.
  bool isNumber = false;
  command[0].toInt( &isNumber );

  // If it's not a command, something has went wrong.
  if( isNumber )
  {
    kmWarning() << "Invalid command received:" << command;
    return;
  }

  // Identify and handle the command
  if( command[0] == "ADL" )
  {
    gotAdl( command );
  }
  else if ( command[0] == "BLP" )
  {
    // Unimplemented. This is a privacy setting. (buddy-list-policy?)
  }
  else if ( command[0] == "BPR" )
  {
    // Unimplemented. This contains buddy properties.
  }
  else if ( command[0] == "CHG" )
  {
    gotChg( command );  // Status change
  }
  else if ( command[0] == "CHL" )
  {
    gotChl( command );  // Server-side ping, aka 'challenge'
  }
  else if ( command[0] == "CVR" )
  {
    gotCvr( command );  // Client version info
  }
  else if ( command[0] == "FLN" )
  {
    gotFln( command );  // Contact offline
  }
  else if ( command[0] == "ILN" )
  {
    gotIln( command );  // Contact initially online
  }
  else if ( command[0] == "NLN" )
  {
    gotNln( command );  // Contact now online
  }
  else if ( command[0] == "OUT" )
  {
    gotOut( command );  // Disconnection from server
  }
  else if ( command[0] == "PRP" )
  {
    gotPrp( command );  // Contact property change
  }
  else if ( command[0] == "QRY" )
  {
    // Ignored. Indicates the challenge was succesful, contains no parameters.
  }
  else if ( command[0] == "RNG" )
  {
    gotRng( command );  // Chat or connection requests
  }
  else if( command[0] == "RML" )
  {
    gotRml( command );
  }
  else if( command[0] == "RFS" )
  {
    // Receveid the request for refresh list
    // so the client reply with adl command
    putAdl();
  }
  else if( command[0] == "UBN" )
  {
    // For the moment is useless
    // Require more investigate
  }
  else if ( command[0] == "SBP" )
  {
    // Unimplemented.
  }
  else if ( command[0] == "SBS" )
  {
    // Ignored. The meaning of this command is unknown, but it's related to the
    // user's mobile credits (MSN Mobile).
    // See: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#SBS for details
  }
  else if ( command[0] == "URL" )
  {
    gotUrl( command );  // Hotmail folder info
  }
  else if ( command[0] == "USR" )
  {
    gotUsr( command );  // User authentication information
  }
  else if ( command[0] == "UUX" )
  {
    // Ignored. It's an echo back from our UUX command. Unlike other commands,
    // the UI is updated for this command already before the server confirms.
  }
  else if ( command[0] == "VER" )
  {
    gotVer( command );  // Version information
  }
  else if ( command[0] == "XFR" )
  {
    gotXfr( command );  // Server transfer data
  }
  else
  {
    // No need to bugger the common user with strange error messages
    kmWarning() << "Unknown command" << command[0] << "received from the server! Full string:" << command.join( " " );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    slotError( i18n( "Unknown command received from the server: %1", command[0] ), MsnSocketBase::ERROR_INTERNAL );
#endif
  }
}



// Parse an error command
void MsnNotificationConnection::parseError( const QStringList& command, const QByteArray &payloadData )
{
  // See if the error was from an open switchboard request.
  if( ! openRequests_.isEmpty() )
  {
    int transactionId = command[1].toInt();
    foreach( ChatInformation *chatInvite, openRequests_ )
    {
      if( chatInvite->getTransactionId() == transactionId )
      {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
        kmDebug() << "Removing chat invitation from list.";
#endif

        openRequests_.removeAll( chatInvite );
        delete chatInvite;
        break;
      }
    }
  }

  // Relay the error detection to the base class
  MsnConnection::parseError( command, payloadData );
}



// Parse a message command
void MsnNotificationConnection::parseMimeMessage( const QStringList& command, const MimeMessage &mainMessage )
{
  Q_UNUSED( command );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Got " << mainMessage.getSubValue("Content-Type") << ".";
#endif

  const QString contentType( mainMessage.getSubValue("Content-Type") );
  const MimeMessage message( mainMessage.getBody() );

#ifdef KMESSDEBUG_NOTIFICATION_MESSAGES
  mainMessage.print();
  kmDebug() << "Message body as mime:";
  message.print();
#endif

  // Check the message type
  if ( contentType == "text/x-msmsgsprofile" )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Load profile information.";
#endif
    uint externalPort = mainMessage.getValue("ClientPort").toUInt();
    externalPort      = ((externalPort & 0x00ff) << 8) | ((externalPort & 0xff00) >> 8); // Byte swapped

    currentAccount_->setAccountInformation( mainMessage.getValue("MSPAuth"),
                                            mainMessage.getValue("preferredEmail"),
                                            mainMessage.getValue("sid"),
                                            mainMessage.getValue("lang_preference"),
                                            mainMessage.getValue("EmailEnabled") == "1",
                                            mainMessage.getValue("ClientIP"),
                                            externalPort,
                                            getLocalIp() );
  }
  else if ( contentType == "text/x-msmsgsinitialmdatanotification"
       ||   contentType == "text/x-msmsgsinitialemailnotification"
       ||   contentType == "text/x-msmsgsoimnotification" )
  {
    // Parse both messages the same way,
    // the x-msmsgsinitialmdatanotification message is an extended version of x-msmsgsoimnotification
    // description of the fields: http://msnpiki.msnfanatic.com/index.php/MSNP13:Offline_IM

    // Parse the URL fields (msmsgsinitialmdatanotification messages).
    if( message.hasField("Post-URL") )
    {
      currentAccount_->setEmailUrl( message.getValue("Post-URL") );
    }

    // Parse the Mail-Data field.
    const QString mailDataSrc( message.getValue("Mail-Data") );
    if( mailDataSrc == "too-large" )
    {
      // After a certain ammount of messages, the XML is replaced with "too-large".
      // Use SOAP to request the actual mail data.
      if( offlineImService_ == 0 )
      {
        offlineImService_ = createOfflineImService();
      }
      offlineImService_->getMetaData();
    }
    else
    {
      // Mail-Data was received directly in the message.
      QDomDocument mailData;
      if( ! mailData.setContent(message.getValue("Mail-Data")) )
      {
        kmWarning() << "Could not parse XML Mail-Data field!";
        return;
      }

      receivedMailData( mailData.namedItem("MD").toElement() );
    }
  }
  else if ( contentType == "text/x-msmsgsemailnotification" )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "An email was received"
              << " from \""             << message.getValue("From")        << "\""
              << " with the subject \"" << message.getValue("Subject")     << "\""
              << " in the folder \""    << message.getValue("Dest-Folder") << "\"." << endl;
#endif

    // If a message delta is to the active folder, emails were moved into it, so use a negative deletion
    if ( message.getValue("Dest-Folder") == "ACTIVE" )
    {
      // Received in the inbox
      currentAccount_->changeNoEmails( 1 );
    }

    emit newEmail( message.decodeRFC2047String( message.getValue("From").toUtf8() ),
                   message.decodeRFC2047String( message.getValue("Subject").toUtf8() ),
                   (message.getValue("Dest-Folder") == "ACTIVE"),
                   message.getValue("id"),
                   message.getValue("Message-URL"),
                   message.getValue("Post-URL") );
  }
  else if ( contentType == "text/x-msmsgsactivemailnotification" )
  {
    int messageDelta = message.getValue("Message-Delta").toInt();

    // If a message delta is from the active folder, emails were deleted
    if ( message.getValue("Src-Folder") == "ACTIVE" )
    {
      currentAccount_->changeNoEmails( -1 * messageDelta );
    }
    // If a message delta is to the active folder, emails were moved into it
    else if ( message.getValue("Dest-Folder") == "ACTIVE" )
    {
      currentAccount_->changeNoEmails( messageDelta );
    }

    // When deleting Offline-IM messages, this message contains:
    // Src-Folder: .!!OIM
    // Dest-Folder: .!!trAsH
    // Message-Delta: <number of messages deleted>
  }
  else if( contentType == "application/x-msmsgssystemmessage" )
  {
     // This is a maintenance info message from the server.

    if( message.getValue("Type") == "1" )
    {
      int timeLeft = message.getValue("Arg1").toInt();
      const QString textTimeLeft( i18ncp( "Time left before server maintenance",
                                          "%1 minute", "%1 minutes", timeLeft ) );
      if( timeLeft > 2 )
      {
        emit statusMessage( i18n("Server closes for maintenance in %1!", textTimeLeft ), false );
      }
      else
      {
        slotError( i18nc( "Server maintenance dialog box text",
                          "The Live Messenger server will be going down in %1 for maintenance.", textTimeLeft ), MsnSocketBase::ERROR_SERVER );
      }

    }
  }
  else
  {
    kmWarning() << "Content-Type '" << contentType << "' not reconized, message ignored.";
  }
}



// Parse a payload command
void MsnNotificationConnection::parsePayloadMessage( const QStringList &command, const QByteArray &payload )
{
  if( command[0] == "UBX" )
  {
    gotUbx( command, payload );
  }
  else if( command[0] == "GCF" )
  {
    // Ignore this
    // Contains policy information, requesting clients
    // to disable certain features or possibly block
    // certain filenames/contents.
#ifdef KMESSDEBUG_NOTIFICATION_MESSAGES
    kmDebug() << "Ignoring security configuration file, message dump follows." << endl << QString::fromUtf8( payload.data(), payload.size() );
#endif
  }
  else if( command[0] == "NOT" )
  {
    // Ignore "notification" messages coming from MSN Calendar, MSN Alerts, and MSN Mobile.
#ifdef KMESSDEBUG_NOTIFICATION_MESSAGES
    kmDebug() << "Ignoring text event notification, message dump follows." << endl << QString::fromUtf8( payload.data(), payload.size() );
#endif
  }
  else if( command[0] == "ADL" )
  {
    gotAdl( command, payload );
  }
  else
  {
    kmWarning() << "Unhandled payload command: " << command[0] << "!"
                << " (message dump follows)\n" << QString::fromUtf8(payload.data(), payload.size())  << endl;
  }
}



/**
 * @brief Send the personal status and current media fields.
 *
 * MSNP13 uses an extra field on the UUX command
 *
 */
void MsnNotificationConnection::putUux()
{
  // TODO Calculate a GUID for the specific machine, or (if possible) find out how
  // WLM obtains the GUID and replicate the algorithm (to have GUIDs compatible with
  // WLM). If that's not needed, add the machine name (WLM knows it).

  const QString xml( "<Data>"
                      "<PSM>" + lastPsm_ + "</PSM>"
                      "<CurrentMedia>" + lastCurrentMedia_ + "</CurrentMedia>"
                      "<MachineGuid>{F26D1F07-95E2-403C-BC18-D4BFED493428}</MachineGuid>"
                     "</Data>" );

  sendPayloadMessage( "UUX", QString::null, xml );
}



/**
 * @brief Send the version command.
 *
 * Send versions of protocol supported by the client
 *
 */
void MsnNotificationConnection::putVer()
{
  // Indicate which protocol version we support.
  // Most of the time it seems that:
  // - even versions are for minor changes.
  // - odd version is are for new revisions.
  sendCommand( "VER", "MSNP15 MSNP14 MSNP13 CVR0" );

  // Reset the addressBookService_ because when the connection is closed the
  // function deleteSoap in MsnConnection delete the soap class.
  addressBookService_ = 0;
}



// Received the Mail-Data field over SOAP or at the notification connection.
void MsnNotificationConnection::receivedMailData( QDomElement mailData )
{
#ifdef KMESSTEST
  KMESS_ASSERT( mailData.tagName() == "MD" );
#endif

  QDomNodeList childNodes = mailData.childNodes();
  for( int i = 0; i < childNodes.count(); i++ )
  {
    QDomElement childNode = childNodes.item(i).toElement();
    QString     childName( childNode.nodeName() );

    if( childName == "E" )
    {
      // Found an initial email status notification.
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      kmDebug() << "Load initial email information.";
#endif

      QDomElement inboxUnread  = childNode.namedItem("IU").toElement();
      QDomElement othersUnread = childNode.namedItem("OU").toElement();

      if( inboxUnread.isNull() || othersUnread.isNull() )
      {
        kmWarning() << "Reveived email notification, but 'IU' and 'OU' elements are missing!";
      }

#ifdef KMESSTEST
      KMESS_ASSERT( ! inboxUnread.isNull()  );
      KMESS_ASSERT( ! othersUnread.isNull() );
#endif

      currentAccount_->setInitialEmailInformation( inboxUnread.text().toInt(),
                                                   othersUnread.text().toInt() );
    }
    else if( childName == "M" )
    {
      // Using this for-loop, multiple 'M' elements are supported.

      // Extract the message ID.
      QString messageId( childNode.namedItem("I").toElement().text() );
      if( messageId.isEmpty() )
      {
        kmWarning() << "Unable to parse Mail-Data payload, offline-im message-id not found.";
        return;
      }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      kmDebug() << "Received offline-IM notification '" << messageId << "'";
#endif

      // Create a SOAP client to request the Offline-IM
      pendingOfflineImMessages_.append(messageId);
    }
    else if( childName == "Q" )
    {
      // Not implemented
    }
    else
    {
      // Warn for unsupported tags.
      kmWarning() << "Unsupported tag '" << childName << "' encountered in Mail-Data payload!";
    }
  }

  // Start a new client if there are pending messages, and no OfflineImService is active
  if( ! pendingOfflineImMessages_.isEmpty() )
  {
    // Initialize on demand
    if( offlineImService_ == 0 )
    {
      offlineImService_ = createOfflineImService();
    }

    // Request the first offline-im message if the current class was
    // not processing already. (otherwise receivedOfflineIm() picks the next one).
    if( offlineImService_->isIdle() )
    {
      offlineImService_->getMessage( pendingOfflineImMessages_.first(), false );
      // Processing continues at receivedOfflineIm()
    }
  }
}



// An Offline-IM message was downloaded
void MsnNotificationConnection::receivedOfflineIm( const QString &messageId, const QString &from, const QString &to,
                                                   const QDateTime &date, const QString &body,
                                                   const QString &runId, int sequenceNum )
{
#ifdef KMESSDEBUG_NOTIFICATION
  kmDebug() << "Received an offline IM message "
            << "(from=" << from
            << " runid=" << runId
            << " sequenceNum=" << sequenceNum << ")" << endl;
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( to == currentAccount_->getHandle() );
#else
  Q_UNUSED( to ); // Avoid compiler warning
#endif

  // Find the SOAP client to send the next request
  if( KMESS_NULL(offlineImService_) )
  {
    slotError( i18n( "KMess could not process Offline-IM messages.<br/>Details: %1",
                     i18n("SOAP client is no longer valid.") ),
               MsnSocketBase::ERROR_INTERNAL );

    qDeleteAll( offlineImMessages_ );
    offlineImMessages_.clear();
    pendingOfflineImMessages_.clear();
    return;
  }

  // Store message until all messages are received.
  OfflineImMessage *offlineIm = new OfflineImMessage;
  offlineIm->messageId   = messageId;
  offlineIm->body        = body;
  offlineIm->date        = date;
  offlineIm->from        = from;
  offlineIm->runId       = runId;
  offlineIm->sequenceNum = sequenceNum;

  // Order by date
  uint insertPos = 0;
  foreach( OfflineImMessage *nextOfflineIm, offlineImMessages_ )
  {
    if( nextOfflineIm->sequenceNum > sequenceNum )
    {
#ifdef KMESSDEBUG_NOTIFICATION
      kmDebug() << "inserted message '" << date << "', #" << sequenceNum << " "
               << "before '" << nextOfflineIm->date << "', #" << nextOfflineIm->sequenceNum << "." << endl;
#endif
      break;
    }

    insertPos++;
  }

  offlineImMessages_.insert( insertPos, offlineIm );


  // Remove from pending list
  if( pendingOfflineImMessages_.removeAll( messageId ) == 0 )
  {
    kmWarning() << "Could not remove "
                  "message '" << messageId << "' from the list!" << endl;
  }


  // If there are more messages, request the next one.
  if( ! pendingOfflineImMessages_.isEmpty() )
  {
#ifdef KMESSDEBUG_NOTIFICATION
    kmDebug() << "Requesting the next offline-im message.";
#endif

    // Not all messages are received yet, request next
    offlineImService_->getMessage( pendingOfflineImMessages_.first(), false );
    return;
  }


  // All messages received, and sorted,
  // display the messages in the chat windows.

#ifdef KMESSDEBUG_NOTIFICATION
  kmDebug() << "All messages received, displaying messages in chat window.";
#endif

  const ContactBase *contact = 0;
  QString name;
  QString picture;
  QStringList messageIds;

  foreach( OfflineImMessage *offlineIm, offlineImMessages_ )
  {
    messageIds.append( offlineIm->messageId );

    // Get contact details
    // Re-use previous results if message is from the same contact
    if( contact == 0 || contact->getHandle() != offlineIm->from )
    {
      contact = CurrentAccount::instance()->getContactByHandle( offlineIm->from );
      if( contact != 0 )
      {
        // Display the name in a simple way, to make it clearer to
        // see the CSS style for Offline IM
        name    = contact->getFriendlyName( STRING_CLEANED );
        picture = contact->getContactPicturePath();
      }
      else
      {
        name    = from;
        picture = QString::null;
      }
    }

    // Send out the chat message
    emit offlineMessage( ChatMessage( ChatMessage::TYPE_OFFLINE_INCOMING,
                                      ChatMessage::CONTENT_MESSAGE,
                                      true,
                                      offlineIm->body,
                                      offlineIm->from,
                                      name,
                                      picture,
                                      QFont(),
                                      QString::null,
                                      offlineIm->date ) );
  }

  // Delete all local messages
  qDeleteAll( offlineImMessages_ );
  offlineImMessages_.clear();

  // Request to remove the Offline-IM message from the online storage.
  offlineImService_->deleteMessages( messageIds );
  // Keep reference to SOAP service so the connection is re-used for other messages.
}



// Remove a contact from the contact list completely
void MsnNotificationConnection::removeContact(QString handle, bool block)
{
  Contact *contact = contactList_->getContactByHandle( handle );
  if(KMESS_NULL(contact)) return;

  QString contactId( contact->getGuid() );
  if( contactId.isEmpty() )
  {
    return;
  }

  // If the user checks the block option then put the contact in Block Membership list ( if not already present )
  if( block && ! contact->isBlocked() )
  {
    // Block the contact
    blockContact( handle );
  }

  // Remove the contact from AB
  AddressBookService *addressBook = createAddressBookService();;
  addressBook->deleteContact( contactId );

  // if not blocking, make sure we re-add to the Allow list.
  // ABContactDelete removes them from the Allow list for some reason. This was what
  // caused the "you've been added" dialog to popup for deleted contacts on the next login.
  if ( ! block && ! contact->isBlocked() )
  {
    allowContact( handle );
  }
}



//remove a contact from a single group
void MsnNotificationConnection::removeContactFromGroup(QString handle, QString groupId)
{
  AddressBookService *addressBook = createAddressBookService();
  Contact *contact = contactList_->getContactByHandle( handle );
  if( contact == 0 )
  {
    return;
  }

  addressBook->deleteContactFromGroup( contact->getGuid(), groupId );
}



// Remove the current media advertising in the personal status.
void MsnNotificationConnection::removeCurrentMedia()
{
  lastCurrentMedia_ = QString::null;
  putUux();
}



// Remove a group
void MsnNotificationConnection::removeGroup(QString id)
{
  AddressBookService *addressBook = createAddressBookService();;
  addressBook->deleteGroup( id );
}



// Rename a group
void MsnNotificationConnection::renameGroup(QString id, QString newName)
{
  AddressBookService *addressBook = createAddressBookService();;
  addressBook->renameGroup( id, newName );
}



// Request a switchboard for a chat with a contact
void MsnNotificationConnection::requestSwitchboard( QString handle, ChatInformation::ConnectionType type )
{
  // Protect again invalid uses.
  if( handle.isEmpty() )
  {
    kmWarning() << "no contact handle set!";
    return;
  }

  // See if there is a open chat request.
  foreach( ChatInformation *switchboardInfo, openRequests_ )
  {
    if( switchboardInfo->getContactHandle() == handle )
    {
#ifdef KMESSDEBUG_NOTIFICATION
      kmDebug() << "A pending switchboard for contact "
                << handle << " is already present." << endl;
#endif
      return;
    }
  }

  // Open directly fake switchboards for offline contacts
  if( type == ChatInformation::CONNECTION_OFFLINE )
  {
    ChatInformation *offlineChatInfo = new ChatInformation( this, handle, -1, type );
    emit startSwitchboard( *offlineChatInfo );
    return;
  }

  // Send the command
  int transactionId = sendCommand( "XFR", "SB" );

  // Store the pending request information so the return value can be handled.
  openRequests_.append( new ChatInformation( this, handle, transactionId, type ) );
}



// Load the contact list properties
void MsnNotificationConnection::readProperties()
{
#ifdef KMESSDEBUG_NOTIFICATION
  kmDebug() << "Loading properties for the notification connection";
#endif

  // Load the contact list's settings
  contactList_->readProperties();
}



// Save the contact list properties
void MsnNotificationConnection::saveProperties()
{
#ifdef KMESSDEBUG_NOTIFICATION
  kmDebug() << "Saving properties for the notification connection";
#endif

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

  // Save the contact list's settings for this account
  contactList_->saveProperties();
}



/**
 * @brief The authentication process has timed out
 *
 * Called by the login timer, when the login timer has expired
 */
void MsnNotificationConnection::slotAuthenticationFailed()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Connection not successful within time limit.";
#endif

  // Bounce back to the unified error handler to give the user a message
  slotError( i18n( "Authentication time limit exceeded" ), MsnSocketBase::ERROR_AUTH_TIME_LIMIT );
}



// Contact added, notify it to contactlist
void MsnNotificationConnection::slotContactAdded( const QString &handle, const QString &contactId, const QStringList &groupsId )
{
  int lists = Contact::MSN_LIST_ALLOWED | Contact::MSN_LIST_FRIEND;

  // If the contact already exists, the user requested re-adding to FL list.
  Contact *contact = contactList_->getContactByHandle( handle );
  if( contact != 0 )
  {
    contact->setGuid( contactId );
    contact->setFriend( true );

    // Ensure to block the contact if "already blocked"
    if( contact->isBlocked() )
    {
      lists ^= Contact::MSN_LIST_ALLOWED;
    }
  }
  else
  {
    // The contact is added to the reverse list: when the user adds a contact who has added the user,
    // the user must appear to be present in the contact's list (this avoids seeing the newly added
    // contact as not having the user in his/her list).
    contactList_->addContact( handle, handle, lists | Contact::MSN_LIST_REVERSE, QStringList(), contactId );
  }

  // Check if the user has requested that contact is putted into groups
  if( ! groupsId.isEmpty() )
  {
    foreach( const QString &groupId, groupsId )
    {
      if( ! contactList_->getGroupById( groupId ) )
      {
        continue;
      }

      addContactToGroup( handle, groupId );
    }
  }

  // Ask the server to add the contact to FL and Allow/Block list.
  putAdl( handle, lists );
}



// Contact added to group
void MsnNotificationConnection::slotContactAddedToGroup( const QString &contactId, const QString &groupId )
{
  Contact *contact = contactList_->getContactByGuid( contactId );
  if( contact == 0 )
  {
    return;
  }

  contact->addGroupId( groupId );
}



// Contact blocked
void MsnNotificationConnection::slotContactBlocked( const QString &handle )
{
  Contact *contact = contactList_->getContactByHandle( handle );
  if( contact != 0 )
  {
    contact->setBlocked( true );
    contact->setAllowed( false );
  }
}



// Contact deleted
void MsnNotificationConnection::slotContactDeleted( const QString& contactId )
{
  Contact *contact = contactList_->getContactByGuid( contactId );
  if( contact == 0 )
  {
    return;
  }

  const QString& handle = contact->getHandle();
  contact->setFriend( false );

  // Remove it from our FL.
  putRml( handle, Contact::MSN_LIST_FRIEND );
}



// Contact removed from group
void MsnNotificationConnection::slotContactDeletedFromGroup( const QString &contactId, const QString &groupId )
{
  Contact *contact = contactList_->getContactByGuid( contactId );
  if( contact == 0 )
  {
    return;
  }

  contact->removeGroupId( groupId );
}



void MsnNotificationConnection::slotGotAddressBookList( const QList< QHash<QString,QVariant> > &contacts )
{
  // Stop the login timer to allow parsing the address book.
  loginTimer_.stop();

  QListIterator<QHash<QString,QVariant> > contact( contacts );
  int lists;

  // Cycle for each contact
  while( contact.hasNext() )
  {
    // Grep the main informations
    QHash<QString,QVariant> contactInfo( contact.next() );

    const QString       handle( contactInfo.take(       "handle" ).toString() );
    const QString friendlyName( contactInfo.take( "friendlyName" ).toString() );
    const QString    contactId( contactInfo.take(    "contactId" ).toString() );
    const QStringList guidList( contactInfo.take(     "guidList" ).toStringList() );
    bool isMessenger( contactInfo.take( "isMessengerUser" ).toBool() );

    // Add each contact in our AB to contactlist ( FL )
    lists = contactsRole_.take( handle ) | Contact::MSN_LIST_FRIEND;

    Contact *contact = contactList_->addContact( handle, friendlyName, lists , guidList , contactId, isMessenger );
    // Set the remaining informations
    if( contact != 0 )
    {
      contact->setInformations( contactInfo );
    }
  }

  // If there are contacts not present in AB, but present in Membership List
  // add them to contactlist
  if( contactsRole_.count() > 0 )
  {
    QHashIterator< QString, int > i( contactsRole_ );
    while( i.hasNext() )
    {
      i.next();
      const QString handle( i.key() );
      contactList_->addContact( handle , handle , i.value(), QStringList(), "" );
    }

    contactsRole_.clear();
  }

  // Allow some time until the next message is received
  if( loginTimer_.isActive() )
  {
    loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT );
  }

  putAdl();
}



/**
 * @brief Called when address book services retrieved membership lists.
 *
 * In this list there are informations about FL, AL, BL lists.
 *
 */
void MsnNotificationConnection::slotGotMembershipLists( const QString &serviceType, const QHash<QString,int> &contactsRole  )
{
  if( serviceType != "Messenger" )
  {
    kmWarning() << "Unable to parse membership list:" << serviceType;
    return;
  }

  contactsRole_ = contactsRole;

  emit statusMessage( i18n("Waiting for contact list..."), false );

  // Allow some time until the next message is received
  if( loginTimer_.isActive() )
  {
    loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT );
  }

  AddressBookService *addressBook = createAddressBookService();
  addressBook->retrieveAddressBook();
}



/**
 * @brief Called when address book services parsed a group.
 *
 * @param id the id of the group
 * @param name the name of the group
 */
void MsnNotificationConnection::slotGotGroup( const QString &id, const QString &name )
{
  contactList_->addGroup( id, name );
}



void MsnNotificationConnection::slotContactUnblocked( const QString &handle )
{
  Contact *contact = contactList_->getContactByHandle( handle );
  if( contact != 0 )
  {
    contact->setBlocked( false );
    contact->setAllowed( true );
  }
}



// Show warning notifications
void MsnNotificationConnection::slotWarning( const QString &warning, bool isImportant )
{
  if( isImportant )
  {
      KMessageBox::sorry( 0,
                          i18nc( "Connection warning: dialog box with message",
                                 "<p><i>Warning</i>: %1</p>", warning ),
                          i18nc( "Error dialog box title", "MSN Warning" ) );
  }
  else
  {
    // Save the error details in the event settings

    NotificationManager::EventSettings settings;
    settings.sender  = this;
    settings.contact = 0;
    settings.widget  = 0;
    settings.buttons = NotificationManager::BUTTON_HIDE;

    // Notify the user about the network error
    NotificationManager::instance()->notify( "status", warning, settings );
  }
}



// Unblock the given contact
void MsnNotificationConnection::unblockContact( QString handle )
{
  putRml( handle, Contact::MSN_LIST_BLOCKED );
  putAdl( handle, Contact::MSN_LIST_ALLOWED );

  AddressBookService *addressBook = createAddressBookService();
  addressBook->unblockContact( handle );
}


/**
 * @brief Called when the connection has been established.
 *
 * This method calls putVer() to start the version exchange.
 */
void MsnNotificationConnection::slotConnected()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Connected to server, sending VER.";
#endif

  // Start the login timer, allowing the defined maximum time between each
  // received command
  loginTimer_.start( NOTIFICATION_COMMAND_INTERVAL_TIMEOUT );

  putVer();
}




/**
 * @brief Shows error dialog boxes
 *
 * Closes the connection, and displays
 * a suitable type of message box.
 *
 * @param  error  The error reason or explanation
 * @param  type   The type of error
 */
void MsnNotificationConnection::slotError( QString error, MsnSocketBase::ErrorType type )
{
  kmWarning() << "MSN Notification Connection error type" << type << ":" << error;

  QString message;
  QString additionalInfo;
  QString detailedMessage;
  bool tryReconnecting = true;
  bool useNotification = true;

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  // Add more details about the error in developer builds.
  additionalInfo = i18n( "<br />Internal error reason: %1", error );
#endif

  // If the server can't be contacted, try again with HTTP before giving up
  if( ( isInitiatingConnection_ && type == MsnSocketBase::ERROR_DROP )
  || type == MsnSocketBase::ERROR_CONNECTING
  || type == MsnSocketBase::ERROR_CONNECTION_TIME_LIMIT )
  {
    if( switchToHttpSocket() )
    {
      // Note how the first error is silently discarded. If it's something important, like
      // the "server is too busy" error, it'll be received when connecting via HTTP anyways.
      return;
    }
  }

  // if there was a proxy error, try removing the proxy details and
  // trying again.
  if ( type == MsnSocketBase::ERROR_PROXY )
  {
    QNetworkProxy::setApplicationProxy( QNetworkProxy::NoProxy );
    openConnection();
    return;
  }

  // Clean up the mess left by the SOAP classes
  if( qobject_cast<OfflineImService*>( sender() ) != 0 )
  {
    // No need to request more, it just failed.
    qDeleteAll( offlineImMessages_ );
    offlineImMessages_.clear();
  }

  // We have to save the lastStatus before closing the connection, because closeConnection() resets it,
  // but it also resets CurrentAccount so we need to retrieve it now, and set it later.
  // TODO XXX HACK FIXME: move this logic to KMessSession (::getInitialStatus?)
  // see the IRC logs in #kmess2 at 14 july 2009, 01:41:44...
  int lastStatus = -1;
  if( currentAccount_ != 0 )
  {
    lastStatus = currentAccount_->getStatus();
  }

  // First disconnect
  closeConnection();

  // Save the status, to use it again when reconnecting
  lastStatus_ = lastStatus;

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Changed the lastStatus to " << MsnStatus::getCode( (Status)lastStatus_ );
#endif

  // Then decide what kind of message to show
  switch( type )
  {
    // Notify that connecting has failed due to a wrong handle or password
    case MsnSocketBase::ERROR_AUTH_LOGIN:
      // This is an immediate error, a dialog box is needed
      KMessageBox::error( 0,
                          i18nc( "Connection error: dialog box",
                                 "<p>Authentication has failed, please verify "
                                 "your account email and password.</p>" ),
                          i18nc( "Error dialog box title", "MSN Error" ) );
      // Do not attempt to reconnect
      tryReconnecting = false;
      // Don't send a passive notification
      useNotification = false;
      break;


    // The user has connected this account from another location
    case MsnSocketBase::ERROR_CONNECTION_OTHER:
      message         = i18nc( "Connection error: passive notification message",
                               "<p>The account <i>%1</i> has been connected from another location.</p>",
                               currentAccount_->getHandle() );
      detailedMessage = i18nc( "Connection error: detailed message",
                               "<p>You have been disconnected: you have connected "
                               "with the account <i>%1</i> from another Messenger client, "
                               "or from another location.</p>",
                               currentAccount_->getHandle() );
      // Do not attempt to reconnect
      tryReconnecting = false;
      break;


    // The HTTP connection failed: there's a redirection page
    case MsnSocketBase::ERROR_CONNECTING_GATEWAY:
      message         = i18nc( "Connection error: passive notification message",
                               "<p>Unable to connect to the Live Messenger service.<br/>"
                               "Maybe you need to authenticate before you can access the network?</p>" );
      detailedMessage = i18nc( "Connection error: detailed message",
                               "<p>KMess could not connect to the Live Messenger servers.<br />"
                               "There may be a problem with your Internet connection, "
                               "or the Live Messenger servers may be temporarily unavailable.<br/>"
                               "It is also possible that an authentication to a web page or proxy "
                               "may be required to access the network.</p>"
                               "<p><a href='%1'>Click here</a> to visit the Messenger service "
                               "status page.</p>",
                               "http://status.messenger.msn.com/Status.aspx" );
      // Do not attempt to reconnect
      tryReconnecting = false;
      break;

    case MsnSocketBase::ERROR_AUTH_COMPUTATION:
      message         = i18n("<p>Unable to resolve the authentication on the client<br/>"
                             "Maybe you do not have installed qca2 and/or qca2-plugin-ossl?</p>" );
      detailedMessage = i18n("<p>KMess could not connect to the Live Messenger servers.<br />"
                             "You probably need to install qca2 and qca2-plugin-ossl to make KMess work.<br/>"
                             "It is also possible that the MSN network is unavailable at the moment.</p>"
                             );
      // Do not attempt to reconnect
      tryReconnecting = false;
      break;


    // Continue as for authentication errors if the switch to HTTP had failed
    case MsnSocketBase::ERROR_CONNECTION_TIME_LIMIT:
    case MsnSocketBase::ERROR_CONNECTING:
    case MsnSocketBase::ERROR_AUTH_TIME_LIMIT:
    case MsnSocketBase::ERROR_SOAP_TIME_LIMIT:
      message         = i18nc( "Connection error: passive notification message",
                               "<p>Unable to connect to the Live Messenger service.</p>" );
      detailedMessage = i18nc( "Connection error: detailed message",
                               "<p>KMess could not connect to the Live Messenger servers.<br />"
                               "There may be a problem with your Internet connection, "
                               "or the Live Messenger servers may be temporarily unavailable.</p>"
                               "<p><a href='%1'>Click here</a> to visit the Messenger service "
                               "status page.</p>",
                               "http://status.messenger.msn.com/Status.aspx" );
      break;

    // Report errors caused by the user
    case MsnSocketBase::ERROR_USER:
      message         = i18nc( "Connection error (Server-reported user error): passive notification message",
                               "<p><i>Error</i>: %1</p>", error );
      detailedMessage = i18nc( "Connection error (Server-reported user error): detailed message",
                               "<p>The Live Messenger server has reported an error:</p>"
                               "<p>%1</p>",
                               error );
      break;

    // Report internal server errors
    case MsnSocketBase::ERROR_SERVER:
    case MsnSocketBase::ERROR_SOAP_TOOMANYREDIRECTS:
    case MsnSocketBase::ERROR_SOAP_RESPONSE:
    case MsnSocketBase::ERROR_SOAP_AUTHENTICATION:
      message         = i18nc( "Connection error (Server-reported server error): passive notification message",
                               "<p><i>Messenger Service Error</i>: %1</p>", error );
      detailedMessage = i18nc( "Connection error (Server-reported server error): detailed message",
                               "<p>The Live Messenger server has reported an error:</p>"
                               "<p>%1</p>",
                               error );
      break;

    // Report internal KMess errors, usually after receiving an error by the MSN server
    case MsnSocketBase::ERROR_INTERNAL:
      message         = i18nc( "Connection error (Server-reported client error): passive notification message",
                               "<p><i>KMess Error</i>: %1</p>", error );
      detailedMessage = i18nc( "Connection error (Server-reported client error): detailed message",
                               "<p>KMess has encountered an internal error:</p>"
                               "<p>%1</p>",
                               error );
      break;

    case MsnSocketBase::ERROR_DROP:
    case MsnSocketBase::ERROR_DATA:
    case MsnSocketBase::ERROR_UNKNOWN:
    case MsnSocketBase::ERROR_SOAP_UNKNOWN:
    default:
      message         = i18nc( "Connection error: passive notification message",
                               "<p>Network connection lost.</p>" );
      detailedMessage = i18nc( "Connection error: detailed message",
                               "<p>Connection to the Live Messenger server has been lost.</p>" );
      break;
  }


  if( useNotification )
  {
    // Save the error details in the event settings
    QMap<QString,QVariant> details;
    details[ "errorType"       ] = (int) type;
    details[ "errorString"     ] = error;
    details[ "detailedMessage" ] = "<html>" + detailedMessage + "</html>";

    NotificationManager::EventSettings settings;
    settings.sender  = this;
    settings.contact = 0;
    settings.widget  = 0;
    settings.buttons = NotificationManager::BUTTON_HIDE | NotificationManager::BUTTON_MORE_DETAILS;
    settings.data    = details;

    // Notify the user about the network error
    NotificationManager::instance()->notify( "status", "<html>" + message + "</html>", settings );
  }

  // Try connecting again
  if( tryReconnecting )
  {
    emit reconnect( currentAccount_->getHandle() );
  }
}



// Displays additional info about network errors
void MsnNotificationConnection::slotErrorEventActivated( NotificationManager::EventSettings settings, NotificationManager::Buttons button )
{
  // The signal wasn't meant for us
  if( settings.sender != this )
  {
    return;
  }

  KDialog *dialog;
  QString developerInfo;
  const QMap<QString,QVariant> &info = settings.data.toMap();

  switch( button )
  {
    case NotificationManager::BUTTON_MORE_DETAILS:
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      kmDebug() << "Showing error details";
#endif

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      // Add more details about the error in developer builds.
      developerInfo = i18nc( "Developer details placed on the network error dialog box",
                             "<p><b>Developer info:</b><br/>"
                             "<i>Error number:</i> %1<br/>"
                             "<i>Error string:</i> %2"
                             "</p>",
                             info["errorType"  ].toString(),
                             info["errorString"].toString() );
#endif

      // We need to create a non-modal informative message box: a modal one would block
      // execution here, and if the user dismisses the box after the notification has
      // already been deleted by KDE, kmess would crash.
      dialog = new KDialog( 0 );
      dialog->setAttribute( Qt::WA_DeleteOnClose );
      dialog->setButtons( KDialog::Ok );
      KMessageBox::createKMessageBox( dialog,
                                      QMessageBox::Information,
                                      info["detailedMessage"].toString() + developerInfo,
                                      QStringList(),
                                      QString(),
                                      0,
                                      KMessageBox::Notify | KMessageBox::AllowLink | KMessageBox::NoExec,
                                      QString() );
      dialog->show();
      break;

    case NotificationManager::BUTTON_HIDE:
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      kmDebug() << "Hiding status notification";
#endif
      // Do nothing
      break;

    default:
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      kmDebug() << "Invalid button" << button;
#endif
      return;
  }
}



/**
 * @brief Unlock notifications after the first seconds of connections
 *
 * The first moments KMess is connected notifications are blocked, to avoid receiving popups before
 * the user could even look at the contact list, possibly distracting him/her or covering screen space.
 */
void MsnNotificationConnection::unlockNotifications()
{
  enableNotifications_ = true;
}

#include "msnnotificationconnection.moc"
