#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#ifdef WINDOWS
#define ZLIB_WINAPI
#include <windows.h>
#endif

#include "zlib.h"

int skip0;
int decompErr;
typedef unsigned char UInt8;
typedef unsigned long UInt32;
typedef unsigned short UInt16;
int origLen;
int newLen;

#ifdef __GNUCC__
#define STRICMP strcasecmp
#else
#define STRICMP stricmp
#endif

#define resourceAttribute  0x0001

typedef struct {
    UInt8   type[ 4 ];
    UInt16 num;
    UInt32 length;
    UInt32 pos;
    UInt8   uniqueID[ 3 ];
    UInt8   attr;
    UInt8   data[ 0 ];
} Record;

int numRecords = 0;
Record** list = NULL;

#define HEADER_SIZE         0x4C
#define HEADER_TYPE_OFFSET    60
#define HEADER_CREATOR_OFFSET 64

UInt8 header[ HEADER_SIZE ];

int prc;
int interactive = 1;

unsigned long rdatapos;
unsigned long rmappos;
unsigned long rdatalet;
unsigned long rmaplen;
unsigned long rtypepos;
unsigned long rnamepos;
UInt8 numtypes;
unsigned int myresnum;
unsigned long myreflistpos;
unsigned long mynamepos;
unsigned long mydatapos;
unsigned long rdatalen;
char thename[1000];
FILE *in = NULL;
FILE *out = NULL;

#define MIN_COMPRESSION 4 /* percent */

#define ZIP_MAX_SIZE 65000
#define ZIP_MIN_SIZE 100

#define ZIP_MAGIC1 0x6EA8C263
#define ZIP_MAGIC2 0x217BAB04

typedef struct {
    unsigned int magic1;
    unsigned int magic2;
    unsigned int uncompressedSize;
    unsigned int compressedSize;
} ZippedHeader;

void CleanUp( void )
{
    if ( list != NULL ) {
        int i;

        for ( i = 0 ; i < numRecords ; i++ )
           if ( list[ i ] != NULL )
               free( list[ i ] );

        free( list );

        list = NULL;
    }

    fcloseall();
}

void msg(char* title, char *s, ...)
{
    char out[ 2048 ];

    va_list argptr;

    if ( ! interactive )
        return;

    va_start(argptr,s);
    vsprintf(out,s,argptr);
    va_end(argptr);
#ifdef WINDOWS
    MessageBox( NULL, out, title, MB_OK );
#else
    fprintf( stderr, "%s\n", out );
#endif

    CleanUp();

    exit(1);
}

void err(char *s, ...)
{
    char out[ 2048 ];

    va_list argptr;
    va_start(argptr,s);
    vsprintf(out,s,argptr);
    va_end(argptr);

#ifdef WINDOWS
    MessageBox( NULL, out, "Error", MB_OK );
#else
    fprintf( stderr, "Fatal error: %s.\n", out );
#endif

    CleanUp();

    exit(1);
}

FILE *myfopen(char *s,char *m)
{
    FILE *f=fopen(s,m);
    if(f==NULL) err("Cannot open %s.",s);
    return f;
}

int myfgetc(FILE *f)
{
    int c=fgetc(f);
    if(c==-1) err("Error reading font!");
    return c;
}

void* mymalloc( int n )
{
    void* p = malloc( n );

    if ( p == NULL ) err("Error allocating %ld bytes.", n );

    return p;
}

int myfread( void* p, int n, int m, FILE* f )
{
    if ( m != fread( p, n, m, f ) )
        err("Error reading %d x %d bytes.", n, m );
    return m;
}

void myseek(FILE *f, unsigned long x)
{
    if(fseek(f,x,SEEK_SET)<0 ||
        ftell(f)!=(long)x) err("Error seeking!");
    return;
}

unsigned char getUInt8(FILE *f)
{
    int c;
    if(-1==(c=fgetc(f))) err("Error reading file.");
    return c;
}

unsigned short getword(FILE *f)
{
    unsigned short x;
    x=getUInt8(f);
    return x*256+getUInt8(f);
}

unsigned long getdword(FILE *f)
{
    unsigned short x;
    x=getword(f);
    return x*65536l+getword(f);
}

