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

#include "mimemessage.h"
#include "msnsocketbase.h"
#include "msnsockettcp.h"
#include "msnsockethttp.h"
#include "multipacketmessage.h"
#include "soap/httpsoapconnection.h"

#include <KLocale>
#include <KMessageBox>

#ifdef KMESSDEBUG_CONNECTION // Area-specific debug statements
  #define KMESSDEBUG_CONNECTION_GENERAL
  #define KMESSDEBUG_CONNECTION_SOCKET
#endif


// Static members initialization
bool MsnConnection::useHttpSocket_( false );


/**
 * @brief The constructor
 *
 * Initializes the sockets, buffers and ping timer.
 *
 * @param  serverType  The type of server connection: TCP or HTTP.
 */
MsnConnection::MsnConnection( MsnSocketBase::ServerType serverType )
: QObject(0)
, ack_(0)
, initialized_(false)
{
  // Create the socket needed for this connection: default to the TCP one
  if( useHttpSocket_ )
  {
    socket_ = new MsnSocketHttp( serverType );
  }
  else
  {
    socket_ = new MsnSocketTcp( serverType );
  }

#ifdef KMESSTEST
  KMESS_ASSERT( socket_ != 0 );
#endif
}



/**
 * @brief The destructor
 *
 * Closes the connection, and deletes the sockets.
 */
MsnConnection::~MsnConnection()
{
  // Disconnect from the server on exit
  if(isConnected())
  {
    disconnectFromServer();
  }

  // Clean up collections
  qDeleteAll( multiPacketBuffer_ );
  multiPacketBuffer_.clear();

  // Off course, delete the SOAP clients now here too.
  deleteSoapClients();

  // Delete the socket
  delete socket_;

#ifdef KMESSDEBUG_CONNECTION
  kmDebug() << objectName() << "- DESTROYED";
#endif
}



/**
 * @brief Internal function to track SOAP clients.
 *
 * This method connects the HttpSoapConnection::soapError() signal,
 * and appends the client to the internal list.
 *
 * @param  client  The SOAP client class.
 */
void MsnConnection::addSoapClient( HttpSoapConnection *client )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kmDebug() << "Adding SOAP client to list.";
#endif

  // Connect signals
  connect( client, SIGNAL( soapError(QString,MsnSocketBase::ErrorType) ),
           this,   SLOT  ( slotError(QString,MsnSocketBase::ErrorType) ) );

  // Add to list so it cleans up when the connection closes
  soapClients_.append( client );
}



/**
 * @brief Connect a socket's signals to our slots
 *
 * Connects the signals of a new socket to this class' slots.
 * This is needed to have one place only for socket connection;
 * since socket connections are made in initialize() and in
 * switchToHttpSocket().
 */
void MsnConnection::attachToSocketSignals()
{
#ifdef KMESSDEBUG_CONNECTION
  kmDebug() << objectName() << "- Connecting socket signals.";
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( socket_ != 0 );
#endif

  // Connect our integrative methods to the raw socket's events signals
  connect( socket_, SIGNAL(        connected()                                     ),
           this,    SLOT  (    slotConnected()                                     ) );
  connect( socket_, SIGNAL(     disconnected()                                     ),
           this,    SLOT  ( slotDisconnected()                                     ) );
  connect( socket_, SIGNAL(     dataReceived(const QStringList&,const QByteArray&) ),
           this,    SLOT  ( slotDataReceived(const QStringList&,const QByteArray&) ) );
  connect( socket_, SIGNAL(            error(QString,MsnSocketBase::ErrorType)     ),
           this,    SLOT  (        slotError(QString,MsnSocketBase::ErrorType)     ) );
  // Forward the socket's notification signals
  connect( socket_, SIGNAL(    statusMessage(QString,bool)                         ),
           this,    SIGNAL(    statusMessage(QString,bool)                         ) );
  connect( socket_, SIGNAL(         pingSent()                                     ),
           this,    SIGNAL(         pingSent()                                     ) );
}



