/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

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 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//
//  plDiffBuffer - A utility class for writing and applying a difference
//                 buffer--i.e. a buffer containing a series of modifications
//                 (specifically, adds and copys) that will modify an old
//                 data buffer to match a new one. It's a useful utility
//                 class when doing binary file patching, for example, as you
//                 can write out the changes to this class, get back a data
//                 buffer suitable for writing, then use this class again
//                 later to reconstruct the new buffer.
//
//// History /////////////////////////////////////////////////////////////////
//
//  7.24.2002 mcn   - Created (Happy late b-day to me!)
//
//////////////////////////////////////////////////////////////////////////////

#include "hsTypes.h"
#include "plDiffBuffer.h"
#include "plBSDiffBuffer.h"
#include "hsUtils.h"
#include "hsStream.h"


//////////////////////////////////////////////////////////////////////////////
//// Constructor/Destructors /////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

//// Creation Constructor ////////////////////////////////////////////////////
//  Use this constructor when creating a new diff buffer. Pass in the length
//  of the final new buffer. You don't need to pass in the length of the old
//  buffer, but if you do, it'll help this class do some internal space 
//  optimization.

plDiffBuffer::plDiffBuffer( UInt32 newLength, UInt32 oldLength )
: fBSDiffBuffer( nil )
, fIsBSDiff( false )
{
    // Basically, if the new and old lengths can both fit into 16 bits 
    // (not including a potential negation), then we can store all the
    // segment info as 16-bit values instead of 32.
    if( oldLength > 0 && oldLength < 32767 && newLength < 32767 )
        f16BitMode = true;
    else
        f16BitMode = false;

    fNewLength = newLength;
    fStream = TRACKED_NEW hsRAMStream();
    fStream->WriteSwap32( fNewLength );
    fStream->WriteBool( f16BitMode );
    fWriting = true;
}

//// Application Constructor /////////////////////////////////////////////////
//  Use this constructor when taking a diff buffer and applying it to an old
//  buffer. Pass in a pointer to the diff buffer and its length. The buffer
//  will be copied, so you don't need to keep it around after you construct
//  this object.

plDiffBuffer::plDiffBuffer( void *buffer, UInt32 length )
: fBSDiffBuffer( nil )
, fStream( nil )
, fIsBSDiff( false )
, fWriting( false )
{
    // Check to see if this uses the newer BSDiff format
    if ( buffer && length > 32 &&
         memcmp(buffer,"BSDIFF40",8)==0 )
    {
        // This is a bsdiff buffer. Use plBSDiffBuffer to handle it.
        fBSDiffBuffer = TRACKED_NEW plBSDiffBuffer(buffer, length);
        fIsBSDiff = true;
    }
    else
    {
        fStream = TRACKED_NEW hsRAMStream();
        fStream->Write( length, buffer );
        fStream->Rewind();

        fNewLength = fStream->ReadSwap32();
        f16BitMode = fStream->ReadBool();
    }
}

plDiffBuffer::~plDiffBuffer()
{
    if (fStream)
        delete fStream;
    if (fBSDiffBuffer)
        delete fBSDiffBuffer;
}


//////////////////////////////////////////////////////////////////////////////
//// Creation/Write Functions ////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

//// Add /////////////////////////////////////////////////////////////////////
//  Add() appends an Add-New-Data operation to the diff buffer. The data 
//  supplied will be copied internally, so you can discard it after you call
//  this function. 

void    plDiffBuffer::Add( Int32 length, void *newData )
{
    hsAssert( fWriting, "Trying to Add() to a difference buffer that's reading" );

    // We flag our two different op types by the sign of the length. Negative
    // lengths are an add operation, positive ones are copy ops.
    if( f16BitMode )
        fStream->WriteSwap16( -( (Int16)length ) );
    else
        fStream->WriteSwap32( -length );
    fStream->Write( length, newData );
}

//// Copy ////////////////////////////////////////////////////////////////////
//  Copy() appends a Copy-Data-From-Old operation to the diff buffer. 

