/******************************************************************************
 * program:     rasimg library                                                *
 * function:    Module for PNG support                                        *
 * modul:       ras_png.cc                                                    *
 * licency:     GPL or LGPL                                                   *
 ******************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "typedfs.h"
#include "image.h"
#include "struct.h"

#include "imgsupp.h"


#if defined(SupportPNG) && SupportPNG>0

#define PNG_UNKNOWN_CHUNKS_SUPPORTED


#include <png.h>
#include <zlib.h>
#ifndef png_infopp_NULL
 #define png_infopp_NULL	NULL
#endif
static png_byte const mng_eXIf[5]={101,  88,  73, 102, '\0'};


#if (PNG_LIBPNG_VER_MAJOR>1) || (PNG_LIBPNG_VER_MAJOR==1 && PNG_LIBPNG_VER_MINOR>=5)
 #define USE_PNG1_5
#endif


#if SupportPNG>=4 || SupportPNG==2

static int read_user_chunk_callback(png_struct *ping, png_unknown_chunkp chunk)
{
		/* process eXIf or exIf chunk */
  if(chunk->name[0]  == 101 &&
     (chunk->name[1] ==  88 || chunk->name[1] == 120 ) &&
     chunk->name[2]  ==  73 &&
     chunk->name[3]  == 102)
  {
    Image *Img = (Image *)png_get_user_chunk_ptr(ping);
    png_byte *s = chunk->data;
    if(Img!=NULL && s!=NULL)
    {
      PropertyItem *PropExif = new PropertyItem;
      PropExif->Data = malloc(chunk->size);
      if(PropExif->Data==NULL) {delete PropExif; return 0;}
      PropExif->DataSize = chunk->size;
      unsigned char *p = (unsigned char *)PropExif->Data;
      for(uint32_t ptr=0; ptr<chunk->size; ptr++)
        *p++ = *s++;
      PropExif->Name = "Exif";
      Img->AttachProperty(PropExif);
    }
  }
  return 0;
}


/// TODO: This function is not finished.
void LoadFragmentPNG(FILE *F, Image *Img)
{
Raster2DAbstract *Raster = NULL;
APalette *Palette = NULL;
png_structp png_ptr;
png_infop info_ptr;
png_colorp palette;
int num_palette;
int i;

  if(F==NULL) return;

  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if(!png_ptr) return;

  info_ptr = png_create_info_struct(png_ptr);
  if(!info_ptr) goto FINISH;	// [read_png_file] png_create_info_struct failed

  if(!setjmp(png_jmpbuf(png_ptr)))  // if(setjmp ..)goto FINISH;	// [read_png_file] Error during init_io
  {
    png_init_io(png_ptr, F);

#if defined(PNG_UNKNOWN_CHUNKS_SUPPORTED)
    //png_set_keep_unknown_chunks(png_ptr, 2, (png_bytep)mng_eXIf, 1);
    png_set_read_user_chunk_fn(png_ptr, Img, read_user_chunk_callback);
#endif

    png_read_info(png_ptr, info_ptr);

    //color_type = info_ptr->color_type;
    png_byte color_type = png_get_color_type(png_ptr,info_ptr);

    int number_of_passes = png_set_interlace_handling(png_ptr);
    png_read_update_info(png_ptr, info_ptr);

	/* read file */
    if(setjmp(png_jmpbuf(png_ptr))) goto FINISH;	  // [read_png_file] Error during read_image

    switch(png_get_channels(png_ptr,info_ptr))
    {
      case 1: if(png_get_rowbytes(png_ptr,info_ptr) > (png_get_image_width(png_ptr,info_ptr)*png_get_bit_depth(png_ptr,info_ptr)+7)/8) goto FINISH;
	    Raster = CreateRaster2D(png_get_image_width(png_ptr,info_ptr),png_get_image_height(png_ptr,info_ptr),png_get_bit_depth(png_ptr,info_ptr));

            png_get_PLTE(png_ptr,info_ptr, &palette, &num_palette);
            if(num_palette>0 && palette!=NULL)
              {
              Palette = BuildPalette(num_palette,8);
	      if(Palette)
                {
                for(i=0;i<num_palette;i++)
		  {
		  Palette->setR(i,palette[i].red);
		  Palette->setG(i,palette[i].green);
		  Palette->setB(i,palette[i].blue);
                  }
		}
              }
	    break;
      case 3: if(png_get_rowbytes(png_ptr,info_ptr) > (3*png_get_image_width(png_ptr,info_ptr)*png_get_bit_depth(png_ptr,info_ptr))/8) goto FINISH;
	    Raster = CreateRaster2DRGB(png_get_image_width(png_ptr,info_ptr),png_get_image_height(png_ptr,info_ptr),png_get_bit_depth(png_ptr,info_ptr));
	    break;
      case 4: if(png_get_rowbytes(png_ptr,info_ptr) > (4*png_get_image_width(png_ptr,info_ptr)*png_get_bit_depth(png_ptr,info_ptr))/8) goto FINISH;
	    Raster = CreateRaster2DRGBA(png_get_image_width(png_ptr,info_ptr),png_get_image_height(png_ptr,info_ptr),png_get_bit_depth(png_ptr,info_ptr));
	    break;
    }
    if(Raster==NULL) goto FINISH;

    png_read_image(png_ptr, (png_byte **)Raster->Data2D);

    png_read_end(png_ptr, info_ptr);
  }
FINISH:
	/* Clean up after the read, and free any memory allocated - REQUIRED */
  if(png_ptr)
      png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);

  Img->AttachRaster(Raster);
  Img->AttachPalette(Palette);
}


