#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h> //includes memset?
#include <png.h>

#include <amscimglib4/amscimglib4.h>
#include <amscimglib4/amscimglib4_intl.h>

#ifdef __cplusplus
extern "C" {  
#endif


/*
* 1) Declare a 'png_image' structure (see below) on the stack, set the
*    version field to PNG_IMAGE_VERSION and the 'opaque' pointer to NULL
*    (this is REQUIRED, your program may crash if you don't do it.)
* 2) Call the appropriate png_image_begin_read... function.
* 3) Set the png_image 'format' member to the required sample format.
* 4) Allocate a buffer for the image and, if required, the color-map.
* 5) Call png_image_finish_read to read the image and, if required, the
*    color-map into your buffers.

* To write a PNG file using the simplified API:
*
* 1) Declare a 'png_image' structure on the stack and memset() it to all zero.
* 2) Initialize the members of the structure that describe the image, setting
*    the 'format' member to the format of the image samples.
* 3) Call the appropriate png_image_write... function with a pointer to the
*    image and, if necessary, the color-map to write the PNG data.

*/

/* Initialize to NULL, free with png_image_free */



AMSCIMGLIB4_API void amscimglib4_readimage_png(const char *fname, amscimglib4_image *img)
{
  int I,J;
  //FILE *fp;
  png_image image; //the control structure used by libpng
  //png_image has height, width, format, flags...
  //
  //format = PNG_FORMAT_RGBA
  //          PNG_FORMAT_
  memset(&image,0,sizeof(image)); //fills memory with zeros
  image.version = PNG_IMAGE_VERSION;

  if(png_image_begin_read_from_file(&image, fname) != 0)
  {
    png_bytep buffer; //presumably this is a pointer to bytes
    unsigned char *bf2;
    int z;
    unsigned char R,G,B,A;
    //set the format in which to read the PNG file: This code chooses RGBA
    image.format = PNG_FORMAT_RGBA;
    buffer = (png_bytep) malloc(PNG_IMAGE_SIZE(image));
    if(buffer!=NULL &&
      png_image_finish_read(&image,
                            NULL /*background*/,
                            buffer,
                            0 /*row stride*/,
                            NULL /*colormap*/) != 0
      );
    {
       // the example code now uses a writing function to write it to another file
       //png_image_write_to_file(&image, fname, 0 /*convert_to_8bit*/, buffer, 0/*row_stride*/,NULL/*colormap*/)
       //I want to convert and translate the image into my struct

      //this function must make some sort of assumption about the contents of the buffer? I might have to try to blit output to see what?
      // fp = fopen("testbufferoutput.csv","w+");
      // //assuming RGBARGBARGBA
      // for(I=0;I<image.width;I++)
      // {
      //   for(J=0;J<image.height;J++)
      //   {
      //     bf2 = (unsigned char *)buffer;
      //     z = (I+J*image.width)*4;
      //     fprintf(fp,"%d,%d,%d,%d,",bf2[z+0],bf2[z+1],bf2[z+2],bf2[z+3]);
      //   }
      //   fprintf(fp,"\n");
      // }
      // fclose(fp);
      amscimglib4_image_resize(img,image.width,image.height);
      bf2 = (unsigned char *) buffer;
      for(I=0;I<image.width;I++)
      {
        for(J=0;J<image.height;J++)
        {
          z = (I+J*image.width)*4;
          R = bf2[z+0];
          G = bf2[z+1];
          B = bf2[z+2];
          A = bf2[z+3];
          amscimglib4_set_pixel_RGBA(img,I,J,R,G,B,A);
        }
      }

      //clean up
      png_image_free(&image);
      free(buffer);

    }

  }
  else
  {
    //something went wrong?
    printf("readimage_png: error: %s\n", image.message);
    amscimglib4_image_resize(img,0,0);
  }

  return;
}

AMSCIMGLIB4_API void amscimglib4_writeimage_png(const char *fname, amscimglib4_image *img)
{
  png_image image;

  unsigned char *bb2 = NULL;
  bb2 = (unsigned char*)malloc(img->sizex*img->sizey*4*sizeof(unsigned char));
  memcpy(bb2,img->data,img->sizex*img->sizey*4*sizeof(unsigned char));

  memset(&image,0,sizeof(image)); //fills memory with zeros
  image.version = PNG_IMAGE_VERSION;
  image.format = PNG_FORMAT_RGBA;
  image.width = img->sizex;
  image.height = img->sizey;

  png_image_write_to_file(&image, fname, 0 /*convert_to_8bit*/, (png_bytep) bb2, 0/*row_stride*/,NULL/*colormap*/);

  free(bb2); //weird - png-writer causes memory leaks if {I operate directly on img->bytebuf
  return;
}



//Nevermind, there is a int png_image_begin_read_from_memory(png_imagep image, png_const_voidp memory, size_t size)

// int png_image_write_to_memory (png_imagep image, void *memory,
//   png_alloc_size_t * PNG_RESTRICT memory_bytes,
//   int convert_to_8_bit, const void *buffer, ptrdiff_t row_stride,
//   const void *colormap));



//Reads and writes png data to and from a bytebuffer in memory (for compression)
AMSCIMGLIB4_API int amscimglib4_pngbytebuffer_to_img(amscimglib4_image *img, unsigned char *bytebuffer, long nbytes)
{
  int ret = amscimglib4_success;
  int res;
  png_image image; //control structure used by libpng
  unsigned char *imgbuffer = NULL;
  long z;
  unsigned char R,G,B,A;
  long I,J;
  

  if(img==NULL || bytebuffer==NULL || nbytes<8)
  {
    ret = amscimglib4_failure;
    return ret;
  }
  
  memset(&image,0,sizeof(image)); //fills memory with zeros
  image.version = PNG_IMAGE_VERSION;

  res = png_image_begin_read_from_memory(&image,bytebuffer,nbytes);
  if(res != 0)
  {
    image.format = PNG_FORMAT_RGBA; //force conversion to RGBA before calling finish read
    imgbuffer = (png_bytep) malloc(PNG_IMAGE_SIZE(image));
    if(imgbuffer==NULL)
    {
      printf("amscimglib4_pngbytebuffer_to_img:: imgbuffer failed to allocate.\n");
      ret = amscimglib4_failure;
      png_image_free(&image);
      return ret;
    }

    
    res = png_image_finish_read(
      &image, 
      NULL, //background
      imgbuffer, //buffer to fill
      0, //row stride
      NULL //colormap
    );

    if(res!=0 && imgbuffer!=NULL)
    {
      res = amscimglib4_image_resize(img,image.width,image.height);
      if(res==amscimglib4_success)
      {
        for(I=0;I<image.width;I++)
        {
          for(J=0;J<image.height;J++)
          {
            z = (I+J*image.width)*4;
            R = imgbuffer[z+0];
            G = imgbuffer[z+1];
            B = imgbuffer[z+2];
            A = imgbuffer[z+3];
            amscimglib4_set_pixel_RGBA(img,I,J,R,G,B,A);
          }
        }
      }
    }

    if(imgbuffer!=NULL) {free(imgbuffer); imgbuffer = NULL;}
  }

  png_image_free(&image);

  return ret;
}



AMSCIMGLIB4_API long amscimglib4_pngbytebufferlen(amscimglib4_image *img)
{
  long ret = 0;
  int res;
  png_alloc_size_t bufflen = 0;

  png_image pngimage;

  pngimage.version = PNG_IMAGE_VERSION;
  pngimage.format = PNG_FORMAT_RGBA;
  pngimage.width = img->sizex;
  pngimage.height = img->sizey;

  res = png_image_write_to_memory(
    &pngimage,
    NULL,     //memory
    &bufflen,    //buffer size required
    0,       //convert to 8 bit
    img->data,  //RGBA buffer
    0,          //row stride,
    NULL        //colormap
  );

  ret = bufflen;

  return ret;
}

// int png_image_write_to_memory (png_imagep image, void *memory,
//   png_alloc_size_t * PNG_RESTRICT memory_bytes,
//   int convert_to_8_bit, const void *buffer, ptrdiff_t row_stride,
//   const void *colormap));

AMSCIMGLIB4_API int amscimglib4_pngbytebuffer_from_img(amscimglib4_image *img, unsigned char *bytebuffer, long bufflen, long *nbyteswritten)
{
  int ret = amscimglib4_success;
  int res;

  png_image pngimage;
  png_alloc_size_t bufflen2 = bufflen;

  pngimage.version = PNG_IMAGE_VERSION;
  pngimage.format = PNG_FORMAT_RGBA;
  pngimage.width = img->sizex;
  pngimage.height = img->sizey;

  res = png_image_write_to_memory(
    &pngimage,
    bytebuffer,     //memory
    &bufflen2,    //buffer size
    0,       //convert to 8 bit
    img->data,  //RGBA buffer
    0,          //row stride,
    NULL        //colormap
  );

  if(nbyteswritten!=NULL) *nbyteswritten = bufflen2;


  return ret;
}


#ifdef __cplusplus
}; //end extern "C"
#endif



