/***************************************************************************
                          picturetransferp2p.cpp -  description
                             -------------------
    begin                : Fri Nov 26 2004
    copyright            : (C) 2004 by Diederik van der Boor
    email                : vdboor --at-- codingdomain.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 "picturetransferp2p.h"

#include "../mimemessage.h"
#include "../p2pmessage.h"
#include "../../msnobject.h"
#include "../../kmessdebug.h"
#include "../../currentaccount.h"

#include <qfile.h>
#include <qregexp.h>

#include <kdebug.h>
#include <kstandarddirs.h>
#include <kmdcodec.h>


/**
 * Constructor
 *
 * @param  localIP        The IP-Address of the SwitchBoard socket.
 * @param  contactHandle  Person to send P2P messages to.
 */
PictureTransferP2P::PictureTransferP2P(const QString &localIP, const QString &contactHandle)
: P2PApplication(localIP, contactHandle),
  file_(0)
{
}



/**
 * Constructor
 *
 * @param  localIP        The IP-Address of the SwitchBoard socket.
 * @param  contactHandle  Person to send P2P messages to.
 * @param  msnObject      MSNObject identifying the picture to request.
 */
PictureTransferP2P::PictureTransferP2P(const QString &localIP, const QString &contactHandle, const QString &msnObject)
: P2PApplication(localIP, contactHandle),
  file_(0),
  msnObject_(msnObject)
{
}



/**
 * Destructor, closes the file if it's open.
 */
PictureTransferP2P::~PictureTransferP2P()
{
  if(file_ != 0)
  {
    delete file_;
  }
}



/**
 * Step one of a contact-started chat: the contact invites the user
 *
 * @param  message  The invitation message
 */
void PictureTransferP2P::contactStarted1_ContactInvitesUser(const MimeMessage &message)
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - contactStarted1_ContactInvitesUser" << endl;
#endif

  // Validate the content type
  if(getInvitationContentType() != "application/x-msnmsgr-sessionreqbody")
  {
    kdWarning() << "PictureTransferP2P: Received unexpected Content-Type: "
                << getInvitationContentType() << "." << endl;

    // Indicate we don't like that content-type:
    sendCancelMessage(CANCEL_INVALID_SLP_CONTENT_TYPE);
    // Don't QUIT, the error will be ACK-ed.
    return;
  }


  // Extract the fields from the message
  unsigned long int appID   = message.getValue("AppID").toUInt();
  QString           context = message.getValue("Context");

  if(! (appID == 1 || appID == 12))  // AppID 12 is used as of MSN Messenger 7.5
  {
    kdDebug() << "PictureTransferP2P: WARNING - Received unexpected AppID: " << appID << "." << endl;

    // Wouldn't know what to do if the AppID is not 1,
    // so send an 500 Internal Error back:
    sendCancelMessage(CANCEL_SESSION);

    // Don't QUIT, the error will be ACK-ed.
    return;
  }

  // TODO: Extract the MSNObject from the context field:

  // Currently we always send our display picture
  fileName_ = CurrentAccount::instance()->getImagePath();

  // Everything seams OK, accept this message:
  contactStarted2_UserAccepts();
}



/**
 * Step two of a contact-started chat: the user accepts.
 */
void PictureTransferP2P::contactStarted2_UserAccepts()
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - contactStarted2_UserAccepts" << endl;
#endif

#ifdef KMESSTEST
  ASSERT(   file_ == 0          );
  ASSERT( ! fileName_.isEmpty() );
#endif

  // Now we try to open the file
  file_ = new QFile(fileName_);
  bool success = file_->open(IO_ReadOnly);

  if( ! success )
  {
    // Notify the user, even if debug mode is not enabled.
    kdDebug() << "WARNING - Unable to open file: " << fileName_ << "!" << endl;

    // Close the file (also causes gotData() to fail)
    delete file_;
    file_ = 0;

#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
    kdDebug() << "PictureTransferP2P::contactStarted2_UserAccepts: Cancelling session" << endl;
#endif

    // Send 500 Internal Error back if we failed
    sendCancelMessage(CANCEL_SESSION);

    // Don't QUIT, the error will be ACK-ed.
  }
  else
  {
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
    kdDebug() << "PictureTransferP2P::contactStarted2_UserAccepts: Sending accept message" << endl;
#endif

    // Create the message
    MimeMessage message;
    message.addField( "SessionID", QString::number( getInvitationSessionID() ) );

    // Send the message
    sendSlpOkMessage(message);
  }
}



/**
 * Step three of a contact-started chat: the contact confirms the accept
 *
 * @param  message  The message of the other contact, not usefull in P2P sessions because it's an ACK.
 */
void PictureTransferP2P::contactStarted3_ContactConfirmsAccept(const MimeMessage &/*message*/)
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - contactStarted3_ContactConfirmsAccept" << endl;
#endif

  // Send the data preparation message back.
  // Once this message is ACKed, we can send our code
  sendDataPreparation();
}



/**
 * Step four in a contact-started chat: the contact confirms the data preparation message.
 */
void PictureTransferP2P::contactStarted4_ContactConfirmsPreparation()
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - contactStarted4_ContactConfirmsPreparation" << endl;
#endif

  // Send the file, the base class handles everything else here
  sendData(file_, P2P_TYPE_PICTURE);
}



