/*
	Twilight Prophecy 3D/Multimedia SDK
	A multi-platform development system for virtual reality and multimedia.

	Copyright (C) 1997-2001 by Twilight 3D Finland Oy Ltd.

	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 2 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, write to the Free Software
	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

	Please read the file LICENSE.TXT for additional details.


	source: 
		BitmapCodec "png"

	revision history:
		May/10/1997 - Thomas Carlsson - initian revision
		Dec/25/1999 - Jukka Liimatta - upgraded libpng to version 1.0.5
		Jan/24/2001 - Jukka Liimatta - renaissance build
*/
#include <prcore/extlib/pnglib/png.h>
#include <prcore/prcore.hpp>
using namespace prcore;



//////////////////////////////////////////////////////
// libpng                                          //
////////////////////////////////////////////////////

struct APNG_DECODE
{
	int		size;
	float	gamma;
	float	r;
	float	g;
	float	b;
	int		warnings;
};

static char*	pvbuf = NULL;
static int		pwidth;
static int		pheight;
static int		pbits;
static char*	cbuf = NULL;



static void user_read_fn( png_structp png_ptr, png_bytep data, png_size_t length )
{
	memcpy( data, cbuf, length ); // need overflow check?
	cbuf += length;
}


static void user_warning_fn( png_struct* png_ptr, png_const_charp warning_msg )
{
}


int pngDecode(APNG_DECODE* png, char* mfbuf, int fsize)
{
	if ( !png || !mfbuf || !fsize ) return 0;
	if ( png->size != sizeof( APNG_DECODE ) ) return 0;
	cbuf = mfbuf;

	if ( !png_check_sig( (unsigned char *)cbuf, 8 ) ) return 0; // Sanity

	png_structp png_ptr;
	png_infop info_ptr;
	png_uint_32 width, height;
	int bit_depth, color_type, interlace_type;
	png_color_16 my_background;
//	png_color_16p image_background;

	double      screen_gamma = 0;
	double      image_gamma = 0.45;
	int         number_passes = 0;


	if ( ( png->r != 0.0 ) || ( png->g != 0.0 ) || ( png->b != 0.0 ) )
	{
		if ( png->r > 1.0 ) png->r = 1.0;
		if ( png->g > 1.0 ) png->g = 1.0;
		if ( png->b > 1.0 ) png->b = 1.0;

		if ( png->r < 0.0 ) png->r = 0.0;
		if ( png->g < 0.0 ) png->g = 0.0;
		if ( png->b < 0.0 ) png->b = 0.0;

		my_background.red   = (png_uint_16)(png->r * 65535.0);
		my_background.green = (png_uint_16)(png->g * 65535.0);
		my_background.blue  = (png_uint_16)(png->b * 65535.0);
	}
	else
	{
		my_background.red = my_background.green = my_background.blue = 0;
	}


	pvbuf = NULL;

	png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, &user_warning_fn );
//      (void *)user_error_ptr, user_error_fn, user_warning_fn );

	if ( png_ptr == NULL ) return 0;

	info_ptr = png_create_info_struct( png_ptr );
	if ( info_ptr == NULL )
	{
		png_destroy_read_struct( &png_ptr, (png_infopp)NULL, (png_infopp)NULL );
		return 0;
	}

	if ( setjmp( png_ptr->jmpbuf ) )
	{
		png_destroy_read_struct( &png_ptr, &info_ptr, (png_infopp)NULL );
//		if ( row_pointers ) delete[] row_pointers;
//		row_pointers = NULL;
		return 0;
	}


//  png_init_io( png_ptr, fp );
//  png_set_read_fn( png_ptr, (void *)user_io_ptr, user_read_fn );
	png_set_read_fn( png_ptr, (void *)NULL, &user_read_fn );



	png_read_info( png_ptr, info_ptr );

	png_get_IHDR( png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
		&interlace_type, NULL, NULL );



	png_set_strip_16( png_ptr );    // Strip 16 bit channels to 8 bit

	png_set_packing( png_ptr );     // Separate palettized channels


	// palette -> RGB
	if ( color_type == PNG_COLOR_TYPE_PALETTE ) png_set_expand( png_ptr );


	// grayscale -> 8 bits
	if ( color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8 ) png_set_expand( png_ptr );


	// if exists, expand tRNS to alpha channel
	if ( png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS ) ) png_set_expand( png_ptr );


	if ( png->r || png->g || png->b )
		png_set_background( png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0 );
