/***************************************************************************
                          multipacketmessage.h -  description
                             -------------------
    begin                : Thu May 23 2005
    copyright            : (C) 2006 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 "multipacketmessage.h"

#include "../utils/kmessshared.h"
#include "../kmessdebug.h"
#include <math.h>


// Many thanks to Siebe Tolsma for the following documentation:
// http://siebe.bot2k3.net/docs/word/Sending Multi-Packet Messages.doc


// The constructor
MultiPacketMessage::MultiPacketMessage()
: chunks_(0)
, lastChunk_(0)
{
  buffer_.open( QIODevice::ReadWrite );

  // TODO: add a live-time to the message, so it will be removed
  // automatically (avoiding yet another denial-of-service attack).
}



// The constructor for sending huge messages
MultiPacketMessage::MultiPacketMessage( const MimeMessage& message )
  : lastChunk_(0)
{
  // Max single MimeMessage length
  int maxSendableMessageLength = 1400;

  // Grep body of MimeMessage
  messageText_ = message.getBody();

  // Set the MimeMessage and the number of chunks ( round up )
  result_ = message;
  float a = (float) messageText_.toUtf8().length() / (float) maxSendableMessageLength;
  chunks_ = (int) ceil( a );

  // Set message guid
  messageId_ = KMessShared::generateGUID();
}



// The destructor
MultiPacketMessage::~MultiPacketMessage()
{
  buffer_.close();
}



// Add a message to the part
void MultiPacketMessage::addChunk( const MimeMessage &message )
{
  if( chunks_ > 20 )
  {
    // Method was called again for the next chunk.
    return;
  }

  if( isComplete() )
  {
    kmWarning() << "already received last chunk!";
    return;
  }

#ifdef KMESSTEST
  KMESS_ASSERT( message.hasField("Message-ID") );
#endif

  if( message.hasField("Chunks") )
  {
    // Test whether the object is already initialized.
    if( ! messageId_.isNull() )
    {
      kmWarning() << "reveived message contains 'Chunks' header, but is not the first message!";
      return;
    }

    // This is the first message.
    messageId_ = message.getValue("Message-ID");
    chunks_    = message.getValue("Chunks").toInt();
    lastChunk_ = 0;  // incremented below (this message is the first chunk)

    // Avoid denial-of-service attacks.
    if( chunks_ > 20 )
    {
      kmWarning() << "about to receive a message of more then 20 chunks, ignoring message!";
      return;
    }

    // Add message fields to result, except Message-ID and 'Chunks'
    int fields = message.getNoFields();
    QString fieldName;
    QString fieldValue;
    for( int i = 0; i < fields; i++ )
    {
      message.getFieldAndValue(fieldName, fieldValue, i);
      if( fieldName != "Message-ID" && fieldName != "Chunks" && fieldName != "Chunk" )
      {
        result_.addField(fieldName, fieldValue);
      }
    }
  }
  else if( message.hasField("Chunk") )
  {
    // This is a second message
    // Test whether this is the correct chunk
    if( message.getValue("Chunk").toInt() != lastChunk_ )
    {
      kmWarning() << "received message chunk " << message.getValue("Chunk").toInt() <<
                    ", expecting chunk " << lastChunk_ << "!" << endl;
      return;
    }
  }
  else
  {
    kmWarning() << "received message without 'Chunks' or 'Chunk' field, ignoring message!";
    return;
  }


  // MimeMessage preserves chunk data as binary, so multibyte
  // UTF-8 characters will be correctly merged later.
  const QByteArray &chunkData = message.getBinaryBody();
#ifdef KMESSTEST
  KMESS_ASSERT( message.getBinaryBody().size() > 0 );
#endif
  buffer_.write( chunkData );


  // Update chunk count
  lastChunk_++;


  // Test whether this was the last message.
  if( lastChunk_ == chunks_ )
  {
#ifdef KMESSDEBUG_MULTIPACKETMESSAGE
    kmDebug() << "Stored final part, restoring message.";
#endif

    // Read all data
    buffer_.reset();
    const QByteArray &bufferData = buffer_.data();

    // Determine how to merge the data.
    if( result_.getValue("Content-Type") == "application/x-msnmsgrp2p" )
    {
      // Merge as binary
      result_.setBinaryBody(bufferData);
    }
    else
    {
      // Merge as UTF-8 text
      result_.setBody( QString::fromUtf8(bufferData.data(), bufferData.size()) );
    }
  }
  else
  {
#ifdef KMESSDEBUG_MULTIPACKETMESSAGE
    kmDebug() << "Stored part " << lastChunk_ << " of " << chunks_;
#endif
  }
}



// Returh the complete message
const MimeMessage &MultiPacketMessage::getMessage() const
{
  return result_;
}



// Return whether the full message is received/sended
bool MultiPacketMessage::isComplete() const
{
  return (chunks_ > 0 && lastChunk_ == chunks_);
}



// Get the next part of one multipacket message
const MimeMessage& MultiPacketMessage::getNextPart()
{
   // Max single MimeMessage length
  int maxSendableMessageLength = 1400;
  
  // If this is not the first chunk (FIXME variable name?), clear all fields
  if( lastChunk_ != 0 )
  {
    result_.clearFields();
  }

  // Add the GUID (Windows Live Messenger 8.5 requires this to be sent before Chunks/Chunk)
  result_.addField( "Message-ID", messageId_ );

  if( lastChunk_ == 0 )
  {
    // If it's the first chunk add the total number of chunks
    result_.addField( "Chunks", QString::number( chunks_, 10 ) );
  }
  else
  {
    // Else set the chunk order number
    result_.addField( "Chunk", QString::number( lastChunk_, 10 ) );
  }

  // Add the body
  if( lastChunk_ == chunks_ - 1 )
  {
    result_.setBody( messageText_.mid( lastChunk_ * maxSendableMessageLength,
                     messageText_.length() - lastChunk_ * maxSendableMessageLength ) );
  }
  else
  {
    result_.setBody( messageText_.mid( lastChunk_ * maxSendableMessageLength, maxSendableMessageLength ) );
  }

  // Increment lastChunk
  lastChunk_++;

#ifdef KMESSDEBUG_MULTIPACKETMESSAGE
  kmDebug() << "Sending part: " << lastChunk_;
#endif

  return result_;
}