/**
 * @brief Connect to the given server via the socket
 *
 * The connection attempt is asynchronous.
 * When the connection is made, connectionSuccess() is called,
 * on error connectionFailed() is called.
 * Connection timeouts are not detected here yet.
 *
 * @param  server  The server hostname or IP address.
 * @param  port    The port to connect to.
 */
void MsnConnection::connectToServer( const QString& server, const quint16 port )
{
#ifdef KMESSDEBUG_CONNECTION_SOCKET
  if( server.isEmpty() && port == 0 )
  {
    kmDebug() << objectName() << "- Connecting to the default server.";
  }
  else
  {
    kmDebug() << objectName() << "- Connecting to server at" << server << ":" << port << ".";
  }
#endif

  // Start connecting now
  socket_->connectToServer( server, port );
}



/**
 * @brief Disconnect and delete a SOAP clients.
 *
 * @param  client  The client to delete.
 * @see deleteSoapClients()
 */
void MsnConnection::deleteSoapClient( HttpSoapConnection *client )
{
  soapClients_.removeAll( client );
  QTimer::singleShot( 10, client, SLOT(deleteLater()) );  // timeout needed for deleting passport login service.
}



/**
 * @brief Disconnect and delete all SOAP clients.
 *
 * This method is called by the derived class when all SOAP clients need to be deleted.
 * This is not done automatically in disconnectFromServer() so the derived class
 * is both responsable for creating and deleting the SOAP client objects.
 */
void MsnConnection::deleteSoapClients()
{
  // Disconnect other possibly active SOAP sessions.
  // Use deleteLater() since this method may be called from loginIncorrect()
  if( ! soapClients_.isEmpty() )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kmDebug() << "Closing SOAP sessions.";
#endif

    foreach( HttpSoapConnection *soapClient, soapClients_ )
    {
      soapClient->abort();
      soapClient->deleteLater();
    }

    soapClients_.clear();
  }
}



/**
 * @brief Disconnect from the server
 *
 * This function is called after closeConnection()
 * It empties all buffers, and resets the sockets.
 *
 * @param  isTransfer  When set, a disconnected() signal won't be fired.
 *                     This is used to handle transfers to another server
 *                     without noticing a disconnect.
 */
void MsnConnection::disconnectFromServer( bool isTransfer )
{
#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kmDebug() << objectName() << "- Closing the connection.";
#endif

  // Gracefully disconnect
  if( isConnected() )
  {
    sendCommand( "OUT" );
  }

  // Stop pinging
  setSendPings( false );

  // Clean up collections
  qDeleteAll( multiPacketBuffer_ );
  multiPacketBuffer_.clear();

  socket_->disconnectFromServer( isTransfer );
}



/**
 * @brief Return the local IP address of the socket.
 *
 * This is typically a LAN address, unless the system
 * is directly connected to the Internet.
 *
 * @returns The IP address the socket uses at the system.
 */
const QString MsnConnection::getLocalIp() const
{
#ifdef KMESSDEBUG_SWITCHBOARD_FILETRANSFER
  kmDebug() << objectName() << "- Local IP is " << socket_->getLocalIp();
#endif

  return socket_->getLocalIp();
}



/**
 * @brief Initialize the object
 *
 * This method should be called before the class is used.
 *
 * It initializes the socket
 * Subclasses may re-implement this method to connect
 * to signals which are not available at startup in the constructor.
 */
bool MsnConnection::initialize()
{
  if( initialized_ )
  {
    kmDebug() << objectName() << "- Already initialized!";
    return false;
  }

  // Attach the socket's signals to this class
  attachToSocketSignals();

  initialized_ = true;
  return true;
}



/**
 * @brief Return whether or the socket is connected.
 * @returns True when connected, false otherwise.
 */
bool MsnConnection::isConnected() const
{
  return socket_->isConnected();
}



/**
 * @brief Return whether the given command is an error command.
 *
 * This method just passes the question to the socket class
 *
 * @returns True when it's an error command, false otherwise.
 */
