//
// Copyright 2003 Sony Corporation 
//
// Permission to use, copy, modify, and redistribute this software for
// non-commercial use is hereby granted.
//
// This software is provided "as is" without warranty of any kind,
// either expressed or implied, including but not limited to the
// implied warranties of fitness for a particular purpose.
//

#include <OPENR/OSyslog.h>
#include <OPENR/OFbkImage.h>
#include "JPEGEncoder.h"
#include "write_jpeg.h"
#include <OPENR/ODebug.h>
#include <OPENR/OPENRAPI.h>
/** Gray*/
const byte JPEGEncoder::CDTMixedColor[]={ 128,128,128};
/** Colori utilizzati per colorare i CDT channel*/
const byte JPEGEncoder::CDTChannelColor[][3]={
  {0,128,128},     // no color detected = black
  {149,22,48},     // channel 0 = light green
  {29,108,255},    // channel 1 = blue
  {105, 234,218},  // channel 2 = pink
  {76,255,86},     // channel 3 = red
  {255, 128, 128}, // channel 4 = white
  {255,149,1},     // channel 5 = yellow
  {75,75,86},      // channel 6 = dark green 
  {140,185,49},    // channel 7 = orange
};
JPEGEncoder::JPEGEncoder() : ipstackRef(),
                             pixelInterleavedYCbCr(0),
                             freeJpegBufList()
{

  initCDTMatrix();
  printMatrix();
}


void JPEGEncoder::initCDTMatrix(){
  CDTColorMatrix[0][0]=CDTChannelColor[0][0];
  CDTColorMatrix[0][1]=CDTChannelColor[0][1];
  CDTColorMatrix[0][2]=CDTChannelColor[0][2];

  int actualColor=1;

  for(int i=1; i< 256; i++)
    {
    if(i & actualColor)
      {
	CDTColorMatrix[i][0]=CDTChannelColor[i][0];
	CDTColorMatrix[i][1]=CDTChannelColor[i][1];
	CDTColorMatrix[i][2]=CDTChannelColor[i][2];
	actualColor*=2;
      }
    else
      {
	CDTColorMatrix[i][0]=CDTMixedColor[0];
	CDTColorMatrix[i][1]=CDTMixedColor[1];
	CDTColorMatrix[i][2]=CDTMixedColor[2];
      }     
    }
  
}

void JPEGEncoder::printMatrix(){
  OSYSDEBUG(("MATRICE DI CONVERSIONE DEI COLORI\n"));
  for(int i=0; i< 256; i++)
    OSYSDEBUG(("%d y=%3d cr=%3d cb=%3d\n", i, CDTColorMatrix[i][0], CDTColorMatrix[i][1], CDTColorMatrix[i][2]));
  OSYSDEBUG(("----------------------------------\n"));
    
}

bool JPEGEncoder::Init(const antStackRef& ipstack)
{
    ipstackRef = ipstack;

    pixelInterleavedYCbCr = (byte*)malloc(IMAGE_BUFSIZE);
    if (pixelInterleavedYCbCr == 0) {
        OSYSLOG1((osyslogERROR, "JPEGEncoder::Init() : NO MEMORY"));
        return false;
    }

    for (int i = 0; i < NUM_JPEG_BUF; i++) {

        antEnvCreateSharedBufferMsg jpegBufferMsg(JPEG_BUFSIZE);
        
        jpegBufferMsg.Call(ipstackRef, sizeof(jpegBufferMsg));
        if (jpegBufferMsg.error != ANT_SUCCESS) {
            OSYSLOG1((osyslogERROR, "%s : NO MEMORY antError %d",
                      "JPEGEncoder::Init()", jpegBufferMsg.error));
            return false;
        }

        jpegBuf[i] = jpegBufferMsg.buffer;
        jpegBuf[i].Map();
        freeJpegBufList.push_back((byte*)jpegBuf[i].GetAddress());
    }

    return true;
}

void
JPEGEncoder::FreeJPEG(byte* jpeg)
{
    Free(jpeg);
}

bool
JPEGEncoder::GetJPEG(OFbkImageVectorData* imageVec,
                     OFbkImageLayer layer, bool reconstruction,
                     int quality, byte** jpeg, int* size)
{
    int w, h;

    if(layer == ofbkimageBAND_CDT)
      {
	ReconstructAndConvertCtd(imageVec, layer,
				 pixelInterleavedYCbCr, &w, &h);
      }
    else
      {
	if (reconstruction == true) 
	  {
	    ReconstructAndConvertYCbCr(imageVec, layer,
				       pixelInterleavedYCbCr, &w, &h);
	  } 
	else 
	  {
	    ConvertYCbCr(imageVec, layer, pixelInterleavedYCbCr, &w, &h);
	  }
      }

    byte* jbuf = Allocate();
    if (jbuf == 0) return false;

    int jsize = write_jpeg_mem(pixelInterleavedYCbCr,
                               w, h, quality, jbuf, JPEG_BUFSIZE);
    *jpeg = jbuf;
    *size = jsize;

    return true;
}

