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

#include "bmp2abmp.h"
typedef struct { UInt8 r; UInt8 g; UInt8 b; } RGB;

#define SQR( x ) ( ( int )( x ) * ( int )( x ) )

#include "palette.c"

#undef WINDOWS

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

#define resourceAttribute  0x0001

FILE *in = NULL;
FILE *out = NULL;
int   interactive = true;
UInt8* inBitmap;
UInt8* outBitmap;

void SafeFree( void** p )
{
    if ( *p != NULL ) {
        free( *p );
        *p = NULL;
    }
}

void CleanUp( void )
{
    SafeFree( ( void** )&inBitmap );
    SafeFree( ( void** )&outBitmap );
    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;
}

UInt32 UInt32LE( UInt32 in )
{
    UInt8*  p;
    UInt32  n;
    int    i;

    p = ( UInt8* )&n;

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

    return n;
}

UInt16 UInt16LE( UInt16 in )
{
    UInt8* p;
    int    i;

    p = ( UInt8* )&in;

    return p[ 0 ] + 256 * p[ 1 ];
}

UInt32 UInt32LEP( UInt16* inP )
{
    return UInt16LE( inP[ 0 ] ) + 65536 * UInt16LE( inP[ 1 ] );
}


#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


static void WrongFormat( void )
{
    err("Unsupported input format");
}


static UInt16 Convert888ToIndexed( UInt8* c )
{
    int i;
    int minD2;
    int minI;
    static RGB lastC = { 0, 0, 0 };
    static int lastIndex = 0xFF;

    if ( c[ 0 ] == 0 && c[ 1 ] == 0 && c[ 2 ] == 0 )
        return 0xFF;

    if ( c[ 2 ] == lastC.r && c[ 1 ] == lastC.g && c[ 0 ] == lastC.b )
        return lastIndex;

    minD2 = SQR(255)*3;
    minI  = 0;

    for ( i = 0 ; i < 256 ; i++ ) {
         int d2;

         d2 = SQR( ( int )c[ 2 ] - ( int )palmPalette[ i ].r ) +
              SQR( ( int )c[ 1 ] - ( int )palmPalette[ i ].g ) +
              SQR( ( int )c[ 0 ] - ( int )palmPalette[ i ].b );

         if ( d2 < minD2 ) {
             minD2 = d2;
             minI  = i;
             if ( d2 == 0 )
                 break;
         }

    }

    lastIndex = minI;
    lastC.r   = c[ 2 ];
    lastC.g   = c[ 1 ];
    lastC.b   = c[ 0 ];

    return minI;
}


static int IsBlack( UInt8* c )
{
    return c[ 0 ] == 0 && c[ 1 ] == 0 && c[ 2 ] == 0;
}


static void ConvertRow( UInt8* dest, UInt8* src, UInt16 width, int fix )
{
    int x;
    UInt8* dest8;

    dest8 = ( void* )dest;

    for ( x = 0 ; x < width ; x++ ) {
         if ( fix && IsBlack( src + x * 3 ) ) {
             int from;

             if ( x == 0 ) {
                 from = x + 1;
             }
             else if ( x == width - 1 ) {
                 from = width - 2;
             }
             else if ( ! IsBlack( src + ( x - 1 ) * 3 ) ) {
                 from = x - 1;
             }
             else if ( ! IsBlack( src + ( x + 1 ) * 3 ) ) {
                 from = x + 1;
             }
             else
                 from = x;

             dest8[ x ] = Convert888ToIndexed( src + from * 3 );
         }
         else
             dest8[ x ] = Convert888ToIndexed( src + x * 3 );
    }
}



#define SEQUENCE_LIMIT 126

Int16 GetRunLength( UInt16* data, UInt16 cols )
{
    UInt16 i;

    for ( i = 1 ; i < cols && data[ i ] == data[ 0 ] && i < SEQUENCE_LIMIT ; i++ ) ;

    return i;
}