//  else
//  if ( png_get_bKGD( png_ptr, info_ptr, &image_background ) )
//      png_set_background( png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0 );
//    else
//        png_set_background( png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0 );


	screen_gamma = png->gamma ? png->gamma : 1.7;

	image_gamma = 0.0;
	if ( png_get_gAMA( png_ptr, info_ptr, &image_gamma ) )
		png_set_gamma( png_ptr, screen_gamma, image_gamma );
	else
		png_set_gamma( png_ptr, screen_gamma, 0.45 );


	png_set_bgr( png_ptr );         // Flip RGBA to BGRA
//	png_set_swap_alpha( png_ptr );  // Swap BGRA to ABGR
//	png_set_swap( png_ptr );        // Swap 16 bit order to lsb first



	png_set_filler( png_ptr, 0xff, PNG_FILLER_AFTER ); // force Alpha byte



	number_passes = png_set_interlace_handling( png_ptr );

	png_read_update_info( png_ptr, info_ptr );      // update gamma, etc.



	png_bytep* row_pointers = new png_bytep[height];

	pwidth = width;
	pheight = height;
	int pdepth = 0;

	if ( color_type == PNG_COLOR_TYPE_GRAY )
	{
		pbits = 9;  // Gray
		pdepth = 1;
	}
	else
	{
		pbits = 32;
		pdepth = 4;
	}
	char* tbuffer = new char[pwidth * pheight * pdepth];
	if ( !tbuffer )
	{
		png_destroy_read_struct( &png_ptr, &info_ptr, (png_infopp)NULL );
		delete[] row_pointers;
		row_pointers = NULL;
		return 0;
	}

	pvbuf = tbuffer;
	for ( int i = 0; i < pwidth * pheight * pdepth; i++ )      // DEBUG!
	{
		tbuffer[i] = 0;
	}

	for ( png_uint_32 row = 0; row < height; row++ )
	{
		row_pointers[row] = (unsigned char *)&tbuffer[row * pwidth * pdepth];
	}


	int number_of_rows = height;

	for ( int pass = 0; pass < number_passes; pass++ )
	{
		for ( png_uint_32 y = 0; y < height; y += number_of_rows )
		{
			png_read_rows( png_ptr, row_pointers, NULL, number_of_rows );
		}
	}

	png_read_end( png_ptr, info_ptr );
	png_destroy_read_struct( &png_ptr, &info_ptr, (png_infopp)NULL );

	delete[] row_pointers;

	return 1;
}


//////////////////////////////////////////////////////
// codec                                           //
////////////////////////////////////////////////////

static int NumEXT()
{
	return 1;
}


static const char* GetEXT(int index)
{
	return "png";
}


static bool IsDecoder()
{
	return true;
}


static bool IsEncoder()
{
	return false;
}


static bool Decode(Bitmap& target, Stream& stream)
{
	// reset stream
	stream.Seek(0,Stream::START);

	// read stream
	int size = stream.GetSize();
	char* data = new char[ size ];
	stream.Read(data,size);

	// parameters for decoder
	APNG_DECODE upng;
	upng.size = sizeof( upng );
	upng.gamma = 2.3f;
	upng.r = 0;
	upng.g = 0;
	upng.b = 0;
	upng.warnings = 0;

	// decode png
	pvbuf = NULL;
	if ( !pngDecode( &upng, data, size ) )
	{
		delete[] pvbuf;
		delete[] data;
		return false;
	}

	// png pixelformat
	PixelFormat pformat;
	switch ( pbits )
	{
		case 9:		pformat = PixelFormat(INTENSITY8); break;
		case 32:	pformat = PixelFormat(ARGB8888); break;
		default:	
		{
			delete[] pvbuf;
			delete[] data;
			return false;
		}
	}

	// setup image
	target.SetImage( pwidth, pheight, pformat, pvbuf );

	// release
	delete[] data;

	return true;
}


static bool Encode(Stream& target, Surface& surface)
{
	return false;
}


//////////////////////////////////////////////////////
// factory                                         //
////////////////////////////////////////////////////

BitmapCodec CreateCodecPNG()
{
	BitmapCodec codec;
	
	codec.NumEXT = NumEXT;
	codec.GetEXT = GetEXT;
	codec.IsDecoder = IsDecoder;
	codec.IsEncoder = IsEncoder;
	codec.Decode = Decode;
	codec.Encode = Encode;
	
	return codec;
}