void
JPEGEncoder::Save(OFbkImageVectorData* imageVec,
                  OFbkImageLayer layer, int quality, char* path)
{
    int w, h;
    ConvertYCbCr(imageVec, layer, pixelInterleavedYCbCr, &w, &h);

    FILE* fp = fopen(path, "w");
    if (fp == NULL) {
        OSYSLOG1((osyslogERROR, "JPEGEncoder::Save() : can't open %s", path));
        return;
    }
    write_jpeg_file(pixelInterleavedYCbCr, w, h, quality, fp);
    fclose(fp);
}

byte*
JPEGEncoder::Allocate()
{
    if (freeJpegBufList.size() == 0) return 0;

    byte* buf = freeJpegBufList.front();
    freeJpegBufList.pop_front();
    return buf;
}

void
JPEGEncoder::Free(byte* buf)
{
    freeJpegBufList.push_back(buf);
}

void
JPEGEncoder::ConvertYCbCr(OFbkImageVectorData* imageVec,
                          OFbkImageLayer layer, byte* image,
                          int* width, int* height)
{
    OFbkImageInfo* info = imageVec->GetInfo(layer);
    byte*          data = imageVec->GetData(layer);

    OFbkImage yImg(info, data, ofbkimageBAND_Y);
    OFbkImage crImg(info, data, ofbkimageBAND_Cr);
    OFbkImage cbImg(info, data, ofbkimageBAND_Cb);

    int w = yImg.Width();
    int h = yImg.Height();
    int skip = yImg.Skip();

    byte* iptr = image;
    
    for (int y = 0; y < h; y++) {

        byte* yline  = yImg.Pointer()  + (w + skip) * y;
        byte* crline = crImg.Pointer() + (w + skip) * y;
        byte* cbline = cbImg.Pointer() + (w + skip) * y;

        for (int x = 0; x < w; x++) {
            *iptr++ = yline[x];     // Y
            *iptr++ = cbline[x];    // Cb
            *iptr++ = crline[x];    // Cr
        }

    }

    *width  = w;
    *height = h;
}

void
JPEGEncoder::ReconstructAndConvertCtd(OFbkImageVectorData* imageVec,
				      OFbkImageLayer layer, byte* image,
				      int* width, int* height)
{
  OFbkImageInfo* info = imageVec->GetInfo(layer);
  byte*          data = imageVec->GetData(layer);

  OFbkImage cdtImage(info, data, ofbkimageBAND_CDT);

  int w     = cdtImage.Width();
  int h     = cdtImage.Height();
  byte* ptr = cdtImage.Pointer();

  *width  = 2 * w;
  *height = 2 * h;  
  
  byte yValue,crValue,cbValue;
  
  for (int y = 0; y < h; y++) 
    {
      for (int x = 0; x < w; x++) 
	{
	  yValue=CDTColorMatrix[*ptr][0];
	  crValue=CDTColorMatrix[*ptr][1];
	  cbValue=CDTColorMatrix[*ptr][2];
	  
	  PutYCbCrPixel(image, *width, 2*x,   2*y,   yValue, cbValue, crValue);
	  PutYCbCrPixel(image, *width, 2*x+1, 2*y,   yValue, cbValue, crValue);
	  PutYCbCrPixel(image, *width, 2*x,   2*y+1, yValue, cbValue, crValue);
	  PutYCbCrPixel(image, *width, 2*x+1, 2*y+1, yValue, cbValue, crValue);
	  ptr++;
	}
    }
  return;
}


