// Lemur OLAP library (c) 2003 National Research Council of Canada by Daniel Lemire, and Owen Kaser
 /**
 *  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 (version 2). 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.
 */
#include "pngimage.h"


PNGImage::~PNGImage() {
  if (fp!=NULL) fclose(fp);
}


bool PNGImage::openFileRead() {
  if (fp!=NULL) {
    cerr << "file already opened" << endl;
    return false;	// can't open an already opened file
  }
  
  fp = fopen(filename.c_str(), "rb");	
  if (!fp) {
    cerr << "couldn't open file "<< filename.c_str() <<
     " for reading." << endl;
    return false;			// couldn't open file
  }
  
//  printf("opened file %s\n", filename.c_str());
  
  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) {
    cerr << "couldn't open file "<< filename.c_str() <<
     " for writing." << endl;
    return false;			// couldn't open file
  }
  
//  printf("opened file %s\n", filename.c_str());
  
  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...\n");

  // initialise png_ptr
  //  printf("initReadStructs: initialising png_ptr...\n");
  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...\n");
  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\n");
  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
    cout << "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);

//  printf("initWriteStructs: complete\n");
  return true;
}


bool PNGImage::writeHeader() {
//  printf("writeHeader: starting...\n");
  // prepare for errors.
  if (setjmp(png_jmpbuf(png_ptr))) {  // png_jmpbuf is a macro in pngconf.h
    cout << "writeHeader: setjmp returned non-zero (i.e. an error occured.)\n" << endl;
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL /*end_info*/);
    return false;
  }

//  printf("writeHeader: setting header\n");
  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);

  //printf("writeHeader: writing header\n");
  png_write_info(png_ptr, info_ptr);
  //printf("writeHeader: finished.\n");

  return true;
}


bool PNGImage::writeImage(png_byte * RGB) {
  //printf("writeImage: starting...\n");
  //printf("image is %d by %d\n", height, width);
  // 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;
  }

  const int bytes_per_pixel = 3;
//	png_byte image = RGB;
//  png_byte image[height][width*bytes_per_pixel];
  png_byte* row_pointers[height];
  const int bytes_per_row = width * bytes_per_pixel ;
  for (int k = 0; k < height; k++) {
    row_pointers[k] = RGB + k * bytes_per_row ;
  }
/*
  for (int i=0; i<height; i++) {
    for (int j=0; j<width; j++) {
      RGB[i * bytes_per_row  + j*bytes_per_pixel+0] = 255;  // red
      RGB[i * bytes_per_row + j*bytes_per_pixel+1] = 0;    // green
      RGB[i * bytes_per_row + j*bytes_per_pixel+2] = 0;    // blue
    }
  }
  */
  png_write_image(png_ptr, row_pointers);


//  printf("writeImage: finished.\n");
  return true;
}


bool PNGImage::writeEnd() {
  //printf("writeEnd: starting...\n");

  // 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.

//  printf("writeEnd: finished\n");
  return true;
}

int maintestpng(int argc, char* argv[]) {
  // open file depending on argument passed from command line
  string filename("newimage.png");
  if (argc==2) filename.assign(argv[1]);
  
  PNGImage image(filename, 10, 10);
  image.openFileWrite();
  /*
  if (image.isPNG()) printf("file is PNG image\n");
  else printf("file is not PNG image\n");
  */
  image.initWriteStructs();
  image.writeHeader();
  png_byte * RGB = new png_byte[3* 10 * 10];
  for (int i=0; i<10/*height*/; i++) {
    for (int j=0; j<10/*width*/; j++) {
      RGB[i * 3 * 10   + j*3+0] = 255;  // red
      RGB[i * 3 * 10 + j*3+1] = 0;    // green
      RGB[i * 3 * 10 + j*3+2] = 0;    // blue
    }
  } 

  image.writeImage(RGB);
  delete[] RGB;
  image.writeEnd();
  
  cout << "finished." << endl;
    
  return 0;	// success
}
 