bool MsnConnection::isErrorCommand( const QString &command ) const
{
  return socket_->isErrorCommand( command );
}



/**
 * @brief Return whether the given command is a payload command.
 *
 * This method just passes the question to the socket class
 *
 * @returns True when it's a payload command, false otherwise.
 */
bool MsnConnection::isPayloadCommand( const QString &command ) const
{
  return socket_->isPayloadCommand( command );
}



// Process a received error command
void MsnConnection::parseError( const QStringList &command, const QByteArray &payloadData )
{
  if( command[0].toInt() == 0 )
  {
    kmWarning() << "Invalid error command received:" << command;
    return;
  }

  // Always print error codes, to ease a bit the error resolution
  kmWarning() << "Received error code" << command[0] << "from server.";
  if( ! payloadData.isEmpty() )
  {
    kmWarning() << "The error also carries a payload:";
    kmWarning() << payloadData;
  }

  QString                  errorMessage;
  bool                     isNotice      = false; // Not really errors, will be displayed on the status bar
  bool                     isWarning     = false; // Will be displayed on a dialog box, but will not disconnect
  MsnSocketBase::ErrorType errorType     = MsnSocketBase::ERROR_USER;  // By default, blame the user :)

  switch( command[0].toInt() )
  {
    // Client side errors.
    // These errors are usually generated by misbehavior on kmess' or the user's side.

    case 200:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Invalid command syntax");
      break;
    case 201:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Invalid command parameter");
      break;
    case 205:
      errorMessage = i18n("The email address you have tried to add is not a Live Messenger account");
      isWarning = true;
      break;
    case 206:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Domain name missing");
      break;
    case 207:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Already logged in");
      break;
    case 208:
      errorMessage = i18n("The given account name is invalid");
      isWarning = true;
      break;
    case 209:
      errorMessage = i18n("This account name is invalid, or your Passport account has not been confirmed yet");
      break;
    case 210:
      errorMessage = i18n("Your contact list is full");
      isWarning = true;
      break;
    case 213:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Invalid SBP parameter");
      break;
    case 215:
      errorMessage = i18n("This contact is already on your list");
      isWarning = true;
      break;
    case 216:
      errorMessage = i18n("This contact is not on your list");
      isWarning = true;
      break;
    case 217:
      errorMessage = i18n("This contact is not online");
      isWarning = true;
      break;
    case 218:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Already in this mode");
      break;
    case 219:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18nc("MSN error", "This contact cannot be added to both allow and block list");
      break;
    case 222:
    case 228:
      errorMessage = i18n("The group name is already present in your contact list. Please use a different name");
      isWarning = true;
      break;
    case 223:
      errorMessage = i18n("Your contact list has too many groups; you are allowed to only have at most 30");
      isWarning = true;
      break;
    case 224:
      errorMessage = i18n("This group cannot be changed");
      isWarning = true;
      break;
    case 225:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Contact is not added to this group");
      isWarning = true;
      break;
    case 227:
      errorMessage = i18n("This group is not empty");
      isWarning = true;
      break;
    case 229:
      errorMessage = i18n("The group name is too long; it cannot be longer than 61 characters");
      isWarning = true;
      break;
    case 230:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("There was an internal error in KMess: %1", i18n("Attempted to change group \"0\"") );
      break;
    case 231:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Invalid Group");
      break;
    case 240:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Empty domain");
      break;
    case 241:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Wrong ADL command format");
      break;
    case 280:
      errorMessage = i18n("Switchboard server failed");
      break;
    case 281:
      errorMessage = i18n("Transfer to switchboard server failed");
      break;
    case 282:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Direct connection (MSNSLP) error, connection failed");
      break;
    case 300:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Required field missing");
      break;
    case 302:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Not logged in");
      break;
    case 420:
      errorMessage = i18nc( "MSN error, due to e.g. trying the beta service without an allowed account",
                            "This account was denied access to the Live Messenger service" );
      break;
    // For other 5xx errors, see below
    case 502:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Command is disabled");
      break;
    case 511:
      errorMessage = i18n("Your account is banned");
      break;
    case 540:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Challenge response failed");
      break;
    // For other 6xx errors, see below
    case 710:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Bad command data");  // bad CVR or URL command
      break;
    case 713:
      // Too many CAL's
      errorMessage = i18n("You are opening chat sessions too fast");
      isWarning = true;
      break;
    case 714:
      errorMessage = i18n("You have too many open chat sessions");
      isWarning = true;
      break;
    case 715:
      // Sent in response to a PRP setting an invalid phone type of three or less characters.
      // Also sent in response to a change of display name (PRP) on an unverified Passport account.
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Unexpected command sequence");
      break;
    case 717:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Bad friend name");
      isWarning = true;
      break;
    case 731:
      // Sent in response to a badly formatted CVR
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Bad CVR data");
      break;
    case 800:
      // Happens when renaming too quickly,
      // but also when sending too many MSNP2P packets over the switchboard without waiting for ACKs.
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("The server reports KMess is flooding it with too much data");
      isNotice = true;
      break;
    // For other 9xx errors, see below
    case 911:
      // User/pass was probably correct, TWN ticket was incorrect
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Authentication ticket was incorrect");
      break;
    case 913:
      errorMessage = i18n("You cannot start a chat with someone while you are invisible");
      isWarning = true;
      break;
    case 920:
      errorMessage = i18n("Not accepting new contacts");
      isWarning = true;
      break;
    case 923:
      errorMessage = i18n("You have a Kids Passport account, you need parental consent to chat online");
      break;
    case 924:
      errorMessage = i18n("Your Passport account needs to be verified first");
      break;
    case 928:
      errorType = MsnSocketBase::ERROR_INTERNAL;
      errorMessage = i18n("Bad USR ticket");
      break;


    // Server side errors.
    // These are generated without any intervention from us.

    case 402:
    case 403:
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18n("Error accessing contact list, try again later");
      break;
    case 500:   // Generic server error
    case 501:   // Database server error
    case 510:   // File operation failed
    case 520:   // Memory allocation failed
    case 603:   // Database connection failed, in response to SYN
    case 605:   // server unavailable
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18n("The Live Messenger service is temporarily unavailable. There was an internal server error");
      break;
    case 600:
    case 601:  // server is unavailable, in response to USR
    case 910:
    case 912:
    case 918:
    case 919:
    case 921:
    case 922:
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18n("The Live Messenger service is temporarily unavailable. The server is too busy");
      break;
    case 602:
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18n("Peer notification server down");
      break;
    case 604:
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18n("The server is going down");
      break;
    case 640:
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18n("The server is going down soon");
      isWarning = true;
      break;
    case 711:
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18nc("Error received from the MSN servers", "Unknown error");
      break;
    case 712:
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18n("Session is overloaded");
      isWarning = true;
      break;
    case 914:
    case 915:
    case 916:
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18n("The server is not available");
      break;
    case 917:
      errorType = MsnSocketBase::ERROR_SERVER;
      errorMessage = i18n("Authentication failed");
      break;

    default:
      // No need to bugger the users with strange error messages
      kmWarning() << "Unknown error" << command[0] << "received from the server! Full string:" << command.join( " " );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      slotError( i18n( "Unknown error code received from the server: %1<br/>Technical details: %2",
                       command[0],
                       payloadData ),
                 MsnSocketBase::ERROR_INTERNAL );
#endif
      return;
  }


  // fontknocker: i'm opting to remove this. we only have one notice error, err 800, and there's nothing we can
  //              do about it anyway. there's no point showing the user something that will make them nervous.
  //              i'll display a debug message instead.
  if( isNotice )
  {
//    emit statusMessage( errorMessage, false );
    kmDebug() << errorMessage;
    return;
  }
  // We've received a warning message: show it in a dialog box, but do not disconnect
  else if( isWarning )
  {
    KMessageBox::error( 0, errorMessage, i18nc( "Error dialog box title with error number",
                                                "MSN Error %1", command[0].toInt() ) );
    return;
  }

  // On debug builds, also display the payload if there is one along with the error.
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
   errorMessage += "<br/><i>" + Qt::escape( payloadData ) + "</i>";
#endif


  // Call the unified error message display method
  slotError( errorMessage, errorType );
}



