
#include "Png.h"
#include <png.h>
#include <iostream>
using namespace std;

PNGImage::~PNGImage() 
{
  cerr << "PNG - destructor" << endl;
  if (fp!=NULL)
     fclose(fp);

  for (int i=0;i<height;i++)
      delete [] image[i];
  delete image;
  delete [] row_pointers;
}


bool PNGImage::openFileRead() {
  if (fp!=NULL) {
     cerr << "file already opened\n" << endl;
    return false;	// can't open an already opened file
  }

  fp = fopen(filename.c_str(), "rb");
  if (!fp) {
    cerr << "couldn't open file " << filename << "for reading" << endl;;
    return false;			// couldn't open file
  }
	
  cerr << "opened file " <<  filename << endl;
  return true;
}

bool PNGImage::openFileWrite() {
  if (fp!=NULL) {
    cerr << "file already opened" << endl;
    return false;	// can't open an already opened file
  }

  fp = fopen(filename.c_str(), "wb");
  if (!fp) {
    return false;			// couldn't open file
  }
  return true;
}

bool PNGImage::isPNG(int bytesToCheck = 8) {
  if (fp==NULL) {
    if (openFileRead() == false) return false;
  }

  if (bytesToCheck > 8) {
    bytesToCheck=8;
    cerr << "bytesToCheck set to 8.  It cannot be more than 8." << endl;
  }
  else if (bytesToCheck < 1) {
    bytesToCheck=1;
    cerr << "bytesToCheck set to 1.  It cannot be less than 1." << endl;
  }

  // todo: check that fp is at start of file.
	
  char header[8];	// 8 is than maximum size that can be checked.
	
  int retValue = fread(header, 1, bytesToCheck, fp);
  if (retValue != bytesToCheck) {
    cerr << "couldn't read " << bytesToCheck << "bytes from file" << endl;
  }

  // use library to check if it's a valid PNG file
  bool valid;
  valid = ( png_sig_cmp((png_byte*)header, 0, bytesToCheck) == 0);
	
  return valid;
}


bool PNGImage::initReadStructs() {
  //  printf("initReadStructs: starting..." << endl;

  // initialise png_ptr
  //  printf("initReadStructs: initialising png_ptr..." << endl;
  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,    // from png.h
				   NULL, // ptr to struct for user error funct.
				   NULL, // ptr to user error function
				   NULL);// ptr to usr warning function

  if (png_ptr == NULL) {
    cerr << "initReadStructs: png_ptr was not able to be initialised" << endl;
    return false;
  }


  // initialise info_ptr
  //  printf("initReadStructs: initialising info_ptr..." << endl;
  info_ptr = png_create_info_struct(png_ptr);
  if (info_ptr == NULL) {
    cerr << "initReadStructs: info_ptr was not able to be initialised" << endl;
    png_destroy_read_struct(&png_ptr,
			    (png_infopp)NULL,   // ?
			    (png_infopp)NULL);  // ?
    return false;
  }

  // note: there is no end_info variable because I don't know what
  // it's for yet.  But if we need one then it's memory allocation
  // should happen here (once a variable has been declared in the
  // class).

  //  printf("initReadStructs: finished" << endl;
  return true;
}


bool PNGImage::initWriteStructs() {
  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
				    NULL, // ptr to user error struct
				    NULL, // ptr to user error function
				    NULL);// ptr to user warning function

  if (!png_ptr) {
    cerr << "initWriteStructs: failed to init png_ptr" << endl;
    return false;
  }

  info_ptr = png_create_info_struct(png_ptr);
  if (!info_ptr) {
    cerr << "initWriteStructs: failed to init info_ptr" << endl;
    png_destroy_write_struct(&png_ptr,
			     (png_infopp)NULL);
    return false;
  }

  // prepare for errors.  Can't call setjmp() before png_ptr has been
  // allocated because the data inside png_ptr is access by png_jmpbuf!
  if (setjmp(png_jmpbuf(png_ptr))) {  // png_jmpbuf is a macro in pngconf.h
    cerr << "initReadStructs: setjmp returned non-zero (i.e. an error occured.)" << endl;
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL /*end_info*/);
    return false;
  }
  png_init_io(png_ptr, fp);
  cerr << "initWriteStructs: complete" << endl;
  return true;
}


bool PNGImage::writeHeader() {
  cerr << "writeHeader: starting..." << endl;
  // prepare for errors.
  if (setjmp(png_jmpbuf(png_ptr))) {  // png_jmpbuf is a macro in pngconf.h
    cerr << "writeHeader: setjmp returned non-zero (i.e. an error occured.)" << endl;
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL );
    return false;
  }

  cerr << "writeHeader: setting header" << endl;
  png_set_IHDR(png_ptr, info_ptr, width, height,
	       8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
	       PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

  cerr << "writeHeader: writing header" << endl;
  png_write_info(png_ptr, info_ptr);
  cerr << "writeHeader: finished." << endl;

  return true;
}