/**
 * Return the application's GUID.
 */
QString PictureTransferP2P::getAppId()
{
  return "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}";
}



/**
 * Return the msn object of the picture we're transferring
 */
QString PictureTransferP2P::getMsnObject() const
{
  return msnObject_;
}



/**
 * Determinate the path for an contact picture.
 *
 * @param msnObject  The MSNObject the other contact uses to identify his resource.
 *
 * @returns  A file name string.
 */
QString PictureTransferP2P::getPictureFileName(const MsnObject &msnObject)
{
  // Replace bad characters, in case someone intends to send a bad SHA1.
  // The sha1 string is actually base64 encoded, meaning we could
  // also expect a "/" character in the string.
  QString sha1d = msnObject.getDataHash();
  const QString safeSha1 = sha1d.replace(QRegExp("[^a-zA-Z0-9+=]"), "_");
  return locateLocal( "data", QString("kmess/pics/") + QString::fromUtf8( safeSha1 ) + ".png");
}



/**
 * Called when data is received.
 * Once all data is received, the SLP BYE message will be sent.
 *
 * @param  message  P2P message with the data.
 */
void PictureTransferP2P::gotData(const P2PMessage &message)
{
  if(file_ == 0)
  {
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
    kdDebug() << "PictureTransferP2P: Unable to handle file data: no file open!" << endl;
#endif

    // Cancel if we can't receive it.
    // If this happens we're dealing with a very stubborn client,
    // because we already rejected the data-preparation message.
    sendCancelMessage(CANCEL_FAILED);
  }
  else
  {
    // Write the data in the file
    Q_LONG status = file_->writeBlock( message.getData(), message.getDataSize() );

    // Check whether the write failed
    if(status == -1)
    {
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
      kdDebug() << "PictureTransferP2P: Failed to write the datablock in the file" << endl;
#endif
      // Close the file
      file_->flush();
      file_->close();
      delete file_;
      file_ = 0;

      sendCancelMessage(CANCEL_FAILED);
      return;
    }

    // is the file complete:
    if( message.isLastFragment() )
    {
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
      kdDebug() << "PictureTransferP2P: Last data part received, closing file" << endl;
#endif

      // Don't send an ACK here, it's already ACK-ed
      // (with a special BYE-request ACK)

      // Clean up
      file_->flush();
      file_->close();
      delete file_;
      file_ = 0;

      // Send an event to the switchboard:
      emit pictureReceived(getContactHandle(), msnObject_);

      // The application should close automatically now,
      // and it sends the BYE message automatically too.
    }
  }
}



/**
 * Step one of a user-started chat: the user invites the contact
 */
void PictureTransferP2P::userStarted1_UserInvitesContact()
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - userStarted1_UserInvitesContact - requesting display picture" << endl;
#endif

  // Set the filename
  MsnObject pictureInfo(msnObject_);
  fileName_ = getPictureFileName(pictureInfo);

  // Encode the context
  QString context;
  context = QString::fromUtf8(KCodecs::base64Encode(msnObject_.utf8()));
  context.replace("=" , QString::null );

  // Create a session ID
  uint sessionID = P2PApplication::generateID();

  // Create the message
  MimeMessage message;
  message.addField( "EUF-GUID",  getAppId()                 );
  message.addField( "SessionID", QString::number(sessionID) );
  message.addField( "AppID",     "1"                        );
  message.addField( "Context",   context                    );

  // Send the message
  sendSlpInvitation(sessionID, "application/x-msnmsgr-sessionreqbody", message);
}



/**
 * Step two of a user-started chat: the contact accepts
 *
 * @param  message  Accept message of the other contact
 */
void PictureTransferP2P::userStarted2_ContactAccepts(const MimeMessage &message)
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - userStarted2_ContactAccepts" << endl;
#endif

#ifdef KMESSTEST
  MimeMessage slpContent(message.getBody());
  ASSERT( message.getValue("Content-Type") == "application/x-msnmsgr-sessionreqbody" );
  ASSERT( slpContent.getValue("SessionID").toULong() == getSessionID() );
#endif

  // We don't need to do anything else here, the P2PApplication base class will
  // ACK this message automatically with the session ID we gave in the sendSlpInvitation()
}



/**
 * Step three of a user-started chat: the user prepares for the session.
 */
void PictureTransferP2P::userStarted3_UserPrepares()
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - userStarted3_UserPrepares" << endl;
#endif

#ifdef KMESSTEST
  ASSERT(   file_ == 0          );
  ASSERT( ! fileName_.isEmpty() );
#endif

  file_ = new QFile(fileName_);
  bool success = file_->open(IO_WriteOnly);

  if( ! success )
  {
    // Notify the user, even if debug mode is not enabled.
    kdDebug() << "WARNING - Unable to open file: " << fileName_ << "!" << endl;

    // Close the file (also causes gotData() to fail)
    delete file_;
    file_ = 0;
  }
  else
  {
    // Acknowledge the data-preparation message
    sendDataPreparationAck();
  }

  // Final step is the gotData() handling..
}


#include "picturetransferp2p.moc"
