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

#include "imgsupp.h"


#if defined(SupportWEBP) && SupportWEBP>0

#ifdef _MSC_VER
#define SUPPORT_WEBP_MUX
#endif	// _MSC_VER


#include <webp/decode.h>
#ifdef SUPPORT_WEBP_MUX
  #include <webp/mux.h>
#endif

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

#define DEFAULT_WEBP_QUALITY	78


#if WEBP_DECODER_ABI_VERSION < 0x0208
// Public API function 0x0201 does not have this, I am sure that 0x0208 has this function.
inline void WebPFree(void* ptr) 
{
  free(ptr);
}
#endif


Image LoadPictureWEBP(const char *Name)
{
Image Img;
VP8StatusCode webp_status;
size_t sz;
FILE *f;
unsigned char *blob;
WebPBitstreamFeatures stream_features;
unsigned char *pixels;
unsigned y, rowsize;

  if(Name==NULL || (f=fopen(Name,"rb"))==NULL) return(Img);
  sz = FileSize(f);
  if(sz<=16) goto FINISH;
  blob = (unsigned char*)malloc(16);
  if(blob==NULL) goto FINISH;
  if(fread(blob,1,16,f)!=16) goto FINISH_B;
  
  if(strncmp((char*)blob,"RIFF",4)) goto FINISH_B;

  pixels = (unsigned char*)realloc(blob,sz);
  if(pixels==NULL) goto FINISH_B;
  blob = pixels;
  if(fread(blob+16,1,sz-16,f)!=sz-16) goto FINISH_B;
  fclose(f);

  webp_status = WebPGetFeatures((uint8_t*)blob,sz,&stream_features);
  if(webp_status != VP8_STATUS_OK) goto FINISH2;

  Img.AttachRaster(stream_features.has_alpha ? 
                   CreateRaster2DRGBA(stream_features.width,stream_features.height,8) :
                   CreateRaster2DRGB(stream_features.width,stream_features.height,8));
  if(Img.Raster==NULL || Img.Raster->Size1D==0 || Img.Raster->Size2D==0) goto FINISH2;

  rowsize = Img.Raster->Size1D * Img.Raster->Channels();

  if(stream_features.has_alpha)
    pixels = WebPDecodeRGBA(blob,sz,&stream_features.width,&stream_features.height);
  else
    pixels = WebPDecodeRGB(blob,sz,&stream_features.width,&stream_features.height);
  if(pixels == NULL) {Img.AttachRaster(NULL); goto FINISH2;}

  for(y=0; y<Img.Raster->Size2D; y++)
  {
    memcpy(Img.Raster->GetRow(y), pixels+y*rowsize, rowsize);
  }
  WebPFree(pixels);

  /* Read features out of the WebP container https://developers.google.com/speed/webp/docs/container-api */
#if defined(SUPPORT_WEBP_MUX)
  {
    uint32_t webp_flags=0;
    WebPData flag_data;
    WebPData content={blob,sz};
    WebPMuxError mux_error;

    WebPMux *mux = WebPMuxCreate(&content,0);
    (void) memset(&flag_data,0,sizeof(flag_data));
    WebPMuxGetFeatures(mux,&webp_flags);

    if((webp_flags & ICCP_FLAG) && ((mux_error=WebPMuxGetChunk(mux,"ICCP",&flag_data)) == WEBP_MUX_OK))
    {
      if((flag_data.bytes != NULL) && (flag_data.size > 0))
      {
        PropertyItem *PropICC = new PropertyItem("ICC",flag_data.size);
        if(PropICC->Data)
        {
          memcpy(PropICC->Data, flag_data.bytes, PropICC->DataSize);
          Img.AttachProperty(PropICC);
        }
        else
            delete PropICC;
      }
    }

    if((webp_flags & EXIF_FLAG) && ((mux_error=WebPMuxGetChunk(mux,"EXIF",&flag_data))==WEBP_MUX_OK))
    {
      if(flag_data.bytes!=NULL && flag_data.size>sizeof(ExifPreffix))
      {
        PropertyItem *PropExif;
        if(!memcmp(ExifPreffix,flag_data.bytes,sizeof(ExifPreffix)))
        {
          PropExif = new PropertyItem(ExifPreffix,flag_data.size-sizeof(ExifPreffix));
          if(PropExif->Data)
            memcpy(PropExif->Data, flag_data.bytes+sizeof(ExifPreffix), PropExif->DataSize);
        }
        else
        {
          PropExif = new PropertyItem(ExifPreffix,flag_data.size);
          if(PropExif->Data)
            memcpy(PropExif->Data, flag_data.bytes, PropExif->DataSize);
        }
        Img.AttachProperty(PropExif);
      }
    }

    if((webp_flags & XMP_FLAG) && ((mux_error=WebPMuxGetChunk(mux,"XMP",&flag_data)) == WEBP_MUX_OK))
    {
      if((flag_data.bytes != NULL) && (flag_data.size > 0))
      {        
        PropertyItem *PropXMP = new PropertyItem("XMP",flag_data.size);
        if(PropXMP->Data)
        {
          memcpy(PropXMP->Data, flag_data.bytes, PropXMP->DataSize);
          Img.AttachProperty(PropXMP);
        }
        else
            delete PropXMP;
      }
    }

    WebPMuxDelete(mux);
  }
#endif /* defined(SUPPORT_WEBP_MUX) */


FINISH2:
  free(blob);
  return(Img);

FINISH_B:
  free(blob);
FINISH:
  fclose(f);
  return(Img);		/* And we're done! */
}
#endif