/**
 * Internal processing of multi-packet messages.
 * @param  command      The received command.
 * @param  mimeMessage  The received mime message.
 */
void MsnConnection::parseMultiPacketMimeMessage( const QStringList &command, const MimeMessage &mimeMessage )
{
#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kmDebug() << objectName() << "- Message is a multi-packet message, buffering.";
#endif

  // Get associated multi-packet message buffer, create one if it doesn't exist yet.
  const QString messageId( mimeMessage.getValue("Message-ID") );
  MultiPacketMessage *multiPacketMessage;
  if( ! multiPacketBuffer_.contains( messageId ) )
  {
    multiPacketMessage = new MultiPacketMessage;
    multiPacketBuffer_.insert( messageId, multiPacketMessage );
  }
  else
  {
    multiPacketMessage = multiPacketBuffer_[ messageId ];
  }

  // Add the message to the handler.
  multiPacketMessage->addChunk( mimeMessage );
  if( ! multiPacketMessage->isComplete() )
  {
    return;
  }

  // Parse message if complete multi-packet message is received.
  const MimeMessage &fullMimeMessage = multiPacketMessage->getMessage();
  parseMimeMessage( command, fullMimeMessage );

  // Delete handler class
  delete multiPacketBuffer_.take( messageId );
}



/**
 * @brief Send a command to the server
 *
 * This function automatically inserts the ACK-number
 * between the prefix and text.
 * This can be used later to track error responses.
 *
 * @param  prefix  The three-letter command, which parseCommand() receives in <code>command[0]</code>.
 * @param  text    The command arguments, joined with spaces.
 * @returns The ack-number used by the command.
 */