unsigned long get3bytes(FILE *f)
{
    unsigned short x;
    x=getUInt8(f);
    return x*65536l+getword(f);
}

void read4bytes(FILE *f,char *s)
{
    int i;
    for(i=0;i<4;i++) s[i]=getUInt8(f);
    s[4]=0;
}

void putword( UInt16 w, FILE* f )
{
    fputc( w / 256, f );
    fputc( w % 256, f );
}

void putdword( UInt32 dw, FILE* f )
{
    putword( dw / 65536, f );
    putword( dw % 65536, f );
}

void NoCompression( void )
{
    exit( 0 );
}

UInt32 my_htonl( UInt32 in )
{
    UInt32 n;
    UInt8*  p;
    int    i;
    
    p = ( UInt8* )&n;

    for ( i = 0 ; i < 4 ; i++ ) {
        p[ 3 - i ] = in % 256;
        in /= 256;
    }

    return n;
}

void Compress( void* src, UInt32* lenP )
{
    int srcLen;
    void* out;
    int  outLen;
    z_stream zs;
    ZippedHeader h;

    srcLen = *lenP;

    if ( srcLen < ZIP_MIN_SIZE || ZIP_MAX_SIZE < srcLen )
        return;

    out = malloc( srcLen );

    if ( out == NULL )
        return;

    memset( &zs, 0, sizeof( zs ) );

    zs.next_in = src;
    zs.avail_in = srcLen;
    zs.next_out = out;
    zs.avail_out = srcLen;

    if ( Z_OK != deflateInit( &zs, 9 ) ) {
        free( out );
        return;
    }

    if ( Z_STREAM_END != deflate( &zs, Z_FINISH ) ) {
        free( out );
        return;
    }

    outLen = zs.next_out - ( UInt8* )out;

    if ( ( 100 - MIN_COMPRESSION ) * srcLen < 100 * ( outLen + sizeof( ZippedHeader ) ) ) {
        free( out );
        return;
    }

    h.magic1 = my_htonl( ZIP_MAGIC1 );
    h.magic2 = my_htonl( ZIP_MAGIC2 );
    h.compressedSize = my_htonl( outLen );
    h.uncompressedSize = my_htonl( srcLen );

    *lenP = outLen + sizeof( ZippedHeader );
    memcpy( src, &h, sizeof( h ) );
    memcpy( ( UInt8* )src + sizeof( ZippedHeader ), out, outLen );

    free( out );

}

void Uncompress( int i )
{
    int srcLen;
    int destLen;
    Record* newRecord;
    z_stream zs;
    ZippedHeader h;

    srcLen = list[ i ]->length;

    if ( srcLen < sizeof( ZippedHeader ) )
        return;

    memcpy( &h, list[ i ]->data, sizeof( ZippedHeader ) );

    if ( h.magic1 != my_htonl( ZIP_MAGIC1 ) ||
         h.magic2 != my_htonl( ZIP_MAGIC2 ) ||
         srcLen   != my_htonl( h.compressedSize ) + sizeof( ZippedHeader ) ) {
        return;
    }

    destLen = my_htonl( h.uncompressedSize );

    newRecord = malloc( destLen + sizeof( Record ) );
    
    *newRecord = *( list[ i ] );
    newRecord->length = destLen;

    if ( newRecord == NULL ) {
        decompErr = 1;
        return;
    }

    memset( &zs, 0, sizeof( zs ) );

    zs.next_in  = list[ i ]->data + sizeof( ZippedHeader );
    zs.avail_in = srcLen;
    zs.next_out = newRecord->data;
    zs.avail_out = destLen;

    if ( Z_OK != inflateInit( &zs ) ) {
        decompErr = 1;
        free( newRecord );
        return;
    }

    if ( Z_STREAM_END != inflate( &zs, Z_FINISH ) ) {
        free( newRecord );
        decompErr = 1;
        return;
    }

    free( list[ i ] );
    list[ i ] = newRecord;

    free( out );

}

