/*
 * Modification History
 *
 * 2006-November-21   Jason Rohrer
 * Created.
 *
 * 2010-May-18    Jason Rohrer
 * String parameters as const to fix warnings.
 */


#include "PNGImageConverter.h"

#include "minorGems/util/SimpleVector.h"
#include "minorGems/graphics/RGBAImage.h"

//#include "lodepng.h"

//#include <zlib.h>

#include <png.h>



PNGImageConverter::PNGImageConverter( int inCompressionLevel )
        : mCompressionLevel( inCompressionLevel  ) {

    // set up the CRC table
    
    // code taken from the PNG spec:
    // http://www.w3.org/TR/2003/REC-PNG-20031110/#D-CRCAppendix
    unsigned long c;
    int n, k;
   
    for( n=0; n<256; n++ ) {
        c = (unsigned long)n;
        for( k=0; k<8; k++ ) {
            if( c & 1 ) {
                c = 0xedb88320L ^ (c >> 1);
                }
            else {
                c = c >> 1;
                }
            }
        mCRCTable[n] = c;
        }
    }



unsigned long PNGImageConverter::updateCRC(
    unsigned long inCRC, unsigned char *inData, int inLength ) {

    // code taken from the PNG spec:
    // http://www.w3.org/TR/2003/REC-PNG-20031110/#D-CRCAppendix
    
    unsigned long c = inCRC;
    int n;
   
    for( n=0; n<inLength; n++ ) {
        c = mCRCTable[ (c ^ inData[n]) & 0xff ]
            ^
            (c >> 8);
        }
    return c;    
    }



#define ADLER_BASE 65521 /* largest prime smaller than 65536 */

/**
 * Updates an adler32 checksum.
 * code found here  http://www.ietf.org/rfc/rfc1950.txt
 *
 * New adlers should start with inAdler set to 1.
 *
 * @param inAdler the current state of the checksum.
 * @param inData the data to add.  Destroyed by caller.
 * @param inLength the length of the data in bytes.
 *
 * @return the new checksum.
 */
unsigned long updateAdler32( unsigned long inAdler,
                             unsigned char *inData, int inLength ) {
    unsigned long s1 = inAdler & 0xffff;
    unsigned long s2 = (inAdler >> 16) & 0xffff;
    int n;

    for (n = 0; n < inLength; n++) {
        s1 = (s1 + inData[n]) % ADLER_BASE;
        s2 = (s2 + s1)     % ADLER_BASE;
        }
    return (s2 << 16) + s1;
    }



void PNGImageConverter::writeChunk(
    const char inChunkType[4], unsigned char *inData,
    unsigned long inNumBytes, OutputStream *inStream ) {

    // chunk layout:
    
    // 4-byte length
    // 4-char type
    // data
    // 4-byte CRC (applied to type and data parts)


    // write the length
    writeBigEndianLong( inNumBytes, inStream );

    inStream->write( (unsigned char *)inChunkType, 4 );

    // start the crc
    unsigned long crc = updateCRC( mStartCRC,
                                   (unsigned char *)inChunkType, 4 );

    if( inData != NULL ) {
        // chunk has data
        
        inStream->write( inData, inNumBytes );

        crc = updateCRC( crc, inData, inNumBytes );
        }

    // final step:  invert the CRC
    crc = crc ^ 0xffffffffL;
    
    // now write the CRC
    writeBigEndianLong( crc, inStream );
    }



// callbacks for libpng io
void libpngWriteCallback( png_structp png_ptr,
                          png_bytep data, png_size_t length ) {
    
    // unpack our extra parameter
    void *write_io_ptr = png_get_io_ptr( png_ptr );

    OutputStream *inStream = (OutputStream *)write_io_ptr;    
    
    inStream->write( data, length );
    }



void libpngFlushCallback( png_structp png_ptr ) {
    // do nothing?
    }



