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

#define PNG_USE_MNG	1
#define PNG_SKIP_SIG	2

static const png_byte mng_signature[8] = {138, 'M', 78, 71, 13, 10, 26, 10};

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);
    const png_byte *s = chunk->data;
    if(Img!=NULL && s!=NULL)
    {
      PropertyItem *PropExif = new PropertyItem("Exif",chunk->size);
      if(PropExif->Data==NULL) {delete PropExif; return 0;}
      unsigned char *p = (unsigned char *)PropExif->Data;
      for(uint32_t ptr=0; ptr<chunk->size; ptr++)
        *p++ = *s++;
      Img->AttachProperty(PropExif);
    }
    return 1;
  }

	// recognized orNT
  if(chunk->name[0] == 111 &&
     chunk->name[1] == 114 &&
     chunk->name[2] ==  78 &&
     chunk->name[3] ==  84)
  {
    if (chunk->size != 1) return(-1);			/* Error return */
    Image *Img = (Image *)png_get_user_chunk_ptr(ping);
    const png_byte *s = chunk->data;
    if(Img!=NULL && s!=NULL && s[0]<9)
    {
      PropertyItem *PropOrientation = new PropertyItem("orientation",1);
      *(unsigned char *)PropOrientation->Data = s[0];
      Img->AttachProperty(PropOrientation);
    }
    return(1);
  }

#if defined(PNG_MNG_FEATURES_SUPPORTED)
  if(chunk->name[0] == 'M' &&
     chunk->name[1] == 'H' &&
     chunk->name[2] == 'D' &&
     chunk->name[3] == 'R')
  {
    return 1;		// This ensures to skip a chunk
  }
#endif

  return 0;
}


void LoadFragmentPNG(FILE *F, Image *Img, unsigned char EnableMng)
{
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
  {
#if defined(PNG_MNG_FEATURES_SUPPORTED)
    if(EnableMng & PNG_USE_MNG)
        png_permit_mng_features(png_ptr,PNG_ALL_MNG_FEATURES);
#endif
    if(EnableMng & PNG_SKIP_SIG)
        png_set_sig_bytes(png_ptr,8);

    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);	// TODO: Dopsat - PNG_COLOR_TYPE_PALETTE; PNG_COLOR_TYPE_RGB_ALPHA ....

    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

    //printf("\nPNG: channels: %d", png_get_channels(png_ptr,info_ptr));
    switch(png_get_channels(png_ptr,info_ptr))
    {
      case 1:
            //printf("\nPNG: png_get_bit_depth: %d", png_get_bit_depth(png_ptr,info_ptr));
            //printf("\nPNG: png_get_rowbytes():%d %u", 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);
            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),
		                    NearAvailPlanes(png_get_bit_depth(png_ptr,info_ptr)));

            palette = NULL;		// Prevent uninitialised value.
            if(PNG_INFO_PLTE==png_get_PLTE(png_ptr,info_ptr, &palette, &num_palette))
            {
              //printf("\nPNG: num_palette: %d", num_palette);
              if(num_palette>0 && palette!=NULL)
              {
                if(Raster!=NULL && num_palette>(1<<Raster->GetPlanes()))
                    num_palette = 1<<Raster->GetPlanes();		// Shrink oversized palette.
                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
    if(fread(header, 1, 8, f)<8) goto FINISH;
    if(png_sig_cmp(header, 0, 8)) goto FINISH;
  }

  LoadFragmentPNG(f,&Img,PNG_SKIP_SIG);

FINISH:
  fclose(f);

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


#if defined(PNG_MNG_FEATURES_SUPPORTED)

Image LoadPictureMNG(const char *Name)
{
Image Img;
Image *CurImg = &Img;
FILE *f;
unsigned char EnableMNG = PNG_SKIP_SIG;
png_byte header[8];			// 8 is the maximum size that can be checked
long ChunkStart;

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

  if(fread(header, 1, 8, f)<8) goto FINISH;
  if(!png_sig_cmp(header, 0, 8)) goto FINISH;
  if(memcmp(mng_signature,header,8)!=0) goto FINISH;

  ChunkStart = 8;
  while(!feof(f))
  {
    if(fread(header, 1, 8, f)<8) goto FINISH;
    uint32_t ChunkSize = LD_UINT32_HI(header);
    ChunkStart += ChunkSize + 8 + 4;
    if(!memcmp(header+4,"IHDR",4))
    {
      if(CurImg->Raster!=NULL)
      {
        CurImg->Next = new Image;
	if(CurImg->Next==NULL) goto FINISH;
        CurImg = CurImg->Next;
      }
      fseek(f,-8,SEEK_CUR);
      LoadFragmentPNG(f, CurImg, PNG_SKIP_SIG|PNG_USE_MNG);
      const long FilePos = ftell(f);
      if(FilePos >= ChunkStart)
          ChunkStart = FilePos;		// no need to seek
      else
          fseek(f,ChunkStart,SEEK_SET);
    }
    else
      fseek(f,ChunkStart,SEEK_SET);
  }

FINISH:
  fclose(f);

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


	// Auxiliary handling of MNG files.
 TImageFileHandler HANDLER_MNG(".MNG", LoadPictureMNG, NULL, NULL, FILE_FORMAT_CAP_EXIF);

#endif		// PNG_MNG_FEATURES_SUPPORTED


#endif


#if SupportPNG>=3

int SavePicturePNG(const char *Name, const Image &Img)
{
  if(Img.Raster==NULL) return ErrMissingRaster;

	/* 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:
              switch(Img.Raster->Channels())
              {
                case 3: 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);
                                sig_bit.alpha = 0;
                                sig_bit.blue = sig_bit.green = sig_bit.red = Img.Raster->GetPlanes()/3;
                        break;
                case 4: png_set_IHDR(png_ptr, info_ptr, Img.Raster->Size1D, Img.Raster->Size2D,
		                Img.Raster->GetPlanes()/4, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
		                PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
			        sig_bit.blue = sig_bit.green = sig_bit.red = sig_bit.alpha = Img.Raster->GetPlanes()/4;
                        break;
                default:fclose(fp);		// Unsupported channel count.
                        png_destroy_write_struct(&png_ptr, &info_ptr);
                        return -8;
              }
		   /* Otherwise, if we are dealing with a color image then */
              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:
              {
                //printf("\npng_set_IHDR: %d", Img.Raster->GetPlanes());
                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 */
                int num_palette = 1 << Img.Raster->GetPlanes();
		if(num_palette>PNG_MAX_PALETTE_LENGTH) num_palette=PNG_MAX_PALETTE_LENGTH;
                //printf("\npng_malloc: %ld", (long)num_palette*png_sizeof(png_color));
	        png_colorp palette = (png_colorp)png_malloc(png_ptr, num_palette*png_sizeof(png_color));
		if(palette != NULL)
		{	   /* ... Set palette colors ... */
                  for(int i=0; i<num_palette; i++)
	          {
		    palette[i].red = Img.Palette->R(i);
		    palette[i].green = Img.Palette->G(i);
		    palette[i].blue = Img.Palette->B(i);
                  }
                  //printf("\npng_set_PLTE: %d", num_palette);
	          png_set_PLTE(png_ptr, info_ptr, palette, num_palette);
		  /* 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