int MsnConnection::sendCommand( const QString& prefix, const QString &text )
{
  int ack = ack_++;

  // Send the data to the server
  const QString command( prefix + " " + QString::number( ack ) + " " + text +  "\r\n" );
  writeData( command );

  // Return the ack used
  return ack;
}



/**
 * @brief Send a MIME message command to the server.
 * @returns, returning the ack
 */
int MsnConnection::sendMimeMessage( AckType ackType, const MimeMessage &message )
{
  // Determine which type to send.
  // Normally a char[4] lookup array would be sufficient,
  // but this is easier to use in the string concatenation with Qt
  const char *ackTypeStr = "?";
  switch(ackType)
  {
    case ACK_NONE:        ackTypeStr = "U"; break;
    case ACK_NAK_ONLY:    ackTypeStr = "N"; break;
    case ACK_ALWAYS:      ackTypeStr = "A"; break;
    case ACK_ALWAYS_P2P:  ackTypeStr = "D"; break;
  }

  // Create the "MSG id type length" header
  const QByteArray rawMessage( message.getMessage() );

  // Build the command
  int ack = ack_++;

  QString command;
  command.sprintf("MSG %u %s %u\r\n", ack, ackTypeStr, rawMessage.size());

  // Write the data
  writeBinaryData( command.toUtf8() + rawMessage );

  // Return the ack used
  return ack;
}



// Send a payload command to the server, convenience function
int MsnConnection::sendPayloadMessage( const QString &prefix, const QString &arguments, const QString &payload )
{
  // Send payload, as UTF-8 encoded raw string.
  const QByteArray binPayload( payload.toUtf8() );
  return sendPayloadMessage( prefix, arguments, binPayload );
}