UInt8* PackRow( UInt8* out, UInt16* in, UInt16 cols )
{
    UInt16  x;
    UInt16* inEnd;

    while ( cols ) {
        Int16 count;

        count = GetRunLength( in, cols );

        if ( 2 <= count ) {
            *out++ = ( UInt8 )( 1 - count );
            *out++ = *( UInt8* )in;
            *out++ = *( 1 + ( UInt8* )in );

            in   += count;
            cols -= count;
        }
        else {
            UInt16 i;

            for ( i = 1 ; i < cols && i < SEQUENCE_LIMIT && ( i + 1 == cols || in[ i ] != in[ i + 1 ] ) ; i++ );
            *out++ = ( UInt8 )( i - 1 );
            while ( i-- ) {
                *out++ = *( UInt8* )in;
                *out++ = *( 1 + ( UInt8* )in );
                in++;
                cols--;
            }
        }
    }
    return out;
}



void* PackData( UInt16* inData, UInt16 cols, UInt16 rows, UInt32* newSizeP )
{
    UInt8* outData;
    UInt8* p;
    UInt16  row;
    /* Implement packbits compression */
//return NULL;

    outData = malloc( rows * cols * ( sizeof( UInt16 ) + 1 ) + 4 );

    if ( outData == NULL )
        return NULL;

    p = outData + 4;

    for ( row = 0 ; row < rows ; row++ ) {
        p = PackRow( p, inData + row * cols, cols );
    }

    if ( rows * cols * sizeof( UInt16 ) <= p - outData ) {
        free( outData );
        return NULL;
    }

    *newSizeP = ( p - outData );
    
    *( UInt32* )outData = UInt32LE( *newSizeP - 4 );

    return outData;
}


UInt8* ScanLineCompressRow( Boolean firstLine, UInt8* out, UInt8* in, UInt16 rowBytes )
{
    UInt8* startOut;
    UInt8* endIn;
    static UInt8* prevLine;
    UInt8* startIn;

    startIn  = in;
    startOut = out;
    endIn    = in + rowBytes;

    while ( in < endIn ) {
        UInt8* octupleMarker;
        UInt8 mask;
        octupleMarker = out;
        *octupleMarker = 0;
        out++;
        for ( mask = 0x80 ; mask != 0 && in < endIn ; mask >>= 1 ) {
            if ( firstLine || *in != *prevLine ) {
                *octupleMarker |= mask;
                *out++ = *in;
            }
            in++;
            prevLine++;
        }
    }
    prevLine = startIn;
    return out;
}


void* ScanLineData( UInt8* inData, UInt16 byteWidth, UInt16 rows, UInt32* newSizeP )
{
    UInt8* outData;
    UInt8* p;
    UInt16  row;
    /* Implement scanline compression */

     outData = malloc( rows * byteWidth * 2 + 4 );

    if ( outData == NULL )
        return NULL;

    p = outData + 4;

    for ( row = 0 ; row < rows ; row++ ) {
        p = ScanLineCompressRow( row == 0, p, inData + row * byteWidth, byteWidth );
    }

    if ( rows * byteWidth <= p - outData + 4 ) {
        free( outData );
        return NULL;
    }

    *newSizeP = ( p - outData );

    *( UInt32* )outData = UInt32LE( *newSizeP - 4 );

    return outData;
}


void* CompressData( void* bitmap, UInt16 cols, UInt16 rows, UInt32* newSizeP, UInt8* type )
{
    void* scanLine;
    UInt32 scanLineSize;
    void* pack;
    UInt32 packSize;

    pack     = PackData( bitmap, cols, rows, &packSize );
    scanLine = ScanLineData( bitmap, cols * sizeof( UInt16 ), rows, &scanLineSize );

    if ( pack == NULL ) {
        if ( scanLine != NULL ) {
            *type = palmBitmapCompressionTypeScanLine;
            *newSizeP = scanLineSize;
        }

        return scanLine;
    }

    if ( scanLine == NULL ) {
        if ( pack != NULL ) {
            *type = palmBitmapCompressionTypePackBits;
            *newSizeP = packSize;
        }

        return pack;
    }

    if ( packSize < scanLineSize ) {
        free( scanLine );
        *type = palmBitmapCompressionTypePackBits;
        *newSizeP = packSize;
        return pack;
    }
    else {
        free( pack );
        *type = palmBitmapCompressionTypeScanLine;
        *newSizeP = scanLineSize;
        return scanLine;
    }
}


