/* This is a PNG image file loading framework */

#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <iostream>
extern "C"
{
#include <png.h>
#include <errno.h>
}
#include "m3derror.h"
#include "image_info.h"

#define PNG_BYTES_TO_CHECK 4
static void png_read_data( png_structp ctx, png_bytep area, png_size_t size )
{
	FILE * src;

	src = ( FILE * ) png_get_io_ptr( ctx );
	fread( area, size, 1, src);
}

void load_png( std::string fname, Image_Info *image_info)
{
	FILE * src;
	src = fopen( fname.c_str(),"rb" );
	if(!src)THROW(strerror(errno));
	png_structp png_ptr;
	png_infop info_ptr;
	png_uint_32 width, height;
	int bit_depth, color_type, interlace_type;
	uint32_t Rmask;
	uint32_t Gmask;
	uint32_t Bmask;
	uint32_t Amask;
	png_bytep *volatile row_pointers;
	int row;
	volatile int ckey = -1;
	png_color_16 *transv;

	/* Initialize the data we will clean up when we're done */
	png_ptr = NULL; info_ptr = NULL; row_pointers = NULL;

	/* Create the PNG loading context structure */
	png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
	                                  NULL, NULL, NULL );
	
	if ( png_ptr == NULL )THROW("Can't create PNG context");

	/* Allocate/initialize the memory for image information.  REQUIRED. */
	info_ptr = png_create_info_struct( png_ptr );
	if ( info_ptr == NULL )THROW("Can't create PNG info");

	/* Set error handling if you are using setjmp/longjmp method (this is
	 * the normal method of doing things with libpng).  REQUIRED unless you
	 * set up your own error handlers in png_create_read_struct() earlier.
	 */
	if ( setjmp( png_ptr->jmpbuf ) )THROW("Making long jump instead of short");

	/* Set up the input control */
	png_set_read_fn( png_ptr, src, png_read_data );

	/* Read PNG header info */
	png_read_info( png_ptr, info_ptr );
	png_get_IHDR( png_ptr, info_ptr, &width, &height, &bit_depth,
	              &color_type, &interlace_type, NULL, NULL );

	/* tell libpng to strip 16 bit/color files down to 8 bits/color */
	png_set_strip_16( png_ptr ) ;

	/* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
	 * byte into separate bytes (useful for paletted and grayscale images).
	 */
	png_set_packing( png_ptr );

	/* scale greyscale values to the range 0..255 */
	if ( color_type == PNG_COLOR_TYPE_GRAY )
		png_set_expand( png_ptr );
	
	/* For images with a single "transparent colour", set colour key;
	   if more than one index has transparency, or if partially transparent
	   entries exist, use full alpha channel */
	if ( png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS ) )
	{
		int num_trans;
		uint8_t *trans;
		png_get_tRNS( png_ptr, info_ptr, &trans, &num_trans,
		              &transv );
		if ( color_type == PNG_COLOR_TYPE_PALETTE )
		{
			/* Check if all tRNS entries are opaque except one */
			int i, t = -1;
			for ( i = 0; i < num_trans; i++ )
				if ( trans[ i ] == 0 )
				{
					if ( t >= 0 )
						break;
					t = i;
				}
				else if ( trans[ i ] != 255 )
					break;
			if ( i == num_trans )
			{
				/* exactly one transparent index */
				ckey = t;
			}
			else
			{
				/* more than one transparent index, or translucency */
				png_set_expand( png_ptr );
			}
		}
		else
			ckey = 0; /* actual value will be set later */
	}

	if ( color_type == PNG_COLOR_TYPE_GRAY_ALPHA )
		png_set_gray_to_rgb( png_ptr );

	png_read_update_info( png_ptr, info_ptr );

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

	Rmask = 0xFF000000;
	Gmask = 0x00FF0000;
	Bmask = 0x0000FF00;
	Amask = 0x000000FF;
	image_info->width = width;
	image_info->height = height; 
	image_info->line_size = width * 4;
        image_info->bpp = 4;
	image_info->data = new char[width * height * 4];
	/* Create the array of pointers to image data */
	row_pointers = ( png_bytep* ) malloc( sizeof( png_bytep ) * height );
	if ( ( row_pointers == NULL ) ) THROW("Row pointers is empty");
	
	for ( row = 1; row <= ( int ) height; row++ )
	{
		row_pointers[ row-1 ] = ( png_bytep )
				( uint8_t * ) (image_info->data + (height-row) * image_info->line_size);
	}

	/* Read the entire image in one go */
	png_read_image( png_ptr, row_pointers );

	/* and we're done!  (png_read_end() can be omitted if no processing of
	 * post-IDAT text/time/etc. is desired)
	 * In some cases it can't read PNG's created by some popular programs (ACDSEE),
	 * we do not want to process comments, so we omit png_read_end
	 */
	png_read_end(png_ptr, info_ptr);
	
	/* Clean up and return */
	png_destroy_read_struct( &png_ptr, info_ptr ? &info_ptr : ( png_infopp ) 0,
	                         ( png_infopp ) 0 );
	if ( row_pointers )
	{
		free( row_pointers );
	}
	fclose(src);
}