#ifdef WINDOWS
int getname(char *s)
{
int r;
OPENFILENAME ofn;
*s=0;
ZeroMemory(&ofn, sizeof(OPENFILENAME));

ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = NULL;
ofn.lpstrFile = s;
ofn.nMaxFile = MAX_PATH;
ofn.lpstrFilter = "Palm databases\0*.prc;*.pdb\0All files\0*.*\0";
ofn.lpstrTitle = "Select database to compress";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
return GetOpenFileName(&ofn);
}
#endif


int main(int argc, char **argv)
{
    int i;
    int argNum;
    char name[1024];
    char restrictType[5];
    int  restrictId;
    int compress;
    char s[5] = "";

    UInt32 pos,pos2,pos3;
    UInt32 inLength;
    UInt32 outLength;

    list = NULL;

    if ( 4 < argc ||
#ifndef WINDOWS
         argc < 2 ||
#endif
    ( argc == 2 && ( argv[ 1 ][ 1 ] == '?' ||
         ( ( argv[ 1 ][ 0 ] == '-' || argv[ 1 ][ 0 ] == '/' ) &&
           ( argv[ 1 ][ 1 ] == 'h' || argv[ 1 ][ 1 ] == 'H' ) ) ) ) )
        msg("zipDB Usage", "Usage:\n"
            "zipDB [-u] [-n] [-rRRRR[HEX]] filename\n"
            "Compress (uncompresses with -u, noninteractively with -n) the records."
#ifndef WINDOWS
            "\nOr simply double-click or execute with no arguments to use interactively."
#endif
            );

    compress    = 1;
    interactive = 1;
    restrictType[ 0 ] = 0;

#ifdef WINDOWS
    if ( argc == 1 ) {
        if ( ! getname( name ) )
            return 1;
    }
    else
#endif
    {
        for ( argNum = 1 ; argNum < argc - 1 ; argNum++ ) {
            if ( ! strcmp( argv[ argNum ], "-u" ) ) {
                compress = 0;
            }
            else if ( ! strcmp( argv[ argNum ], "-n" ) ) {
                interactive = 0;
            }
            else if ( ! strncmp( argv[ argNum ], "-r", 2 ) ) {
                char* r;

                if ( argv[ argNum ][ 2 ] == 0 ) {
                    r = argv[ ++argNum ];
                }
                else {
                    r = argv[ argNum ] + 2;
                }
                strncpy( restrictType, r, 4 );
                restrictType[ 4 ] = 0;
                if ( strlen( r ) < 5 ) {
                    restrictId = -1;
                }
                else {
                    sscanf( r + 4 , "%x", &restrictId );
                }

            }
        }

        strcpy(name,argv[argNum]);
    }

    in=myfopen(name,"rb");
    myfread( header, HEADER_SIZE, 1, in );

    prc = !! ( resourceAttribute & ( header[ 32 ] * 256 + header[ 33 ] ) );

    /* For speed, do not compress record 0 for palmBible Bibles / xrefs */
    skip0 = ( ! strncmp( header + HEADER_TYPE_OFFSET, "bibl", 4 ) &&
         ! strncmp( header + HEADER_CREATOR_OFFSET, "PPBL", 4 ) ) ||
         ( ! strncmp( header + HEADER_TYPE_OFFSET, "xref", 4 ) &&
         ! strncmp( header + HEADER_CREATOR_OFFSET, "PBLP", 4 ) );

    /* Zodiac skins: change database type */
    if ( ! strncmp( header + HEADER_CREATOR_OFFSET, "ZDIA", 4 ) ) {
        if ( compress && ! strncmp( header + HEADER_TYPE_OFFSET, "skin", 4 ) ) {
            header[ HEADER_TYPE_OFFSET + 3 ] = '_';
        }
        else if ( ! compress && ! strncmp( header + HEADER_TYPE_OFFSET, "ski_", 4 ) ) {
            header[ HEADER_TYPE_OFFSET + 3 ] = 'n';
        }
    }

    /* SkinDIA skins: change database type */
    if ( ! strncmp( header + HEADER_CREATOR_OFFSET, "T3sk", 4 ) ||
         ! strncmp( header + HEADER_CREATOR_OFFSET, "F3BG", 4 ) ||
         ! strncmp( header + HEADER_CREATOR_OFFSET, "sBar", 4 ) ) {
        if ( compress && ! strncmp( header + HEADER_TYPE_OFFSET, "rsrc", 4 ) ) {
            header[ HEADER_TYPE_OFFSET + 3 ] = '_';
        }
        else if ( ! compress && ! strncmp( header + HEADER_TYPE_OFFSET, "rsr_", 4 ) ) {
            header[ HEADER_TYPE_OFFSET + 3 ] = 'c';
        }
    }

    numRecords=getword(in);

    if ( numRecords <= 0 )
        list = NULL;
    else
        list = mymalloc( numRecords * sizeof *list );

    for ( i = 0 ; i < numRecords ; i++ )
        list[ i ] = NULL;

    for(i=0;i<numRecords;i++)
    {
        unsigned short rnum;
        UInt32         size;
        UInt8           attr;
        UInt8           uniqueID[ 3 ];

        if ( prc ) {
            read4bytes(in,s);
            rnum = getword(in);
        }
        pos2=getdword(in);
        if ( ! prc ) {
            attr = getUInt8( in );
            uniqueID[ 0 ] = getUInt8( in );
            uniqueID[ 1 ] = getUInt8( in );
            uniqueID[ 2 ] = getUInt8( in );
        }
        pos=ftell(in);
        if(i<numRecords-1)
        {
       	    if ( prc ) {
                getdword(in);
                getword(in);
            }
            pos3=getdword(in);
        }
        else
        {
            fseek( in, 0, SEEK_END );
            pos3 = ftell( in );
        }

        size = pos3 - pos2;

//        printf( "%d %ld %ld\n", i, pos2, pos3 );
        list[ i ] = mymalloc( size + sizeof( Record ) );
        if ( prc ) {
            memcpy( list[ i ]->type, s, 4 );
            list[ i ]->num = rnum;
        }
        else {
            list[ i ]->num = i;
            memcpy( list[ i ]->uniqueID, uniqueID, 3 );
            list[ i ]->attr = attr;
        }
        list[ i ]->length = size;
        myseek( in, pos2 );
        myfread( list[ i ]->data, 1, size, in );

        myseek(in,pos);
    }

    fclose(in);
    in = NULL;

    inLength = 0;
    outLength = 0;

    for ( i = 0 ; i < numRecords ; i++ ) {
         inLength += list[ i ]->length;

         if ( ! prc || ! restrictType[ 0 ] ||
              ( ! strncmp( restrictType, ( char* )( list[ i ]->type ), 4 )
                && ( restrictId < 0 || list[ i ]->num == restrictId ) ) ) {
             if ( compress && ( i != 0 || ! skip0 ) ) {
                 Compress( list[ i ]->data, &( list[ i ]->length ) );
             }
             else {
                 decompErr = 0;
                 Uncompress( i );
             }
         }
         outLength += list[ i ]->length;
    }

    out = myfopen( name, "wb" );

    fwrite( header, HEADER_SIZE, 1, out );

    putword( numRecords, out );

    list[ 0 ]->pos = HEADER_SIZE + 2 + numRecords * ( prc ? 10 : 8 );
    for ( i = 1 ; i < numRecords ; i++ ) {
        list[ i ]->pos = list[ i - 1 ]->pos + list[ i - 1 ]->length;
    }

    for ( i = 0 ; i < numRecords ; i++ ) {
        if ( prc ) {
            fwrite( list[ i ]->type, 4, 1, out );
            putword( list[ i ]->num, out );
        }
        putdword( list[ i ]->pos, out );

        if ( ! prc ) {
            fputc( list[ i ]->attr, out );
            fwrite( list[ i ]->uniqueID, 3, 1, out );
        }
    }

    for ( i = 0 ; i < numRecords ; i++ ) {
        fwrite( list[ i ]->data, 1, list[ i ]->length, out );
    }

    CleanUp();

    msg( "Done!", "Database data went from %ld bytes to %ld bytes, a %ld%% change.",
         inLength, outLength,
         ( inLength == 0 ) ? 0 :
         ( ( ( long )outLength - ( long )inLength ) * 100 + ( long )inLength / 2 ) / ( long )inLength );

    return 0;
}