int main(int argc, char **argv)
{
    int i;
    int x;
    int y;
    int index;
    int fix;

    UInt32 pos,pos2,pos3;
    UInt32 inLength;
    UInt32 outLength;
    UInt16 width;
    UInt16 height;
    UInt32 inRowLen;
    UInt16 outRowLen;
    void* compressedBitmap = NULL;
    UInt32 compressedSize = 0;

    BMPHeaderType    inH;
    PalmBitmapTypeV3 outH;

    interactive = 1;
    inBitmap    = NULL;
    outBitmap   = NULL;

    if ( argc != 3 && argc != 4 ) {
        msg("usage", "bmp2abmp [-f] inFileName outFileName.\n"
                     "The -f parameter tries to remove black pixels due to svg artefacts.\n");
        return 1;
    }

    index = 1;
    fix   = 0;

    if ( 3 < argc ) {
        if ( argv[ index ][ 0 ] == '-' ) {
            if ( ! strcmp( argv[ index ], "-f" ) )
                fix = 1;
            index++;
        }
    }

    in = myfopen( argv[ index ], "rb" );

    myfread( &inH, sizeof( inH ), 1, in );

    if ( inH.magic != ( UInt8 )'B' + 256 * ( UInt8 )'M' ||
         UInt16LE( inH.bitsPerPixel ) != 24 ||
         UInt32LEP( inH.compressionType ) != 0 ||
         UInt16LE( inH.planes ) != 1 ) {
        WrongFormat();
    }

    width  = UInt32LEP( inH.width );
    height = UInt32LEP( inH.height );

    outH.width  = UInt16LE( width );
    outH.height = UInt16LE( height );

    outRowLen = width;
    outH.rowBytes = UInt16LE( outRowLen );
    outH.flags    = 0; // directColorLE;
    outH.reserved0 = 0;
    outH.pixelSize = 8;
    outH.version   = 0x83;
    outH.size      = sizeof( PalmBitmapTypeV3 );
    outH.pixelFormat = palmPixelFormatIndexed;
    outH.unused      = 0;
    outH.compressionType = 0;
    outH.density         = UInt16LE( 144 );
    outH.transparentValue = 0;
    outH.nextBitmapOffset = 0;

    /* Ensure inRowLen is 0 mod 4. */
    inRowLen = width * 3 + width % 4;

    inBitmap  = mymalloc( inRowLen * height );
    outBitmap = mymalloc( outRowLen * height );

    myseek( in, UInt32LEP( inH.offset ) );

    myfread( inBitmap, 1, inRowLen * height, in );

    out = myfopen( argv[ index + 1 ], "wb" );

    for ( y = 0 ; y < height ; y++ ) {
        int x;

        ConvertRow( outBitmap + outRowLen * ( height - 1 - y ),
                 inBitmap + inRowLen * y, width, fix );
    }

 //   compressedBitmap = CompressData( ( void* )outBitmap, width, height, &compressedSize, &( outH.compressionType ) );
//
//    if ( compressedBitmap != NULL ) {
//        outH.flags |= compressedLE;
//    }

    fwrite( &outH, 1, sizeof( outH ), out );

    if ( compressedBitmap != NULL ) {
        fwrite( compressedBitmap, 1, compressedSize, out );
        free( compressedBitmap );
    }
    else {
        fwrite( outBitmap, 1, outRowLen * height, out );
    }

    CleanUp();

    return 0;
}