// Old Code //

// Structure to hold our buffer data
// typedef struct amscimglib4_png_buffer_data {
//   const unsigned char *data;
//   size_t size;
//   size_t offset;
// } amscimglib4_png_buffer_data;

// //fills data bytes with data from png_ptr
// void amscimglib4_pngbytes_readfn(
//   png_structp png_ptr,
//   png_bytep data,
//   png_size_t length
// )
// {
//   amscimglib4_png_buffer_data *buff = (amscimglib4_png_buffer_data*) png_get_io_ptr(png_ptr);

//   if(buff->offset + length > buff->size)
//   {
//     //not enough data in buffer;
//     return;
//   }

//   memcpy(data,buff->data + buff->offset, length);
//   buff->offset += length;
//   return;
// }

// //Reads and writes png data to and from a bytebuffer in memory (for compression)
// AMSCIMGLIB4_API int amscimglib4_pngbytebuffer_to_img(amscimglib4_image *img, unsigned char *bytebuffer, long nbytes)
// {
//   // Following tutorial https://pulsarengine.com/2009/01/reading-png-images-from-memory/

//   int ret = amscimglib4_success;

//   if(img==NULL || bytebuffer==NULL)
//   {
//     ret = amscimglib4_failure;
//     return ret;
//   }

//   if(nbytes<8)
//   {
//     ret = amscimglib4_failure;
//     return ret;
//   }