void    plDiffBuffer::Copy( Int32 length, UInt32 oldOffset )
{
    hsAssert( fWriting, "Trying to Copy() to a difference buffer that's reading" );

    // We flag our two different op types by the sign of the length. Negative
    // lengths are an add operation, positive ones are copy ops.
    if( f16BitMode )
    {
        fStream->WriteSwap16( (Int16)length );
        fStream->WriteSwap16( (UInt16)oldOffset );
    }
    else
    {
        fStream->WriteSwap32( length );
        fStream->WriteSwap32( oldOffset );
    }
}

//// GetBuffer ///////////////////////////////////////////////////////////////
//  GetBuffer() will copy the diff stream into a new buffer and return it.
//  You are responsible for freeing the buffer. Call this once you're done 
//  adding ops and want the raw data to write out somewhere. Note: this
//  function will rewind the diff stream, so once you call it, you can't do
//  anything else on the object.

void    plDiffBuffer::GetBuffer( UInt32 &length, void *&bufferPtr )
{
    hsAssert( fWriting, "Trying to GetBuffer() on a difference buffer that's reading" );

    length = fStream->GetPosition();
    bufferPtr = (void *)TRACKED_NEW UInt8[ length ];

    fStream->Rewind();
    fStream->Read( length, bufferPtr );
}


//////////////////////////////////////////////////////////////////////////////
//// Application Functions ///////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

//// Apply ///////////////////////////////////////////////////////////////////
//  Apply() will take this diff buffer and apply it to the given old buffer, 
//  allocating and producing a new buffer. You are responsible for freeing 
//  the new buffer.

#define hsAssertAndBreak( cond, msg ) { if( cond ) { hsAssert( false, msg ); break; } }

void    plDiffBuffer::Apply( UInt32 oldLength, void *oldBuffer, UInt32 &newLength, void *&newBuffer )
{
    hsAssert( !fWriting, "Trying to Apply() a difference buffer that's writing" );

    // Is this is a BSDiff patch, use plBSDiffBuffer and return.
    if (fIsBSDiff)
    {
        fBSDiffBuffer->Apply(oldLength, oldBuffer, newLength, newBuffer);
        return;
    }

    /// Step 1: Allocate the new buffer
    newLength = fNewLength;
    UInt8 *new8Buffer = TRACKED_NEW UInt8[ newLength ];
    UInt8 *old8Buffer = (UInt8 *)oldBuffer;
    newBuffer = (void *)new8Buffer;


    /// Step 2: Loop through the difference stream
    Int32   opLength;
    UInt32  newBufferPos = 0;
    while( newBufferPos < newLength )
    {
        // Read in the op length
        if( f16BitMode )
        {
            Int16 opLen16 = fStream->ReadSwap16();
            if( opLen16 < 0 )
                opLength = -( (Int32)( -opLen16 ) );
            else
                opLength = (UInt32)opLen16;
        }
        else
            opLength = fStream->ReadSwap32();

        // As defined, negative ops are add ops, positive ones are copys
        if( opLength < 0 )
        {
            hsAssertAndBreak( newBufferPos - opLength > newLength, "Destination buffer offset in plDiffBuffer() is out of range!" );

            // Add op, read in the added data
            fStream->Read( -opLength, &new8Buffer[ newBufferPos ] );
            newBufferPos += -opLength;
        }
        else
        {
            // Copy op, so get the old offset and copy from there
            UInt32 oldOffset = f16BitMode ? fStream->ReadSwap16() : fStream->ReadSwap32();

            hsAssertAndBreak( newBufferPos + opLength > newLength, "Destination buffer offset in plDiffBuffer() is out of range!" );
            hsAssertAndBreak( oldOffset + opLength > oldLength, "Difference buffer offset in plDiffBuffer() is out of range of the old buffer!" );

            memcpy( &new8Buffer[ newBufferPos ], old8Buffer + oldOffset, opLength );
            newBufferPos += opLength;
        }
    }

    hsAssert( newBufferPos == newLength, "Invalid sequence of difference ops in plDiffBuffer::Apply()" );
}