#ifndef RECONSTRUCT_POINTER_VERSION
void
JPEGEncoder::ReconstructAndConvertYCbCr(OFbkImageVectorData* imageVec,
                                        OFbkImageLayer layer, byte* image,
                                        int* width, int* height)
{
    OFbkImageInfo* info = imageVec->GetInfo(layer);
    byte*          data = imageVec->GetData(layer);

    OFbkImage yLLImg(info, data, ofbkimageBAND_Y); // Y_LL
    OFbkImage yLHImg(info, data, ofbkimageBAND_Y_LH);
    OFbkImage yHLImg(info, data, ofbkimageBAND_Y_HL);
    OFbkImage yHHImg(info, data, ofbkimageBAND_Y_HH);

    OFbkImage crImg(info, data, ofbkimageBAND_Cr);
    OFbkImage cbImg(info, data, ofbkimageBAND_Cb);

    int w = yLLImg.Width();
    int h = yLLImg.Height();

    for (int y = 0; y < h; y++) {

        for (int x = 0; x < w; x++) {
            //
            // yLH, yHL, yHH : offset binary [0, 255] -> signed int [-128, 127]
            //
            int yLL = (int)yLLImg.Pixel(x, y);
            int yLH = (int)yLHImg.Pixel(x, y) - 128;
            int yHL = (int)yHLImg.Pixel(x, y) - 128;
            int yHH = (int)yHHImg.Pixel(x, y) - 128;

            int a = yLL + yLH + yHL + yHH; // ypix11
            int b = 2 * (yLL + yLH);       // ypix11 + ypix01
            int c = 2 * (yLL + yHL);       // ypix11 + ypix10
            int d = 2 * (yLL + yHH);       // ypix11 + ypix00
            
            byte ypix00 = ClipRange(d - a);
            byte ypix10 = ClipRange(c - a);
            byte ypix01 = ClipRange(b - a);
            byte ypix11 = ClipRange(a);
            
            byte cb = cbImg.Pixel(x, y);
            byte cr = crImg.Pixel(x, y);
            
            PutYCbCrPixel(image, 2*w, 2*x,   2*y,   ypix00, cb, cr);
            PutYCbCrPixel(image, 2*w, 2*x+1, 2*y,   ypix10, cb, cr);
            PutYCbCrPixel(image, 2*w, 2*x,   2*y+1, ypix01, cb, cr);
            PutYCbCrPixel(image, 2*w, 2*x+1, 2*y+1, ypix11, cb, cr);
        }

    }

    *width  = 2 * w;
    *height = 2 * h;
}
#else
void
JPEGEncoder::ReconstructAndConvertYCbCr(OFbkImageVectorData* imageVec,
                                        OFbkImageLayer layer, byte* image,
                                        int* width, int* height)
{
    OFbkImageInfo* info = imageVec->GetInfo(layer);
    byte*          data = imageVec->GetData(layer);

    OFbkImage yLLImg(info, data, ofbkimageBAND_Y); // Y_LL
    OFbkImage yLHImg(info, data, ofbkimageBAND_Y_LH);
    OFbkImage yHLImg(info, data, ofbkimageBAND_Y_HL);
    OFbkImage yHHImg(info, data, ofbkimageBAND_Y_HH);

    OFbkImage crImg(info, data, ofbkimageBAND_Cr);
    OFbkImage cbImg(info, data, ofbkimageBAND_Cb);

    byte* yLLPtr = yLLImg.Pointer();
    byte* yLHPtr = yLHImg.Pointer();
    byte* yHLPtr = yHLImg.Pointer();
    byte* yHHPtr = yHHImg.Pointer();

    byte* crPtr = crImg.Pointer();
    byte* cbPtr = cbImg.Pointer();

    int w    = yLLImg.Width();
    int h    = yLLImg.Height();
    int skip = yLLImg.Skip();
    OSYSDEBUG(("w %d h %d skip %d\n", w, h, skip));

    byte* iptr0 = image;
    byte* iptr1 = image + 3 * (2 * w);
    
    for (int y = 0; y < h; y++) {

        for (int x = 0; x < w; x++) {
            //
            // yLH, yHL, yHH : offset binary [0, 255] -> signed int [-128, 127]
            //
            int yLL = (int)*yLLPtr++;
            int yLH = (int)*yLHPtr++ - 128;
            int yHL = (int)*yHLPtr++ - 128;
            int yHH = (int)*yHHPtr++ - 128;

            int a = yLL + yLH + yHL + yHH; // ypix11
            int b = 2 * (yLL + yLH);       // ypix11 + ypix01
            int c = 2 * (yLL + yHL);       // ypix11 + ypix10
            int d = 2 * (yLL + yHH);       // ypix11 + ypix00
            
            byte ypix00 = ClipRange(d - a);
            byte ypix10 = ClipRange(c - a);
            byte ypix01 = ClipRange(b - a);
            byte ypix11 = ClipRange(a);
            
            byte cb = *cbPtr++;
            byte cr = *crPtr++;
            
            iptr0[0] = ypix00;
            iptr0[1] = cb;
            iptr0[2] = cr;

            iptr0[3] = ypix10;
            iptr0[4] = cb;
            iptr0[5] = cr;

            iptr1[0] = ypix01;
            iptr1[1] = cb;
            iptr1[2] = cr;

            iptr1[3] = ypix11;
            iptr1[4] = cb;
            iptr1[5] = cr;

            iptr0 += 6;
            iptr1 += 6;
        }

        yLLPtr += skip;
        yLHPtr += skip;
        yHLPtr += skip;
        yHHPtr += skip;

        cbPtr  += skip;
        crPtr  += skip;

        iptr0  += 3 * (2 * w);
        iptr1  += 3 * (2 * w);
    }

    *width  = 2 * w;
    *height = 2 * h;
}
#endif // RECONSTRUCT_POINTER_VERSION
