/***************************************************************************
                          p2pfragmenttracker.cpp -  description
                             -------------------
    begin                : Wed Dec 5 2007
    copyright            : (C) 2007 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 "p2pfragmenttracker.h"

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

#define ADDDIFF(orig, new) ( (new) > (orig) ? (new) - (orig) : 0 )



/**
 * Constructor
 */
P2PFragmentTracker::P2PFragmentTracker()
: messageID_(0)
, totalSize_(0)
, transferredBytes_(0)
{
}



/**
 * Destructor
 */
P2PFragmentTracker::~P2PFragmentTracker()
{
}


/**
 * @brief Return a string with the various received parts.
 * @returns  String with 1..100 120..200 to indicate the received parts.
 */
QString P2PFragmentTracker::getDebugMap() const
{
  // Add header
  QString result( "id=" + QString::number( messageID_ ) +
                  ", got " + QString::number( transferredBytes_ ) +
                  " bytes of " + QString::number( totalSize_ ) );
  if( ! ranges_.isEmpty() )
  {
    result += ", ranges:";
  }

  // Add range details
  unsigned long realCount = 0;
  foreach( Range *range, ranges_ )
  {
    // Update string and counter
    result    += " " + QString::number( range->start ) + ".." + QString::number( range->end );
    realCount += ( range->end - range->start );
  }

  // Test if counters are correct
#ifdef KMESSTEST
  KMESS_ASSERT( realCount == transferredBytes_ );
#endif
  if( realCount != transferredBytes_ )
  {
    result += " ERROR realCount=" + QString::number( realCount );
  }

  return "fragmentedMessage[ " + result + " ]";
}


/**
 * @brief  Return the identifier of the tracked message.
 * @return  The message identifier.
 */
quint32 P2PFragmentTracker::getMessageID() const
{
  return messageID_;
}



/**
 * @brief  Return the number of bytes transferred.
 * @returns Number of bytes transferred.
 */
quint32 P2PFragmentTracker::getTransferredBytes() const
{
  // TODO: for KMess 1.6, remove this
  // Just soo close before release don't assume transferredBytes_ will always be correct

  quint32 realCount = 0;
  foreach( Range *range, ranges_ )
  {
    realCount += ( range->end - range->start );
  }

  if( transferredBytes_ != realCount )
  {
    kWarning() << "P2PFragmentTracker: Invalid byte count for " << getDebugMap();
    return realCount;
  }

  return transferredBytes_;
}



/**
 * @brief  Initialize the tracker with a new message.
 * @param  messageID  Unique identifier of the message.
 * @param  totalSize  The total size of the message.
 *
 * Resets the current state.
 */
void P2PFragmentTracker::initialize( quint32 messageID, quint32 totalSize )
{
  // Reset fields
  messageID_        = messageID;
  transferredBytes_ = 0;
  totalSize_        = totalSize;

  // Clear collection
  qDeleteAll( ranges_ );
  ranges_.clear();
}



/**
 * @brief  Return whether the fragment tracker is complete.
 * @return  Returns true if all fragments are transferred.
 */
bool P2PFragmentTracker::isComplete() const
{
  // Check the preconditions, otherwise don't even check the ranges.
  if( totalSize_== 0 || ranges_.count() != 1 || getTransferredBytes() < totalSize_ )
  {
    return false;
  }

  // Check the last remaining range.
  const Range *range = ranges_.first();
  return range->start == 0 && range->end >= totalSize_;
}



/**
 * @brief Return whether the tracker is empty, no data added yet.
 * @return  Returns true if there is no data in the tracker.
 */
bool P2PFragmentTracker::isEmpty() const
{
  return ranges_.isEmpty();
}



/**
 * @brief Return whether the tracker is initialized to receive a part for the given message ID.
 * @param  messageID  Message identifier to verify.
 * @return  Returns true if the tracker is initialized to receive parts of the given message ID.
 */