//   if(!png_check_sig(bytebuffer,8))
//   {
//     printf("amscimglib4_pngbytebuffer_to_img - bytes do not have PNG signature.\n");
//     ret = amscimglib4_failure;
//     return ret;
//   }

//   amscimglib4_png_buffer_data buffer2;
//   buffer2.data = bytebuffer;
//   buffer2.size = nbytes;
//   buffer2.offset = 0;

//   // get PNG file info struct (memory is allocated by libpng)
//   png_structp png_ptr = NULL;
//   png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

//   if(png_ptr == NULL)
//   {
//     printf("amscimglib4_pngbytebuffer_to_img - png pointer failed to allocate.\n");
//     ret = amscimglib4_failure;
//     return ret;
//   }

//   // get PNG image data info struct (memory is allocated by libpng)
//   png_infop info_ptr = NULL;
//   info_ptr = png_create_info_struct(png_ptr);

//   if(info_ptr == NULL)
//   {
//     // libpng must free file info struct memory before we bail
//     png_destroy_read_struct(&png_ptr, NULL, NULL);
//     ret = amscimglib4_failure;
//     return ret;
//   }

//   //This is where the magic happens. Now we must tell libpng to use a our own custom routine for retrieving the image data. To do this, we use the png_set_read_fn() routine:
//   //png_set_read_fn(png_ptr, &inputStream, ReadDataFromInputStream);

//   /*
//   http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-5
//   Input/Output in libpng is done through png_read() and png_write(), which currently just call fread() and fwrite(). The FILE * is stored in png_struct and is initialized via png_init_io(). If you wish to change the method of I/O, the library supplies callbacks that you can set through the function png_set_read_fn() and png_set_write_fn() at run time, instead of calling the png_init_io() function. These functions also provide a void pointer that can be retrieved via the function png_get_io_ptr(). For example:

//     png_set_read_fn(png_structp read_ptr,
//         voidp read_io_ptr, png_rw_ptr read_data_fn)

//   The replacement I/O functions must have prototypes as follows:

//     void user_read_data(png_structp png_ptr,
//         png_bytep data, png_size_t length);
//     void user_write_data(png_structp png_ptr,
//         png_bytep data, png_size_t length);
//     void user_flush_data(png_structp png_ptr);
//   */

//   png_set_read_fn(png_ptr, &buffer2, &amscimglib4_pngbytes_readfn);

//   png_read_info(png_ptr, info_ptr);
//   int w,h,bit_depth,color_type;

//   w = png_get_image_width(png_ptr, info_ptr);
//   h = png_get_image_height(png_ptr, info_ptr);
//   bit_depth = png_get_bit_depth(png_ptr, info_ptr);
//   color_type = png_get_color_type(png_ptr, info_ptr);




//   return ret;
// }