/***************************************************************************
                          msndirectconnection.cpp -  description
                             -------------------
    begin                : Tue 12 27 2005
    copyright            : (C) 2005 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 "msndirectconnection.h"

#include "../../kmessdebug.h"
#include "../p2pmessage.h"


#ifdef KMESSDEBUG_DIRECTCONNECTION
  #define KMESSDEBUG_DIRECTCONNECTION_GENERAL
  // note that enabling the KMESSDEBUG_DIRECTCONNECTION_RECEIVING makes file transfer slow and inresponsive.
  #define KMESSDEBUG_DIRECTCONNECTION_RECEIVING
#endif



MsnDirectConnection::MsnDirectConnection(const QString &contactHandle)
 : DirectConnectionBase(0)
 , bufferOffset_(0)
 , contactHandle_(contactHandle)
 , firstMessage_(true)
 , preambleOffset_(0)
 , remainingBlockBytes_(0)
{
  setObjectName( "MsnDirectConnection[" + contactHandle + "]" );

  preambleBuffer_.resize(4);
}


MsnDirectConnection::~MsnDirectConnection()
{
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "DESTROYED. [handle=" << contactHandle_ << "]";
#endif
}



// Return the contact handle
const QString& MsnDirectConnection::getContactHandle() const
{
  return contactHandle_;
}



// Send the handshake packets
bool MsnDirectConnection::initialize()
{
  // When this is the server, nothing needs to be sent.
  if(isServer())
  {
    return isConnected();
  }

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "sending preamble message (0x666f6f00).";
#endif
#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_INIT(this, "DC " + getRemoteIp());
#endif
  firstMessage_ = false;

  // Mark the time we received the message, to detect timeouts
  lastActivity_.start();

  // Send the preamble packet
  // This consists of 8 bytes:
  // the length field followed by "foo\0"
  QByteArray preambleMessage( 8, 0x00 );
  P2PMessage::insertBytes( preambleMessage, 4, 0 );
  preambleMessage[4] = 0x66;  // f
  preambleMessage[5] = 0x6f;  // 0
  preambleMessage[6] = 0x6f;  // 0
  preambleMessage[7] = 0x00;  // \0
  return writeBlock( preambleMessage.data(), preambleMessage.size() );
}



// Send the message to the contact
bool MsnDirectConnection::sendMessage(const QByteArray &message)
{
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
  kDebug() << "sending message block (size=" << message.size() << ", handle=" << contactHandle_ << ")";
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( message.size() <= 1352 );  // max message size for official client
  KMESS_ASSERT( ! hasTemporaryWriteError() );
#endif

  // KMESS_NET_SENT is called by the base class already.

  // Mark the time we received the message, to detect timeouts
  lastActivity_.start();

  // Send the message size in advance
  QByteArray lengthField( 4, 0x00 );
  P2PMessage::insertBytes( lengthField, message.size(), 0 );
  if( ! writeBlock( lengthField ) && ! hasTemporaryWriteError() )
  {
    kWarning() << "Unable to send the preamble block "
                   "(contact=" << contactHandle_ << ")." << endl;
    return false;
  }

  // Send the actual block
  return writeBlock( message );
}



// This is called when data is received from the socket.
void MsnDirectConnection::slotSocketDataReceived()
{
  // HACK: Completely avoid using DirectConnectionBase::getAvailableBytes() in this method: it makes this method
  // to freeze. It seems that the call to the socket ( KExtendedSocket::bytesAvailable() ) always returns zero.
  // This wouldn't be SO bad as an issue by itself. But the loop ( doing getAvailableBytes() > 0 ) evals to TRUE
  // sticking itself in an infinite loop. I've tried putting a "kWarning() << getAvailableBytes()" statement
  // immediately before the while() statement as last instruction of the loop: in the log it says "kmess: WARNING:
  // 0", but the loop goes on anyways.

  // Also weird stuff: peekBlock() seams to return 0 bytes while readBlock() could return 2.
  // Therefore the preamble is now buffered too.

  while( true )
  {
    // If no block was started,
    // start with a new data block
    if( remainingBlockBytes_ <= 0 )
    {
      qint64 noBytesPeeked = peekBlock(4);
      if( preambleOffset_ == 0 )
      {
        // First see if there are enough bytes.
        // It's possible only 2 of the 4 bytes have arrived.
        if( 0 < noBytesPeeked && noBytesPeeked < 4 )
        {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
          kDebug() << "Socket buffer only has " << noBytesPeeked << " of 4 bytes for the size preamble field, will wait for new data.";
#endif
          return;
        }

        // Reset the buffer for debugging.
        preambleBuffer_.fill( 0x00, 4 );
      }

      // First 4 bytes indicate the message/block length.
      qint64 noBytesRead = readBlock( preambleBuffer_, 4 - preambleOffset_, preambleOffset_ );
      if( noBytesRead == 0 )
      {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
        kDebug() << "Socket buffer is empty, will wait for new data (read=" << noBytesRead << " peek=" << noBytesPeeked << ").";
#endif
        return;
      }
      else if( noBytesRead < 0 )
      {
        kWarning() << "read error on socket "
                       "(read="   << noBytesRead     << " peek="    << noBytesPeeked  <<
                       " offset=" << preambleOffset_ << " contact=" << contactHandle_ << ")." << endl;

        closeConnection();
        return;
      }
      else if( noBytesRead < 4 )
      {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
        kDebug() << "read partial size preamble message "
                     "(read=" << noBytesRead   << " offset=" << preambleOffset_ <<
                     " peek=" << noBytesPeeked << ")." << endl;
#endif

        // Received partial preamble message.
        if( noBytesRead + preambleOffset_ == 4 )
        {
          // Got complete message, reset and continue processing
          preambleOffset_ = 0;

#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
          kDebug() << "got final part of size preamble message.";
#endif
        }
        else
        {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
          kDebug() << "will wait for "
                       "the remaining " << ( 4 - noBytesRead ) << " bytes of the size preamble message..." << endl;
#endif

          preambleOffset_ = noBytesRead;
          return;
        }
      }

      // Add the message to the network window.
#ifdef KMESS_NETWORK_WINDOW
      if( firstMessage_ )
      {
        KMESS_NET_INIT(this, "DC " + getRemoteIp());
      }
      KMESS_NET_RECEIVED( this, preambleBuffer_ );
#endif
#ifdef KMESSTEST
      KMESS_ASSERT( preambleOffset_ == 0 );
#endif
      firstMessage_ = false;

      // Convert the received bytes to a uint
      remainingBlockBytes_ = P2PMessage::extractBytes( preambleBuffer_, 0 );

      // Prevent other clients from DOS-ing us by forcing a large allocation
      // Official client only allows up to 1352 bytes.
      if( remainingBlockBytes_ > 4000 )
      {
        kWarning() << "received unexpected large block length, "
                       "aborting transfer "
                       "(length="  << remainingBlockBytes_ <<
                       " contact=" << contactHandle_ << ")!" << endl;
        closeConnection();
        return;
      }

      // Allocate the buffer.
      buffer_.resize( (int)remainingBlockBytes_ );
      buffer_.fill('\0');
      bufferOffset_ = 0;

#ifdef KMESSDEBUG_DIRECTCONNECTION_RECEIVING
      kDebug() << "4 bytes read, "
                << "next block size is " << remainingBlockBytes_ << " bytes." << endl;
#endif

      // End if there are no more bytes waiting to be read.
      if( remainingBlockBytes_ <= 0 )
      {
        return;
      }
    }


    // Fill the next part of the buffer with the bytes received.
    // Read as many bytes as possible, test for errors.
    qint64 noBytesRead = readBlock( buffer_, remainingBlockBytes_, bufferOffset_ );
    if( noBytesRead < 0 )
    {
      kWarning() << "read error "
                     "(returned "      << noBytesRead <<
                     " block size="    << buffer_.size() <<
                     " buffer offset=" << bufferOffset_ <<
                     " contact=" << contactHandle_ << ")" << endl;
      closeConnection();
      return;
    }
    else if( noBytesRead == 0 )
    {
      // Buffer is currently empty: wait the next data arrival to handle it
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
      kDebug() << "Socket buffer is empty, will wait for new data.";
#endif
      return;
    }

#ifdef KMESS_NETWORK_WINDOW
    KMESS_NET_RECEIVED( this, buffer_ );
#endif

    // Update offsets
    bufferOffset_        += noBytesRead;
    remainingBlockBytes_ -= noBytesRead;

#ifdef KMESSDEBUG_DIRECTCONNECTION_RECEIVING
    kDebug() << "read "
              << noBytesRead << " bytes, " << remainingBlockBytes_ << " remaining." << endl;
#endif
#ifdef KMESSTEST
    KMESS_ASSERT( (int) (bufferOffset_ + remainingBlockBytes_) == buffer_.size() );
#endif

    // Signal when we've received the whole block
    if( remainingBlockBytes_ <= 0 )
    {
      // Find out whether it's a preamble packet or normal message.
      if( buffer_.size() == 4 )
      {
        // This should be a preamble packet from the other client.
        if( QByteArray(buffer_.data(), 3) == "foo" )
        {
#ifdef KMESSDEBUG_DIRECTCONNECTION_GENERAL
          kDebug() << "received preamble packet, ignoring.";
#endif
        }
        else
        {
          kWarning() << "received unexpected preamble packet, ignoring"
                      << " (data=" << buffer_.data() << " contact=" << contactHandle_ << ")." << endl;
        }
      }
      else
      {
#ifdef KMESSDEBUG_DIRECTCONNECTION_RECEIVING
        kDebug() << "Emitting message received signal.";
#endif
        // A normal message was received
        emit messageReceived( buffer_ );

        // Mark the time we received the message, to detect timeouts
        lastActivity_.start();
      }
    }
  }
}



#include "msndirectconnection.moc"