// Send a payload command to the server
int MsnConnection::sendPayloadMessage( const QString &prefix, const QString &arguments, const QByteArray &payload )
{
  int ack = ack_++;

  // Build the command
  QString command( prefix + " " + QString::number( ack ) );
  if( ! arguments.isEmpty() )
  {
    command += " " + arguments;
  }

  command += " " + QString::number( payload.size() ) + "\r\n";

  // Write the data
  if( payload.size() > 0 )
  {
    writeBinaryData( command.toUtf8() + payload );
  }
  else
  {
    writeData( command );
  }

  // Return the ack used
  return ack;
}



// Specify which accepted commands carry payload data for this connection
void MsnConnection::setAcceptedPayloadCommands( QStringList commandList )
{
  socket_->setAcceptedPayloadCommands( commandList );
}



// Set whether we're sending pings or not
void MsnConnection::setSendPings( bool sendPings )
{
  socket_->setSendPings( sendPings );
}



/**
 * @brief Called when data is received from the server.
 *
 * This method parses the incoming data for the syntax used by the MSN protocol.
 *
 * A parsed message to handled to one of the following methods:
 * - one-line commands are delivered to parseCommand()
 * - <code>MSG</code> commands with MIME payloads are delivered to parseMimeMessage()
 * - other commands with payloads are delivered to parsePayloadMessage().
 *   This uses isPayloadCommand() to detect if a command has a payload.
 */
void MsnConnection::slotDataReceived( const QStringList &commandLine, const QByteArray &payloadData )
{
#ifdef KMESSDEBUG_SERVERMESSAGES
  // Display the received command data
  kmDebug() << objectName() << "- <<< (C) " << commandLine;
  if( ! payloadData.isEmpty() )
  {
    kmDebug() << objectName() << "- <<< (P) " << payloadData;
  }
#endif

#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_RECEIVED( this, commandLine.join(" ").toUtf8() );
  // Also display the payload if present
  if( ! payloadData.isEmpty() )
  {
    KMESS_NET_RECEIVED( this, payloadData );
  }
#endif

  // See if it's a normal or payload command, or an error
  if( isErrorCommand( commandLine[0] ) )
  {
    parseError( commandLine, payloadData );
  }
  else if( isPayloadCommand( commandLine[0] ) )
  {
    // See if it's a Mime message.
    if( commandLine[0] != "MSG" )
    {
      // Other payload message types are parsed in the subclasses.
      parsePayloadMessage( commandLine, payloadData );
    }
    else
    {
      // It's a mime message
      // Note we can't parse strings here, the message could contain binary p2p data.
      // UTF-8 characters are translated in the MimeMessage constructor itself.
      MimeMessage mimeMessage( payloadData );

      // Detect Multi-packet messages
      if( ! mimeMessage.hasField("Message-ID") )
      {
        // It's a normal message, parse it.
        parseMimeMessage( commandLine, mimeMessage );
      }
      else
      {
        // Handle multi-packet message parts.
        parseMultiPacketMimeMessage( commandLine, mimeMessage );
      }
    }
  }
  else
  {
    // It's a normal command, handle it in the subclass.
    parseCommand( commandLine );
  }
}



/**
 * @brief Called when the socket is disconnected.
 */
void MsnConnection::slotDisconnected()
{
#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_CLOSE(this);
#endif

  emit disconnected();
}



/**
 * @brief If it is possible, switch to the HTTP connection
 *
 * This method checks what connection is being established, and if is a TCP
 * connection, will delete it and re-start connecting using the HTTP method.
 *
 * This is used when the TCP connection process fails.
 */
