/*==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==*/
//////////////////////////////////////////////////////////////////////////////
//
//  plBSDiffBuffer - A utility class for writing and applying a difference
//                  buffer--i.e. a buffer containing a series of modifications
//                  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.
//
//////////////////////////////////////////////////////////////////////////////

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

#define plBSDiffBuffer_MIN(x,y) (((x)<(y)) ? (x) : (y))

//////////////////////////////////////////////////////////////////////////////
//// 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.

plBSDiffBuffer::plBSDiffBuffer( UInt32 newLength, UInt32 oldLength )
: fNewLength( newLength )
, fPatchLength( 0 )
{
    fPatchBuffer = nil;
    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.

plBSDiffBuffer::plBSDiffBuffer( void *buffer, UInt32 length )
: fNewLength( 0 )
, fPatchLength( 0 )
, fPatchBuffer( nil )
, fWriting( false )
{
    if (!buffer || length < 32)
        hsAssert(false, "Corrupt Patch Buffer!");

    if((fPatchBuffer=TRACKED_NEW unsigned char[length+1])!=nil)
    {
        fPatchLength = length;
        memcpy(fPatchBuffer, buffer, fPatchLength);
    }
}

plBSDiffBuffer::~plBSDiffBuffer()
{
    if ( fPatchBuffer )
        delete[] fPatchBuffer;
}


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

//// Diff /////////////////////////////////////////////////////////////////////
//  Diff() creates the diff buffer from the new and old. 
UInt32 plBSDiffBuffer::Diff( UInt32 oldLength, void *oldBuffer, UInt32 newLength, void *newBuffer )
{
    hsAssert( fWriting, "Trying to Add() to a difference buffer that's reading" );

    Int32 *I,*V;

    Int32 scan,pos,len;
    Int32 lastscan,lastpos,lastoffset;
    Int32 oldscore,scsc;

    Int32 s,Sf,lenf,Sb,lenb;
    Int32 overlap,Ss,lens;
    Int32 i;

    UInt32 cblen,dblen,eblen;
    unsigned char *oldbuf, *newbuf;
    unsigned char *cb,*db,*eb;

    unsigned char header[32];

    if (oldBuffer == nil ||
            oldLength < 0 ||
            newBuffer == nil ||
            newLength < 0)
    {
        return (-1);
    }

    oldbuf = (unsigned char *)oldBuffer;
    newbuf = (unsigned char *)newBuffer;

    if(((I=TRACKED_NEW Int32[oldLength+1])==nil) ||
        ((V=TRACKED_NEW Int32[oldLength+1])==nil))
    {
        delete[] I;
        delete[] V;
        return fPatchLength;
    }

    IQSuffixSort(I,V,oldbuf,oldLength);

    delete[] V;

    /*
     *  These could probably be smaller, especially cb.
     */
    if(((cb=TRACKED_NEW unsigned char[newLength+1])==nil) ||
       ((db=TRACKED_NEW unsigned char[newLength+1])==nil) ||
       ((eb=TRACKED_NEW unsigned char[newLength+1])==nil))
    {
        delete[] I;
        delete[] cb;
        delete[] db;
        delete[] eb;
        return fPatchLength;
    }
    cblen=0;
    dblen=0;
    eblen=0;

    /* Header is
        0   8    "BSDIFF40"
        8   8   length of bzip2ed ctrl block
        16  8   length of bzip2ed diff block
        24  8   length of new file */
    /* File is
        0   32  Header
        32  ??  Bzip2ed ctrl block
        ??  ??  Bzip2ed diff block
        ??  ??  Bzip2ed extra block */
    memcpy(header,"BSDIFF40",8);
    memset(header+8,0,24);

    scan=0;len=0;
    lastscan=0;lastpos=0;lastoffset=0;
    while(scan<newLength) {
        oldscore=0;

        for(scsc=scan+=len;scan<newLength;scan++) {
            len=ISearch(I,oldbuf,oldLength,newbuf+scan,newLength-scan,
                    0,oldLength,&pos);

            for(;scsc<scan+len;scsc++)
            if((scsc+lastoffset<oldLength) &&
                (oldbuf[scsc+lastoffset] == newbuf[scsc]))
                oldscore++;

            if(((len==oldscore) && (len!=0)) || 
                (len>oldscore+8)) break;

            if((scan+lastoffset<oldLength) &&
                (oldbuf[scan+lastoffset] == newbuf[scan]))
                oldscore--;
        };

        if((len!=oldscore) || (scan==newLength)) {
            s=0;Sf=0;lenf=0;
            for(i=0;(lastscan+i<scan)&&(lastpos+i<oldLength);) {
                if(oldbuf[lastpos+i]==newbuf[lastscan+i]) s++;
                i++;
                if(s*2-i>Sf*2-lenf) { Sf=s; lenf=i; };
            };

            lenb=0;
            if(scan<newLength) {
                s=0;Sb=0;
                for(i=1;(scan>=lastscan+i)&&(pos>=i);i++) {
                    if(oldbuf[pos-i]==newbuf[scan-i]) s++;
                    if(s*2-i>Sb*2-lenb) { Sb=s; lenb=i; };
                };
            };

            if(lastscan+lenf>scan-lenb) {
                overlap=(lastscan+lenf)-(scan-lenb);
                s=0;Ss=0;lens=0;
                for(i=0;i<overlap;i++) {
                    if(newbuf[lastscan+lenf-overlap+i]==
                       oldbuf[lastpos+lenf-overlap+i]) s++;
                    if(newbuf[scan-lenb+i]==
                       oldbuf[pos-lenb+i]) s--;
                    if(s>Ss) { Ss=s; lens=i+1; };
                };

                lenf+=lens-overlap;
                lenb-=lens;
            };

            for(i=0;i<lenf;i++)
                db[dblen+i]=newbuf[lastscan+i]-oldbuf[lastpos+i];
            for(i=0;i<(scan-lenb)-(lastscan+lenf);i++)
                eb[eblen+i]=newbuf[lastscan+lenf+i];

            dblen+=lenf;
            eblen+=(scan-lenb)-(lastscan+lenf);

            IWriteUnsignedInt8(lenf,cb+cblen);
            IWriteUnsignedInt8((scan-lenb)-(lastscan+lenf),cb+cblen+8);
            IWriteUnsignedInt8((pos-lenb)-(lastpos+lenf),cb+cblen+16);
            cblen+=24;

            lastscan=scan-lenb;
            lastpos=pos-lenb;
            lastoffset=pos-scan;
        };
    };

    IWriteUnsignedInt8(cblen,header+8);
    IWriteUnsignedInt8(dblen,header+16);
    IWriteUnsignedInt8(newLength,header+24);

    fPatchLength = 32 + cblen + dblen + eblen;
    fPatchBuffer = nil;
    if((fPatchBuffer=TRACKED_NEW unsigned char[fPatchLength])!=nil)
    {
        memcpy(fPatchBuffer,header,32);
        memcpy(fPatchBuffer+32,cb,cblen);
        memcpy(fPatchBuffer+32+cblen,db,dblen);
        memcpy(fPatchBuffer+32+cblen+dblen,eb,eblen);
    }
    else
    {
        fPatchLength = 0;
    }

    delete[] cb;
    delete[] db;
    delete[] eb;
    delete[] I;

    return fPatchLength;
}

//// 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 plBSDiffBuffer::GetBuffer( UInt32 &length, void *&bufferPtr )
{
    hsAssert( fWriting, "Trying to GetBuffer() on a difference buffer that's reading" );

    if (fPatchBuffer && fPatchLength)
    {
        length = fPatchLength;
        if (bufferPtr = (void *)TRACKED_NEW unsigned char[ length ])
            memcpy(bufferPtr, fPatchBuffer, length);
    }
    else
    {
        length = 0;
        bufferPtr = nil;
    }
}

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

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

// Patch() 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.
UInt32 plBSDiffBuffer::Patch( UInt32 oldLength, void *oldBuffer, UInt32 &newLength, void *&newBuffer )
{
    hsAssert( !fWriting, "Trying to Patch() a difference buffer that's writing" );

    unsigned char *ctrlpipe, *ctrlend;
    unsigned char *diffpipe, *diffend;
    unsigned char *extrapipe;
    UInt32 ctrllen,datalen;
    int version=0;
    unsigned char *newpos, *newend;
    unsigned char *oldpos, *oldend;
    unsigned char *patchend;
    UInt32 ctrl[3];
    UInt32 i;

    if (oldBuffer == nil ||
        oldLength < 0 ||
        newBuffer != nil ||
        newLength != 0 ||
        fPatchBuffer == nil ||
        fPatchLength < 32)
    {
        return (-1);
    }

    patchend = fPatchBuffer + fPatchLength;
    /*
      Ok, this is going to be messy.  There are two different patch
    formats which we need to support.

      The old format (pre-4.0) is:
        0   8   "QSUFDIFF" or "BSDIFF30"
        8   8   X
        16  8   Y
        24  8   sizeof(newfile)
        32  X   bzip2(control block)
        32+X    Y   bzip2(data block)
    with control block a set of pairs (x,y) meaning "seek forward
    in oldfile by y bytes, and add the next x bytes to x bytes from
    the data block".

      The new format (4.0) is:
        0   8   "BSDIFF40"
        8   8   X
        16  8   Y
        24  8   sizeof(newfile)
        32  X   bzip2(control block)
        32+X    Y   bzip2(diff block)
        32+X+Y  ??? bzip2(extra block)
    with control block a set of triples (x,y,z) meaning "add x bytes
    from oldfile to x bytes from the diff block; copy y bytes from the
    extra block; seek forwards in oldfile by z bytes".
    */

    if(memcmp(fPatchBuffer,"QSUFDIFF",8)==0) version=1;
    if(memcmp(fPatchBuffer,"BSDIFF30",8)==0) version=1;
    if(memcmp(fPatchBuffer,"BSDIFF40",8)==0) version=2;

    if(!version) return (-1);

    ctrllen=IReadUnsignedInt8(fPatchBuffer+8);
    datalen=IReadUnsignedInt8(fPatchBuffer+16);
    newLength=IReadUnsignedInt8(fPatchBuffer+24);
    if((ctrllen<0) || (datalen<0) || (newLength<0) ||
        ((version==1) && (32+ctrllen+datalen!=fPatchLength)))
        return (-1);

    ctrlpipe=fPatchBuffer+32;
    ctrlend =ctrlpipe+ctrllen; 
    diffpipe=ctrlend;
    diffend = diffpipe + datalen;
    if(version==2) {
        extrapipe=diffpipe+datalen;
    };

    if((newBuffer=(void *)TRACKED_NEW unsigned char[newLength+1])==nil) return(-1);
    newpos = (unsigned char *)newBuffer;
    newend = (unsigned char *)newBuffer + newLength;
    oldpos = (unsigned char *)oldBuffer;
    oldend = (unsigned char *)oldBuffer + oldLength;
    while(newpos<newend) {
        for(i=0;i<=version;i++) {
            if(ctrlend-ctrlpipe < 8) return (-1);
            ctrl[i]=IReadUnsignedInt8(ctrlpipe);
            ctrlpipe += 8;
        };

        if(version==1) oldpos+=ctrl[1];

        ISafeMemcpy(newpos, diffpipe, ctrl[0], newend, diffend);
        diffpipe += ctrl[0];

        for(i=0;i<ctrl[0];i++)
            if(oldpos<oldend)
            {
                *newpos += *oldpos;
                newpos++;
                oldpos++;
            }

        if(version==2) {
            ISafeMemcpy(newpos,extrapipe,ctrl[1],newend,patchend);
            extrapipe += ctrl[1];
            newpos+=ctrl[1];
            oldpos+=ctrl[2];
        };
    };

    if((ctrlpipe != ctrlend) ||
       (diffpipe != diffend) ||
       ((version==2) && (extrapipe != patchend)))
    {
        return (-1);
    }

    if(newpos != newend)
    {
        delete[] newBuffer;
        newLength = 0;
    }

    return newLength;
}

//////////////////////////////////////////////////////////////////////////////
//// Private Helper Functions ///////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

// Reads in an 8-byte unsigned integer least significant byte first and returns a
// UInt32. We'll ignore the top four bytes.
UInt32 plBSDiffBuffer::IReadUnsignedInt8(unsigned char *buf)
{
    UInt32 y;

    y=buf[7]&0x7F;
    y=y*256;y+=buf[6];
    y=y*256;y+=buf[5];
    y=y*256;y+=buf[4];
    y=y*256;y+=buf[3];
    y=y*256;y+=buf[2];
    y=y*256;y+=buf[1];
    y=y*256;y+=buf[0];

    // casting added so compiler doesn't warn about negating an unsigned value
    Int32 signedY = (Int32)y;
    if(buf[7]&0x80) signedY=-signedY;
    y = (UInt32)signedY;

    return y;
}

void plBSDiffBuffer::IWriteUnsignedInt8(UInt32 x,unsigned char *buf)
{
    UInt32 y;
    Int32 signedY, signedX = (Int32)x;

    // casting added so compiler doesn't warn about negating an unsigned value
    if(x<0) signedY=-signedX; else signedY=signedX;
    y = (UInt32)signedY;

    buf[0]=(unsigned char)(y%256);y-=buf[0];
    y=y/256;buf[1]=(unsigned char)(y%256);y-=buf[1];
    y=y/256;buf[2]=(unsigned char)(y%256);y-=buf[2];
    y=y/256;buf[3]=(unsigned char)(y%256);y-=buf[3];
    y=y/256;buf[4]=(unsigned char)(y%256);y-=buf[4];
    y=y/256;buf[5]=(unsigned char)(y%256);y-=buf[5];
    y=y/256;buf[6]=(unsigned char)(y%256);y-=buf[6];
    y=y/256;buf[7]=(unsigned char)(y%256);

    if(x<0) buf[7]|=0x80;
}

/*
 *  This function will ensure that no boundary errors occur. It also increments *src
 *  by nbytes when done.
 */
void plBSDiffBuffer::ISafeMemcpy(unsigned char *dest, unsigned char *src, size_t nbytes,
                                 unsigned char *destend, unsigned char *srcend)
{
    if((destend - dest < nbytes) ||
       (srcend - src < nbytes))
    {
        hsAssert(false,"Corrupt patch\n");
    }
    memcpy(dest,src,nbytes);
}

void plBSDiffBuffer::ISplit(Int32 *I,Int32 *V,UInt32 start,UInt32 len,UInt32 h)
{
    UInt32 i,j,k,x,tmp,jj,kk;

    if(len<16) {
        for(k=start;k<start+len;k+=j) {
            j=1;x=V[I[k]+h];
            for(i=1;k+i<start+len;i++) {
                if(V[I[k+i]+h]<x) {
                    x=V[I[k+i]+h];
                    j=0;
                };
                if(V[I[k+i]+h]==x) {
                    tmp=I[k+j];I[k+j]=I[k+i];I[k+i]=tmp;
                    j++;
                };
            };
            for(i=0;i<j;i++) V[I[k+i]]=k+j-1;
            if(j==1) I[k]=-1;
        };
        return;
    };

    x=V[I[start+len/2]+h];
    jj=0;kk=0;
    for(i=start;i<start+len;i++) {
        if(V[I[i]+h]<x) jj++;
        if(V[I[i]+h]==x) kk++;
    };
    jj+=start;kk+=jj;

    i=start;j=0;k=0;
    while(i<jj) {
        if(V[I[i]+h]<x) {
            i++;
        } else if(V[I[i]+h]==x) {
            tmp=I[i];I[i]=I[jj+j];I[jj+j]=tmp;
            j++;
        } else {
            tmp=I[i];I[i]=I[kk+k];I[kk+k]=tmp;
            k++;
        };
    };

    while(jj+j<kk) {
        if(V[I[jj+j]+h]==x) {
            j++;
        } else {
            tmp=I[jj+j];I[jj+j]=I[kk+k];I[kk+k]=tmp;
            k++;
        };
    };

    if(jj>start) ISplit(I,V,start,jj-start,h);

    for(i=0;i<kk-jj;i++) V[I[jj+i]]=kk-1;
    if(jj==kk-1) I[jj]=-1;

    if(start+len>kk) ISplit(I,V,kk,start+len-kk,h);
}

void plBSDiffBuffer::IQSuffixSort(Int32 *I,Int32 *V,unsigned char *old,UInt32 oldsize)
{
    UInt32 buckets[256];
    UInt32 i,h,len;

    for(i=0;i<256;i++) buckets[i]=0;
    for(i=0;i<oldsize;i++) buckets[old[i]]++;
    for(i=1;i<256;i++) buckets[i]+=buckets[i-1];
    for(i=255;i>0;i--) buckets[i]=buckets[i-1];
    buckets[0]=0;

    for(i=0;i<oldsize;i++) I[++buckets[old[i]]]=i;
    I[0]=oldsize;
    for(i=0;i<oldsize;i++) V[i]=buckets[old[i]];
    V[oldsize]=0;
    for(i=1;i<256;i++) if(buckets[i]==buckets[i-1]+1) I[buckets[i]]=-1;
    I[0]=-1;

    // oldsize converted to Int32 to stop warning about negating unsigned numbers
    for(h=1;I[0]!=-(Int32)(oldsize+1);h+=h) {
        len=0;
        for(i=0;i<oldsize+1;) {
            if(I[i]<0) {
                len-=I[i];
                i-=I[i];
            } else {
                // len converted to Int32 to stop the warning about negating an unsigned number
                if(len) I[i-len]=-(Int32)len;
                len=V[I[i]]+1-i;
                ISplit(I,V,i,len,h);
                i+=len;
                len=0;
            };
        };
        // len converted to Int32 to stop the warning about negating an unsigned number
        if(len) I[i-len]=-(Int32)len;
    };

    for(i=0;i<oldsize+1;i++) I[V[i]]=i;
}

UInt32 plBSDiffBuffer::IMatchLen( unsigned char *oldBuffer, UInt32 oldLength,
                                  unsigned char *newBuffer, UInt32 newLength)
{
    UInt32 i;

    for(i=0;(i<oldLength)&&(i<newLength);i++)
        if(oldBuffer[i]!=newBuffer[i]) break;

    return i;
}

UInt32 plBSDiffBuffer::ISearch( Int32 *I,
                                unsigned char *oldBuffer, UInt32 oldLength,
                                unsigned char *newBuffer, UInt32 newLength,
                                UInt32 st, UInt32 en, Int32 *pos)
{
    UInt32 x,y;

    if(en-st<2) {
        x=IMatchLen(oldBuffer+I[st],oldLength-I[st],newBuffer,newLength);
        y=IMatchLen(oldBuffer+I[en],oldLength-I[en],newBuffer,newLength);

        if(x>y) {
            *pos=I[st];
            return x;
        } else {
            *pos=I[en];
            return y;
        }
    };

    x=st+(en-st)/2;
    if(memcmp(oldBuffer+I[x],newBuffer,plBSDiffBuffer_MIN(oldLength-I[x],newLength))<0) {
        return ISearch(I,oldBuffer,oldLength,newBuffer,newLength,x,en,pos);
    } else {
        return ISearch(I,oldBuffer,oldLength,newBuffer,newLength,st,x,pos);
    };
}