Image LoadPicturePNG(const char *Name)
{
Image Img;
FILE *f;

  if((f=fopen(Name,"rb"))==NULL) return(Img);

  {
    png_byte header[8];			// 8 is the maximum size that can be checked
    fread(header, 1, 8, f);
    if(png_sig_cmp(header, 0, 8)) goto FINISH;
  }

  LoadFragmentPNG(f,&Img);

FINISH:
  fclose(f);

  return(Img);		/* And we're done! */
}
#endif


#if SupportPNG>=3

int SavePicturePNG(const char *Name, const Image &Img)
{
png_colorp palette = NULL;

  if(Img.Raster==NULL) return(-10);

	/* create file */
  FILE *fp = fopen(Name, "wb");
  if(!fp) return -1;
  //	abort_("[write_png_file] File %s could not be opened for writing", file_name);

  /* Create and initialize the png_struct with the desired error handler
    * functions.  If you want to use the default stderr and longjump method,
    * you can supply NULL for the last three parameters.  We also check that
    * the library version is compatible with the one used at compile time,
    * in case we are using dynamically linked libraries.  REQUIRED.  */
  png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

  if(png_ptr == NULL)
  {
     fclose(fp);
     return -2;
  }

	/* Allocate/initialize the image information data.  REQUIRED */
  png_infop info_ptr = png_create_info_struct(png_ptr);
  if(info_ptr == NULL)
  {
    fclose(fp);
    png_destroy_write_struct(&png_ptr,  png_infopp_NULL);
    return -3;
  }

   /* Set error handling.  REQUIRED if you aren't supplying your own
    * error handling functions in the png_create_write_struct() call. */
  if(setjmp(png_jmpbuf(png_ptr)))
  {	/* If we get here, we had a problem writing the file */
    fclose(fp);
    png_destroy_write_struct(&png_ptr, &info_ptr);
    return -4;
  }
  
	/* Set up the output control if you are using standard C streams */
  png_init_io(png_ptr, fp);

   /* Optional significant bit (sBIT) chunk */
  png_color_8 sig_bit;

   /* Set the image information here.  Width and height are up to 2^31,
    * bit_depth is one of 1, 2, 4, 8, or 16, but valid values also depend on
    * the color_type selected. color_type is one of PNG_COLOR_TYPE_GRAY,
    * PNG_COLOR_TYPE_GRAY_ALPHA, PNG_COLOR_TYPE_PALETTE, PNG_COLOR_TYPE_RGB,
    * or PNG_COLOR_TYPE_RGB_ALPHA.  interlace is either PNG_INTERLACE_NONE or
    * PNG_INTERLACE_ADAM7, and the compression_type and filter_type MUST
    * currently be PNG_COMPRESSION_TYPE_BASE and PNG_FILTER_TYPE_BASE. REQUIRED */
  IMAGE_TYPE Ityp = Img.ImageType();
  switch(Ityp)
  {
    case ImageTrueColor:
              png_set_IHDR(png_ptr, info_ptr, Img.Raster->Size1D, Img.Raster->Size2D,
		     Img.Raster->GetPlanes()/3, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
		     PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
		   /* Otherwise, if we are dealing with a color image then */
              sig_bit.blue = 
                  sig_bit.green = 
	          sig_bit.red = Img.Raster->GetPlanes() / 3;
              png_set_sBIT(png_ptr, info_ptr, &sig_bit);
              break;
    case ImageGray:
              png_set_IHDR(png_ptr, info_ptr, Img.Raster->Size1D, Img.Raster->Size2D,
		     Img.Raster->GetPlanes(), PNG_COLOR_TYPE_GRAY, PNG_INTERLACE_NONE,
		     PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
			/* If we are dealing with a grayscale image then */
	      sig_bit.gray = Img.Raster->GetPlanes();
              png_set_sBIT(png_ptr, info_ptr, &sig_bit);
              break;
    case ImagePalette:
              png_set_IHDR(png_ptr, info_ptr, Img.Raster->Size1D, Img.Raster->Size2D,
		     Img.Raster->GetPlanes(), PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
		     PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
		   /* Set the palette if there is one.  REQUIRED for indexed-color images */
	      palette = (png_colorp)png_malloc(png_ptr, PNG_MAX_PALETTE_LENGTH*png_sizeof(png_color));
		   /* ... Set palette colors ... */
	      png_set_PLTE(png_ptr, info_ptr, palette, PNG_MAX_PALETTE_LENGTH);
		  /* You must not free palette here, because png_set_PLTE only makes a link to
		   * the palette that you malloced.  Wait until you are about to destroy
		   * the png structure.  */		  
              break;

    default:  fclose(fp);
              png_destroy_write_struct(&png_ptr, &info_ptr);
              return -5;
  }

	/* Write the file header information.  REQUIRED */
  png_write_info(png_ptr, info_ptr);

	/* write bytes */
  if(setjmp(png_jmpbuf(png_ptr)))
  {
    //abort_("[write_png_file] Error during writing bytes");
    fclose(fp);
    png_destroy_write_struct(&png_ptr,  png_infopp_NULL);
    return -6;
  }

  png_write_image(png_ptr, (png_bytepp)Img.Raster->Data2D);

	/* end write */
  if(setjmp(png_jmpbuf(png_ptr)))
  {
    //	abort_("[write_png_file] Error during end of write");
    fclose(fp);
    png_destroy_write_struct(&png_ptr,  png_infopp_NULL);
    return -7;
  }

  if(!Img.Properties.isEmpty())
  {
    for(int i=0; i<Img.Properties.PropCount; i++)
    {
      PropertyItem *Prop = Img.Properties.pProperties[i];
      if(Prop!=NULL && Prop->Data!=NULL && Prop->DataSize>0 && Prop->Name=="Exif")
      {
        WrDWORD_HiEnd(Prop->DataSize,fp);
        fwrite(mng_eXIf,1,4,fp);
        fwrite(Prop->Data,1,Prop->DataSize,fp);
        const uint32_t CRC = crc32(crc32(0,mng_eXIf,4), (const Bytef*)(Prop->Data), (uint32_t)Prop->DataSize);
        WrDWORD_HiEnd(CRC,fp);
      }
    }
  }

  png_write_end(png_ptr, NULL);

  fclose(fp);
  return 0;
}
#endif


#endif