bool P2PFragmentTracker::isInitialized( quint32 messageID ) const
{
  return ( messageID_ == messageID );
}



/**
 * @brief  // Register the arrival of a new fragment, updates the counters.
 * @param  offset  The starting point of the fragment.
 * @param  size    The length of the fragment.
 */
void P2PFragmentTracker::registerFragment( quint32 offset, quint32 size )
{
  // Calculate end, check unsafe values.
  quint32 newEnd  = offset + size;
  if( newEnd > totalSize_ )
  {
    newEnd = totalSize_;
  }

  if( offset > totalSize_ )
  {
    return;
  }


  // This algorithm also allows overlapping parts
  // just to be on the safe side.

  // Example ranges:
  // --------------------------------
  //    __A__       ___B___                __C__
  //   |     |     |       |              |     |
  //
  //
  //         |__4__|         |__3__|   |__2__|       |__1__|
  //
  //
  //         |___________5_________|
  //
  //              |____________6____________|
  //
  //
  // Posibilities for new range:
  // 1: after last one
  // 2: after current, but overlap next one.
  // 3: after previous, but before next
  // 4: append current
  // 4b: even fill the gap with the previous element.
  // 5: append current but span over next
  // 6: after current, span over next, but C contains the real new end.
  //
  // Unhandled option: fragment overlapping an existing one completely.

  Range *nextRange = 0; // as in "after current"
  Range *range     = 0;

  // Initialize with first range.
  if( ranges_.isEmpty() )
  {
    range = new Range;
    range->start = offset;
    range->end   = newEnd;
    ranges_.append( range );
    transferredBytes_ = size;
  }
  else
  {
    // More items, see where to add.
    int last   = ranges_.count() - 1;
    bool merge = false;
    int i;
    for( i = last; i >= 0; i-- )
    {
      nextRange = range;
      range     = ranges_.at( i );

      if( offset > range->end )
      {
        // new range starts after current item.

        // See it does not touche/overlap the next item, otherwise update that one instead.
        if( nextRange == 0 || newEnd < nextRange->start )
        {
          // No overlap, insert between current and next element.
          // Option 1 or 3
          i++;
          range = new Range;
          range->start = offset;
          range->end   = newEnd;
          ranges_.insert( i, range );
          transferredBytes_ += size;
          return;
        }
        else
        {
          // Option 2.
          range = nextRange;  // had to update this one after all.
          transferredBytes_ += ADDDIFF( offset, range->start );
          transferredBytes_ += ADDDIFF( range->end, newEnd );
          range->start = offset;
          range->end   = qMax( nextRange->end, newEnd );

          // Fix next item, for next test
          i++;
          nextRange = ( i < last ) ? ranges_.at( i + 1 ) : 0;
          merge = true;
          break;
        }
      }
      else if( offset >= range->start )
      {
        // New range appends current item.
        // Option 4
        transferredBytes_ += ADDDIFF( range->end, newEnd );
        range->end         = qMax( range->end, newEnd );

        // See if the item can be merged.
        // Option 4b and 5
        merge = true;
        break;
      }
      else if( i == 0 && offset < range->start )
      {
        // See if the item should be added before all elements.
        range = new Range;
        range->start = offset;
        range->end   = newEnd;
        ranges_.insert( i, range );
        transferredBytes_ += size;
        merge = true;
        break;
      }
    }


    // Found an item.
    // See if item can be merged with next ones.
    if( merge )
    {
      while( nextRange != 0 && nextRange->start <= range->end )
      {
        quint32 rangeSize = ( nextRange->end - nextRange->start );
        transferredBytes_ -= rangeSize;
        if( nextRange->end > range->end ) // last item has highest range
        {
          transferredBytes_ += ( nextRange->end - range->end );
        }

        range->end = qMax( range->end, nextRange->end );

        delete ranges_.takeAt( i + 1 );
        last--;
        nextRange = ( i < last ) ? ranges_.at( i + 1 ) : 0;
      }
    }
  }
}