png_byte** PNGImage::writeImage_Start() {
  cerr << "writeImage: starting..." << endl;
  cerr << "image is " << height << " by " << width << endl;
  // prepare for errors.
  if (setjmp(png_jmpbuf(png_ptr))) {  // png_jmpbuf is a macro in pngconf.h
    cerr << "writeImage: setjmp returned non-zero (i.e. an error occured.)" << endl;
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL /*end_info*/);
    return false;
  }

  for (int k = 0; k < height; k++) {
    row_pointers[k] = image[k];
  }

  return image;
}


bool PNGImage::writeImage_End() {
  png_write_image(png_ptr, row_pointers);
  cerr << "writeImage: finished." << endl;
  return true;
}

bool PNGImage::writeEnd() {
  cerr << "writeEnd: starting..." << endl;

  // prepare for errors.
  if (setjmp(png_jmpbuf(png_ptr))) {  // png_jmpbuf is a macro in pngconf.h
    cerr << "writeEnd: setjmp returned non-zero (i.e. an error occured.)" << endl;
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL /*end_info*/);
    return false;
  }

  png_write_end(png_ptr, NULL);  // NULL because we don't need to
				 // write any comments, etc.

  cerr << "writeEnd: finished" << endl;
  return true;
}


unsigned char** PNGImage::loadPNG(const string& nom, unsigned& _sX, unsigned& _sY)
{
  png_byte header[8];
  int flags;
	png_infop end_info;

  cerr << "PNG::loadPNG - Entering algo" << endl;
  FILE *fp = fopen(nom.c_str(), "rb");
  // check if this is a PNG file
  fread( (char*)header, 1, 8, fp );
  if ( png_sig_cmp(header, 0, 8) ){
    cerr << "PNG::loadPNG - Error SIG size !" << endl;
    return 0;
  }

  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL, NULL, NULL ); //pngfile

  info_ptr = png_create_info_struct( png_ptr ); //pnginfo
  png_infop pngendinfo = png_create_info_struct( png_ptr );

  // In case of an error, we want to die here!
  if( setjmp( png_jmpbuf(png_ptr) ) )
  {
    cerr << "PNG::loadPNG - Error setjmp !" << endl;
    png_destroy_read_struct(&png_ptr, &info_ptr, &pngendinfo);
    fclose(fp);
    return 0;
  }

  png_init_io( png_ptr, fp );
  png_set_sig_bytes( png_ptr, 8 ); // we read 8 bytes to verify

  flags = PNG_TRANSFORM_STRIP_16|PNG_TRANSFORM_PACKING;
  // you can set additional transforms here to convert unuseable formats as you need it

  // "the hard way"
  png_read_png( png_ptr, info_ptr, flags, NULL );

  cerr << "PNG::loadPNG - Get row_pointers" << endl;
  // get pointers to each row of the image
  row_pointers = png_get_rows( png_ptr, info_ptr );
  // read the image into the target buffer. sucks but works, could stand a little reworking
  _sX = info_ptr->width;
  _sY = info_ptr->height;
  //
  int bit_depth, color_type;
  png_uint_32 sX, sY;

	png_get_IHDR(png_ptr, info_ptr, &sX, &sY, &bit_depth, &color_type, NULL,NULL,NULL);

	if (color_type != PNG_COLOR_TYPE_RGB && color_type != PNG_COLOR_TYPE_RGB_ALPHA) {
    cerr << "PNG::loadPNG - Non-readable format !" << endl;
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
		fclose(fp);
	}
  unsigned pixel_size;
	if      (color_type==PNG_COLOR_TYPE_RGB)  pixel_size = 3;
	else if (color_type==PNG_COLOR_TYPE_GRAY) pixel_size = 1;
	else                                      pixel_size = 4;

  bytes_per_pixel = pixel_size;

  unsigned        line_size = sX * pixel_size;

  cerr << "PNG::loadPNG - Allocate the buffer";
  image = new png_byte* [sY];

  if (!image)
  {
    cerr << "\t\t nok" << endl;
    return 0;
  }
  else
    cerr << "\t\t ok" << endl;


  cerr << "PNG::loadPNG - Writing buffer";
  fflush(stdout);

  for (unsigned y=0; y<sY; y++)
  {
    image[y] = new png_byte [line_size];
    if (!memcpy(image[y], row_pointers[y], line_size))
    {
       cerr << "\t\t ~ok" << endl;
       return 0;
    }
  }
  cerr << "\t\t ok" << endl;
  
  _sX = static_cast<unsigned>(sX);
  _sY = static_cast<unsigned>(sY);

  // this will clear all memory used other than the stored texture
  png_destroy_read_struct( &png_ptr, &info_ptr, &pngendinfo );
  fclose( fp );

  return image;
}