void PNGImageConverter::formatImage( Image *inImage, 
	OutputStream *inStream ) {

	int numChannels = inImage->getNumChannels();
	
	// make sure the image is in the right format
	if( numChannels != 3 &&
		numChannels != 4 ) {
		printf( "Only 3- and 4-channel images can be converted to " );
		printf( "the PNG format.\n" );
		return;
		}

	int w = inImage->getWidth();
	int h = inImage->getHeight();
	
    //RGBAImage rgbaImage( inImage );
    
    unsigned char *imageBytes = RGBAImage::getRGBABytes( inImage );

    

    // libpng implementation

    // adapted from this:
    //  http://zarb.org/~gc/html/libpng.html


    // get pointers to rows
    unsigned char **rows = new unsigned char *[h];
    
    for( int y=0; y<h; y++ ) {
        rows[y] = &( imageBytes[ y * (w * 4) ] );
        }


    png_structp png_ptr;
    png_infop info_ptr;

	// initialize structures
	png_ptr = png_create_write_struct( PNG_LIBPNG_VER_STRING, 
                                       NULL, NULL, NULL );
	if( !png_ptr ) {
        delete [] imageBytes;
        delete [] rows;
        printf( "PNG Writing:  png_create_write_struct failed\n" );
        return;
        }
    
    png_set_compression_level( png_ptr, mCompressionLevel );


	info_ptr = png_create_info_struct( png_ptr );
	
    if( !info_ptr ) {
        delete [] imageBytes;
        delete [] rows;
        png_destroy_write_struct( &png_ptr, NULL );

        printf( "PNG Writing:  png_create_info_struct failed\n" );
        return;
        }
    
    // weird way that libpng handles errors with a jump
	if( setjmp( png_jmpbuf( png_ptr ) ) ) {
        delete [] imageBytes;
        delete [] rows;
        png_destroy_write_struct( &png_ptr, &info_ptr );

        printf( "PNG Writing: error when setting writing funciton\n" );
        return;
        }
    

	//png_init_io(png_ptr, fp);


    // set our write callback (since we're not writing directly to file)
    // pass the stream as the extra void* parameter
    png_set_write_fn( png_ptr,
                      (void*)inStream, 
                      libpngWriteCallback,
                      libpngFlushCallback );
    

	// write header
	if( setjmp( png_jmpbuf( png_ptr ) ) ) {
        delete [] imageBytes;
        delete [] rows;
        png_destroy_write_struct( &png_ptr, &info_ptr );

        printf( "PNG Writing: error writing header\n" );
        return;
        }

	png_set_IHDR( png_ptr, info_ptr, w, h,
                  8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
                  PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE );

	png_write_info( png_ptr, info_ptr );


	// write bytes
    // write header
	if( setjmp( png_jmpbuf( png_ptr ) ) ) {
        delete [] imageBytes;
        delete [] rows;
        png_destroy_write_struct( &png_ptr, &info_ptr );

        printf( "PNG Writing: error writing image bytes\n" );
        return;
        }

	png_write_image( png_ptr, rows );


	// end write
    if( setjmp( png_jmpbuf( png_ptr ) ) ) {
        delete [] imageBytes;
        delete [] rows;
        png_destroy_write_struct( &png_ptr, &info_ptr );

        printf( "PNG Writing: error ending write\n" );
        return;
        }

	png_write_end( png_ptr, NULL );


    png_destroy_write_struct( &png_ptr, &info_ptr );
    
    delete [] imageBytes;
    delete [] rows;
    

    if( true ) {
        return;
        }
    






    




    /*

    // LodePNG implementation
    
    unsigned char *encodedBytes;
    size_t encodedSize;

    unsigned int error = LodePNG_encode32( &encodedBytes, &encodedSize, 
                                           imageBytes, w, h );
    
    delete [] imageBytes;
    
    if( error ) {
        delete [] encodedBytes;
        
        printf( "Error encoding PNG image\n" );
        return;
        }

    inStream->write( encodedBytes, encodedSize );

    delete [] encodedBytes;
    
    if( true ) {
        return;
        }
    
    */    



    // same for all PNG images
    // used to check for basic transmission errors, such as line-end flipping
    unsigned char pngSignature[8] = { 0x89, 0x50, 0x4E, 0x47,
                                      0x0D, 0x0A, 0x1A, 0x0A };

    inStream->write( pngSignature, 8 );
	

    // data for IHDR chunk
    unsigned char headerData[13];

    // width
    headerData[0] = (w >> 24) & 0xff;
    headerData[1] = (w >> 16) & 0xff;
    headerData[2] = (w >> 8) & 0xff;
    headerData[3] = w & 0xff;

    // height
    headerData[4] = (h >> 24) & 0xff;
    headerData[5] = (h >> 16) & 0xff;
    headerData[6] = (h >> 8) & 0xff;
    headerData[7] = h & 0xff;

    // bit depth
    headerData[8] = 8;

    // color type
    // 2 = truecolor (RGB)
    headerData[9] = 2;

    // compression method
    // method 0  (deflate)
    headerData[10] = 0;

    // filter method
    // method 0 supports 5 filter types
    headerData[11] = 0;

    // no interlace
    headerData[12] = 0;

    writeChunk( "IHDR", headerData, 13, inStream );


    int numRawBytes = w * h * 3;
    // extra byte per scanline for filter type
    numRawBytes += h;

    unsigned char *rawScanlineBytes = new unsigned char[ numRawBytes ];

    // ignore alpha channel
    double *channels[3];
    int i;
    for( i=0; i<3; i++ ) {
        channels[i] = inImage->getChannel( i );
        }

    int pixelNumber = 0;
    for( int y=0; y<h; y++ ) {
        // each scanline starts with filter type byte
        // using filter type 0 (no filtering)

        int index = y * ( w * 3 + 1 );

        // filter type
        rawScanlineBytes[ index ] = 0;

        index++;

        for( int x=0; x<w; x++ ) {

            for( i=0; i<3; i++ ) {
                rawScanlineBytes[ index ] =
                    (unsigned char)( 255 * channels[i][pixelNumber] );

                index ++;
                }

            pixelNumber++;
            }        
        }


    // zlib block contains
    // zlib compression method  (1 byte)
    // flags  (1 byte)
    // compressed data blocks
    // check value (4 bytes) (not CRC)

    SimpleVector<unsigned char> zlibBlock;

    // compression method 8 (deflate)
    // with a LZ77 window size parameter of w=7
    // LZ77 window size is then 2^( w + 8 ), or in this case 32768
    zlibBlock.push_back( 0x78 );

    // flags
    // compression level 0  (2 bytes = 00b)
    // no preset dictionary (1 byte = 0b)
    // check bits for compression method (5 bits)
    //   Should be such that if the 8-bit compression method, followed
    //   by the 8-bit flags field, is viewed as a 16-bit number,
    //   it is an even multiple of 31
    //   For our settings, check bits of 00001 works
    //zlibBlock.push_back( 0x01 );
    // hack:  mimic zlib here
    zlibBlock.push_back( 0xda );

    
    // now ready for compressed data blocks

    int rawDataIndex = 0;

    // length field is 16 bits
    int maxUncompressedBlockLength = 65535;
    
    while( rawDataIndex < numRawBytes ) {

        // push another deflate block

        // first bit BFINAL
        //   only 1 for final block
        // next two bits BTYPE
        //   BTYPE=00 is an uncompressed block
        // remaining 5 bits ignored
        // Thus, we put 0x00 for uncompressed blocks that are not final
        // and 0x80 for final uncompressed block

        
        int bytesLeft = numRawBytes - rawDataIndex;

        int bytesInBlock;
        
        if( bytesLeft <= maxUncompressedBlockLength ) {
            // final

            // hack:  when comparing output with zlib, noticed that it doesn't
            // set 0x80 for the final block, instead it uses 0x01
            // For some reason, this was making the PNG unreadable
            // zlibBlock.push_back( 0x80 );
            zlibBlock.push_back( 0x01 );

            bytesInBlock = bytesLeft;
            }
        else {
            // not final
            zlibBlock.push_back( 0x00 );
            
            bytesInBlock = maxUncompressedBlockLength;
            }

        // length in least-significant-byte-first order
        unsigned char firstLengthByte = bytesInBlock & 0xff;
        unsigned char secondLengthByte = (bytesInBlock >> 8) & 0xff;
        zlibBlock.push_back( firstLengthByte );
        zlibBlock.push_back( secondLengthByte );

        // those same length bytes inverted
        // (called "one's compliment" in the spec
        zlibBlock.push_back( firstLengthByte ^ 0xff);
        zlibBlock.push_back( secondLengthByte ^ 0xff );

        // now the uncompressed data

        for( int b=0; b< bytesInBlock; b++ ) {
            zlibBlock.push_back( rawScanlineBytes[ rawDataIndex ] );

            rawDataIndex++;
            }
        }
    

    // finally, adler32 of original data
    unsigned long adler = updateAdler32( 1L, rawScanlineBytes, numRawBytes );
    
    zlibBlock.push_back( (adler >> 24) & 0xff );
    zlibBlock.push_back( (adler >> 16) & 0xff );
    zlibBlock.push_back( (adler >> 8) & 0xff );
    zlibBlock.push_back( adler & 0xff );
    

    // the zlib block is now complete



    
    /*
    // check against real zlib implementation
    z_stream zStream;

    zStream.next_in = rawScanlineBytes;
    zStream.avail_in = numRawBytes;
    zStream.total_in = 0;
    
    int outSize = 2 * numRawBytes + 100;
    unsigned char *zlibOutBuffer = new unsigned char[ outSize ]; 
    zStream.next_out = zlibOutBuffer;
    zStream.avail_out = outSize;
    zStream.total_out = 0;

    zStream.data_type = Z_BINARY;

    zStream.zalloc = Z_NULL;
    zStream.zfree = Z_NULL;

    // init the stream
    // no compression
    int result;
    //result = deflateInit( &zStream, Z_DEFAULT_COMPRESSION);
    result = deflateInit( &zStream, Z_NO_COMPRESSION);

    if( result != Z_OK ) {
        printf( "zlib deflateInit error: %s\n", zStream.msg );
        }

    
    // deflate and flush
    result = deflate( &zStream, Z_FINISH );

    if( result != Z_STREAM_END ) {
        printf( "zlib deflate error (%d): %s\n", result, zStream.msg );
        }
    printf( "Total in = %d, total out = %d\n",
            zStream.total_in, zStream.total_out );
    
    

    
    printf( "Our raw bytes (%d):\n", numRawBytes );
    int b;
    for( b=0; b<numRawBytes; b++ ) {
        printf( "%02x ", rawScanlineBytes[ b ] );
        }
    
    printf( "\n\nOur zlib bytes (%d) (adler = %d) :\n",
            zlibBlock.size(), adler );
    
    for( b=0; b<zlibBlock.size(); b++ ) {
        printf( "%02x ", *( zlibBlock.getElement( b ) ) );
        }


    printf( "\n\nTheir zlib bytes (%d) (adler = %d) :\n",
            (int)( zStream.total_out ), zStream.adler );
    for( b=0; b<zStream.total_out; b++ ) {
        printf( "%02x ", zlibOutBuffer[ b ] );
        }

    int minBytes = zStream.total_out;
    if( minBytes > zlibBlock.size() ) {
        minBytes = zlibBlock.size();
        }

    for( b=0; b<minBytes; b++ ) {
        if( zlibOutBuffer[b] != *( zlibBlock.getElement( b ) ) ) {
            printf( "mismatch at byte %d\n", b );
            printf( "Theirs: %02x, ours: %02x\n", zlibOutBuffer[b],
                    *( zlibBlock.getElement( b ) ) );
            }
        }

    
    printf( "\n\n" );
    

    
    
    delete [] zlibOutBuffer;
    result = deflateEnd( &zStream );

    if( result != Z_OK ) {
        printf( "zlib deflateEnd error (%d): %s\n", result, zStream.msg );
        }

    */    
    

    
    // the zlib block is the data of an IDAT chunk

    unsigned char *zlibBytes = zlibBlock.getElementArray();

    writeChunk( "IDAT", zlibBytes, zlibBlock.size(), inStream );
    
    
    delete [] zlibBytes;
    
    // no data in end chunk
    writeChunk( "IEND", NULL, 0, inStream );


    delete [] rawScanlineBytes;

    
    // done
	}



Image *PNGImageConverter::deformatImage( InputStream *inStream ) {

    printf( "ERROR:  reading PNG images is not supported by "
            "PNGImageConverter\n" );

    return NULL;
	}