bool MsnConnection::switchToHttpSocket()
{
  // Check if we're already using HTTP
  if( qobject_cast<MsnSocketHttp*>( socket_ ) )
  {
#ifdef KMESSDEBUG_CONNECTION_SOCKET
    kmDebug() << "Cannot switch to HTTP connection. Class is" << socket_->metaObject()->className();
#endif
    return false;
  }

#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kmDebug() << "Attempting switch to HTTP connection.";
#endif

  // Save the socket details to use the same ones for the new socket
  MsnSocketBase::ServerType serverType = socket_->getServerType();
  QStringList acceptedPayloadCommands = socket_->getAcceptedPayloadCommands();

  // Close and disable the old server
  socket_->blockSignals( true );
  socket_->disconnectFromServer();
  socket_->deleteLater();

  // Replace it with the new one
  socket_ = new MsnSocketHttp( serverType );
  socket_->setAcceptedPayloadCommands( acceptedPayloadCommands );

  // Set the internal socket switch
  useHttpSocket_ = true;

  // Attach its signals to this class
  attachToSocketSignals();

  // Start connecting
  connectToServer();
  emit statusMessage( i18n("Trying the HTTP fallback..."), false );

  return true;
}



/**
 * @brief Switch back to the TCP connection
 *
 * This method checks what connection is being established, and if is an HTTP
 * connection, will delete it and start using again the TCP method.
 *
 * This is used after disconnecting from an HTTP session, to try again with TCP the next time.
 */
bool MsnConnection::switchToTcpSocket()
{
  // Check if we're already using TCP
  if( qobject_cast<MsnSocketTcp*>( socket_ ) )
  {
#ifdef KMESSDEBUG_CONNECTION_SOCKET
    kmDebug() << "Cannot switch to TCP connection. Class is" << socket_->metaObject()->className();
#endif
    return false;
  }

#ifdef KMESSDEBUG_CONNECTION_SOCKET
  kmDebug() << "Attempting to switch back to TCP connection.";
#endif

  // Save the socket details to use the same ones for the new socket
  MsnSocketBase::ServerType serverType = socket_->getServerType();
  QStringList acceptedPayloadCommands = socket_->getAcceptedPayloadCommands();

  // Close and disable the old server
  socket_->blockSignals( true );
  disconnect( socket_, 0 );
  socket_->disconnectFromServer();
  socket_->deleteLater();

  // Replace it with the new one
  socket_ = new MsnSocketTcp( serverType );
  socket_->setAcceptedPayloadCommands( acceptedPayloadCommands );

  // Set the internal socket switch
  useHttpSocket_ = false;

  // Attach its signals to this class
  attachToSocketSignals();

  return true;
}



// Write data to the socket without conversions
void MsnConnection::writeBinaryData( const QByteArray& data )
{
  if( ! isConnected() )
  {
    kmWarning() << objectName() << "- Attempting to write binary data while disconnected, aborting.";
    return;
  }

#ifdef KMESSDEBUG_SERVERMESSAGES
// To display the entire contents:
/*
  QByteArray debugData( data );
  for( int i = 0; i < debugData.size(); i++ ) { if( debugData[i] < 10 ) debugData[ i ] = '?'; }
  kmDebug() << objectName() << "- >>> " << QString::fromUtf8( debugData.data(), debugData.size() );
*/
// To display the first readable part: (removing the final \r\n)
  kmDebug() << objectName() << "- >>> " << QString( data ).trimmed();
#endif

  // Write to the socket
  socket_->writeBinaryData( data );

  // Append the message to the network window
#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_SENT( this, data );
#endif
}



// Write data to the socket
void MsnConnection::writeData( const QString& data )
{
  if( ! isConnected() )
  {
    kmWarning() << objectName() << "- Attempting to write data while disconnected, aborting.";
    return;
  }

#ifdef KMESSDEBUG_SERVERMESSAGES
  kmDebug() << objectName() << "- >>> " << data.trimmed();
#endif

  const QByteArray binData( data.toUtf8() );
  qint64 noBytesWritten = socket_->writeData( binData );

  if( noBytesWritten != binData.length() ) // Note that the previous code used data.length instead of unicode.length(), but I
  {                                         // changed this since that is the string we send to the socket... (could be wrong here)
    kmWarning() << objectName() << "- Wanted to write " << data.length() << " bytes to the socket, wrote " << noBytesWritten << ".";
  }

  // Append message to the network window
#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_SENT( this, data.toUtf8() );
#endif
}


#include "msnconnection.moc"