#if SupportWEBP>=3

#include "webp/encode.h"


int WebPFileWriter(const uint8_t* data, size_t data_size, const WebPPicture* picture)
{
  if(picture==NULL || picture->custom_ptr==NULL) return 0;  
  return fwrite(data,1,data_size,(FILE*)picture->custom_ptr);
}


int SavePictureWEBP(const char *Name, const Image &Img)
{
WebPConfig config;
const PropertyItem *Prop;
unsigned y;
WebPPicture pic;
unsigned char *Pix;
int webp_status;
Raster1DAbstract *RasConv = NULL;

  if(Img.Raster==NULL) return ErrEmptyRaster;

  Prop = Img.Properties.Find("QUALITY",y);
  if(Prop && Prop->DataSize>=sizeof(int))
    y = *(int*)Prop->Data;
  else
    y = DEFAULT_WEBP_QUALITY;
  if(!WebPConfigPreset(&config, WEBP_PRESET_PHOTO, y)) return -1;   // version error

  if(!WebPPictureInit(&pic)) return -50;  // version error
  pic.width = Img.Raster->Size1D;
  pic.height = Img.Raster->Size2D;
  if(!WebPPictureAlloc(&pic)) return -15;   // memory error

  switch(Img.Raster->Channels())
  {
    default:
    case 3:
    case 1: RasConv = CreateRaster1DRGB(0,8);
	    RasConv->Size1D = Img.Raster->Size1D;
            break;
    case 4: RasConv = CreateRaster1DRGBA(0,8);
            RasConv->Size1D = Img.Raster->Size1D;
            break;
  }

  if(RasConv)
  {
    Pix = (unsigned char*)malloc(Img.Raster->Size1D*Img.Raster->Size2D*((Img.Raster->Channels()<=3)?3:4));
    if(Pix)
    {
      for(y=0; y<Img.Raster->Size2D; y++)
      {
        RasConv->Data1D = Pix+3*y*Img.Raster->Size1D;
        RasConv->Set(*Img.Raster->GetRowRaster(y));
      }      

      if(RasConv->Channels() <= 3)
          webp_status = WebPPictureImportRGB(&pic,Pix,3*Img.Raster->Size1D);
      else
          webp_status = WebPPictureImportRGBA(&pic,Pix,4*Img.Raster->Size1D);
      free(Pix);
    }
    RasConv->Data1D=NULL;
    delete RasConv;
  }

	/* create file */
  FILE *fp = fopen(Name, "wb");
  if(!fp) {WebPPictureFree(&pic);return ErrOpenFile;}

#if defined(SUPPORT_WEBP_MUX)
  WebPMemoryWriter writer;
  WebPMemoryWriterInit(&writer);
  if(!Img.Properties.isEmpty())
    for(int i=0; i<Img.Properties.PropCount; i++)
      {
        if(Img.Properties.pProperties[i] != NULL)
        {
          if(!strcmp(Img.Properties.pProperties[i]->Name,ExifPreffix) ||
	     !strcmp(Img.Properties.pProperties[i]->Name,"ICC") ||
             !strcmp(Img.Properties.pProperties[i]->Name,"XMP"))
          { 
            pic.writer = WebPMemoryWrite;
            pic.custom_ptr = &writer;
            break;
          }
        }
      }
#endif
  if(pic.writer!=WebPMemoryWrite)
  {
    pic.writer = WebPFileWriter;
    pic.custom_ptr = fp;
  }

  webp_status = WebPEncode(&config, &pic);
  WebPPictureFree(&pic);   // Always free the memory associated with the input.

#if defined(SUPPORT_WEBP_MUX)

  if(writer.size>0 && writer.mem!=NULL)
  {
    WebPMuxError mux_error;
    WebPMux *mux = WebPMuxNew();
    WebPData encoded_image = {writer.mem,writer.size};

    WebPMuxSetImage(mux,&encoded_image,1);

    for(int i=0; i<Img.Properties.PropCount; i++)
    {
      if(Img.Properties.pProperties[i] != NULL)
      {
        const char *ChunkName = NULL;
        if(!strcmp(Img.Properties.pProperties[i]->Name,ExifPreffix)) ChunkName="EXIF";
        else if(!strcmp(Img.Properties.pProperties[i]->Name,"ICC")) ChunkName="ICC";
        else if(!strcmp(Img.Properties.pProperties[i]->Name,"XMP")) ChunkName="XMP";           

        if(ChunkName)
        {
          WebPData chunk;
          chunk.bytes = (uint8_t*)Img.Properties.pProperties[i]->Data;
          chunk.size = Img.Properties.pProperties[i]->DataSize;
          mux_error = WebPMuxSetChunk(mux,ChunkName,&chunk,0);
        }
      }
    }

      // Create the new container
    WebPData picture_profiles = {writer.mem, writer.size};
    mux_error = WebPMuxAssemble(mux,&picture_profiles);

        /* Replace the original data with the container data */
    WebPMemoryWriterClear(&writer);
    writer.size = picture_profiles.size;
    writer.mem = (unsigned char *)picture_profiles.bytes;
        
    WebPMuxDelete(mux);						// Cleanup the muxer.
    fwrite(writer.mem,1,writer.size,fp);
  }
  WebPMemoryWriterClear(&writer);

#endif

  fclose(fp);
  return (webp_status==1) ? 0 : -12;
}
#endif

