/**************************************************************************/
/*                                                                        */
/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com)          */
/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de>  */
/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de>                 */
/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de>                */
/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/
/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com)           */
/*                                                                        */
/* NXCOMPEXT, NX protocol compression and NX extensions to this software  */
/* are copyright of the aforementioned persons and companies.             */
/*                                                                        */
/* Redistribution and use of the present software is allowed according    */
/* to terms specified in the file LICENSE which comes in the source       */
/* distribution.                                                          */
/*                                                                        */
/* All rights reserved.                                                   */
/*                                                                        */
/* NOTE: This software has received contributions from various other      */
/* contributors, only the core maintainers and supporters are listed as   */
/* copyright holders. Please contact us, if you feel you should be listed */
/* as copyright holder, as well.                                          */
/*                                                                        */
/**************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <nx-X11/Xutil.h>

#include "Compext.h"

#include "Mask.h"
#include "Png.h"

#include "../Utils.h"

#define PANIC
#define WARNING
#undef  TEST
#undef  DEBUG

/*
 * Selected ZLIB compression level.
 */

#define PNG_Z_LEVEL   4

/*
 * Local function prototypes.
 */

static void PrepareRowForPng(CARD8 *dst, int y, int count);
static void PrepareRowForPng24(CARD8 *dst, int y, int count);
static void PrepareRowForPng16(CARD8 *dst, int y, int count);
static void PrepareRowForPng32(CARD8 *dst, int y, int count);

static void PngWriteData(png_structp png_ptr, png_bytep data, png_size_t length);
static void PngFlushData(png_structp png_ptr);

/*
 * Image characteristics.
 */

static int bytesPerLine;
static int byteOrder;

static CARD8 bitsPerPixel;
static CARD16 redMax, greenMax, blueMax;
static CARD8 redShift, greenShift, blueShift;

/*
 * Other variables used for the Png
 * encoding.
 */

png_byte    color_type;
png_structp png_ptr;
png_infop   info_ptr;
png_colorp  palette;
static char *pngCompBuf;
static int  pngDataLen;
static char *pngBeforeBuf = NULL;

/*
 * Allocate data for the compressed image.
 * We need to ensure that there is enough
 * space to include the palette and the
 * header.
 */

#define PNG_DEST_SIZE(width, height) ((width) * 3 * (height) + 1024 + 256)

/*
 * Just for debug purposes.
 */

#ifdef DEBUG

static int  pngId;
static char pngName[10];
static FILE *pngFile;

#endif

int PngCompareColorTable(NXColorTable *c1, NXColorTable *c2)
{
  return (c1 -> pixel - c2 -> pixel);
}

#define NB_COLOR_MAX 256

int NXCreatePalette32(XImage *src_image, NXColorTable *color_table, CARD8 *image_index, int nb_max)
{
  int    x, y, t, p;
  CARD8 *fbptr;
  CARD32 pixel;

  fbptr = (CARD8 *) (src_image -> data);

  /*
   * TODO: Find a more intelligent way to
   * estimate the number of colors.
   */

  memset(color_table, 0, nb_max * sizeof(NXColorTable));

  for (x = 0, p = 0; x < src_image -> height; x++)
  {
    for (y = 0; y < src_image -> width; y++)
    {
      if (byteOrder == LSBFirst)
      {
        pixel = (CARD32) *(fbptr + 3);
        pixel = (pixel << 8) | (CARD32) *(fbptr + 2);
        pixel = (pixel << 8) | (CARD32) *(fbptr + 1);
        pixel = (pixel << 8) | (CARD32) *fbptr;
      }
      else
      {
        pixel = (CARD32) *fbptr;
        pixel = (pixel << 8) | (CARD32) *(fbptr + 1);
        pixel = (pixel << 8) | (CARD32) *(fbptr + 2);
        pixel = (pixel << 8) | (CARD32) *(fbptr + 3);
      }

      fbptr += 4;

      for (t = 0; t < nb_max; t++)
      {
        if (color_table[t].found == 0)
        {
          color_table[t].pixel =  pixel;
          color_table[t].found =  1;
          p++;
          image_index[((x * src_image -> width) + y)] = t;

          break;
        }
        else if ((CARD32)(color_table[t].pixel) == pixel)
        {
          image_index[((x * src_image -> width) + y)] = t;

          break;
        }
      }

      if (p == nb_max)
      {
        return nb_max + 1;
      }
    }
  }
  return p;
}

int NXCreatePalette16(XImage *src_image, NXColorTable *color_table, CARD8 *image_index, int nb_max)
{
  int    x, y, t, p;
  CARD8 *fbptr;
  CARD16 pixel;

  fbptr = (CARD8 *) (src_image -> data);

  /*
   * TODO: Find a more intelligent way to
   * estimate the number of colors.
   */

  memset(color_table, 0, nb_max * sizeof(NXColorTable));

  for (x = 0, p = 0; x < src_image -> height; x++)
  {
    for (y = 0; y < src_image -> width; y++)
    {
      if (byteOrder == LSBFirst)
      {
        pixel = (CARD16) *(fbptr + 1);
        pixel = (pixel << 8) | (CARD16) *fbptr;
      }
      else
      {
        pixel = (CARD16) *fbptr;
        pixel = (pixel << 8) | (CARD16) *(fbptr + 1);
      }

      fbptr += 2;

      for (t = 0; t < nb_max; t++)
      {
        if (color_table[t].found == 0)
        {
          color_table[t].pixel =  pixel;
          color_table[t].found =  1;
          p++;
          image_index[((x * src_image -> width) + y)] = t;

          break;
        }
        else if ((color_table[t].pixel) == pixel)
        {
          image_index[((x * src_image -> width) + y)] = t;

          break;
        }
      }

      /*
       * In case the number of 16bit words is not even
       * we have 2 padding bytes that we have to skip.
       */

      if ((y == src_image -> width - 1) && (src_image -> width % 2 == 1)) fbptr += 2;

      if (p == nb_max)
      {
        return nb_max + 1;
      }
    }
  }

  return p;
}

char *PngCompressData(XImage *image, int *compressed_size)
{
  unsigned int num = 0;
  CARD8        *srcBuf;

  int dy, w, h;

  int nb_colors;

  NXColorTable color_table[NB_COLOR_MAX];
  CARD8       *image_index;

  *compressed_size = 0;

  pngDataLen = 0;

  /*
   * Initialize the image stuff.
   */

  bitsPerPixel = image -> bits_per_pixel;
  bytesPerLine = image -> bytes_per_line;
  byteOrder = image -> byte_order;

  if (bitsPerPixel < 15)
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! Can't compress images with [%d] bits per pixel.\n",
                bitsPerPixel);
    #endif

    return NULL;
  }

  redShift   = FindLSB(image -> red_mask)   - 1;
  greenShift = FindLSB(image -> green_mask) - 1;
  blueShift  = FindLSB(image -> blue_mask)  - 1;

  redMax   = image -> red_mask   >> redShift;
  greenMax = image -> green_mask >> greenShift;
  blueMax  = image -> blue_mask  >> blueShift;

  w             = image -> width;
  h             = image -> height;
  pngBeforeBuf  = image -> data;

  #ifdef DEBUG
  fprintf(stderr, "******PngCompressData: Compressing image with width [%d] height [%d].\n",
              w, h );
  #endif

  /*
   * Initialize the PNG stuff.
   */

  png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);

  if (png_ptr == NULL)
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! Failed creating the png_create_write_struct.\n");
    #endif

    return NULL;
  }

  info_ptr = png_create_info_struct(png_ptr);

  if (info_ptr == NULL)
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! Failed creating the png_create_info_struct.\n");
    #endif

    png_destroy_write_struct(&png_ptr, NULL);

    return NULL;
  }

  if (setjmp(png_jmpbuf(png_ptr)))
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! Error during compression initialization.\n");
    #endif

    png_destroy_write_struct(&png_ptr, &info_ptr);

    return NULL;
  }

  /*
   * Be sure we allocate enough data.
   */

  #ifdef TEST
  fprintf(stderr, "******PngCompressData: Allocating [%d] bytes for the destination data.\n",
              PNG_DEST_SIZE(w, h));
  #endif

  pngCompBuf = malloc(PNG_DEST_SIZE(w, h));

  if (pngCompBuf == NULL)
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! Error allocating [%d] bytes for the Png data.\n",
                PNG_DEST_SIZE(w, h));
    #endif

    return NULL;
  }

  png_set_write_fn(png_ptr, (void *) pngCompBuf, PngWriteData, PngFlushData);

  if (setjmp(png_jmpbuf(png_ptr)))
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! Error writing the header.\n");
    #endif

    png_destroy_write_struct(&png_ptr, &info_ptr);
    SAFE_free(pngCompBuf);

    return NULL;
  }

  image_index = (CARD8 *) calloc(1, (image -> height) * (image -> width) * sizeof(CARD8));
  if (image_index == NULL)
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! Could not alloc image_index.\n");
    #endif

    SAFE_free(pngCompBuf);
    return NULL;
  }

  /*
   * TODO: Be sure the padded bytes are cleaned.
   * It would be better to set to zero the bytes
   * that are not aligned to the word boundary
   * at the end of the procedure.
   */

  png_set_compression_level(png_ptr, PNG_Z_LEVEL);

  if (bitsPerPixel == 16)
  {
    nb_colors = NXCreatePalette16(image, color_table, image_index, NB_COLOR_MAX);
  }
  else
  {
    nb_colors = NXCreatePalette32(image, color_table, image_index, NB_COLOR_MAX);
  }

  if (nb_colors <= NB_COLOR_MAX)
  {
    color_type = PNG_COLOR_TYPE_PALETTE;
  }
  else
  {
    color_type = PNG_COLOR_TYPE_RGB;
  }

  png_set_IHDR(png_ptr, info_ptr, w, h,
                   8, color_type, PNG_INTERLACE_NONE,
                       PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

  if (color_type == PNG_COLOR_TYPE_PALETTE)
  {
    palette = png_malloc(png_ptr, sizeof(*palette) * 256);

    /*
     * TODO: Do we need to clean these bytes?
     *
     * memset(palette, 0, sizeof(*palette) * 256);
     */

    for (num = 0; num < 256 && color_table[num].found != 0; num++)
    {
      if (bitsPerPixel == 24)
      {
        palette[num].red = (color_table[num].pixel >> redShift) & redMax;
        palette[num].green = (color_table[num].pixel >> greenShift) & greenMax;
        palette[num].blue = color_table[num].pixel >> blueShift & blueMax;
      }
      else
      {
        int inRed, inGreen, inBlue;

        inRed = (color_table[num].pixel >> redShift) & redMax;
        inGreen = (color_table[num].pixel >> greenShift) & greenMax;
        inBlue = color_table[num].pixel >> blueShift & blueMax;

        palette[num].red = (CARD8)((inRed * 255 + redMax / 2) / redMax);
        palette[num].green = (CARD8)((inGreen * 255 + greenMax / 2) / greenMax);
        palette[num].blue = (CARD8)((inBlue * 255 + blueMax / 2) / blueMax);
      }

      #ifdef DEBUG
      fprintf(stderr, "******PngCompressData: pixel[%d] r[%d] g[%d] b[%d].\n",
                  (int) color_table[num].pixel,palette[num].red,palette[num].green,palette[num].blue);
      #endif
    }

    png_set_PLTE(png_ptr, info_ptr, palette, num);

    #ifdef DEBUG
    fprintf(stderr, "******PngCompressedData: Setting palette.\n");
    #endif
  }

  /*
   * End of palette.
   */

  png_write_info(png_ptr, info_ptr);

  /*
   * Allocate space for one line of
   * the image, 3 bytes per pixel.
   */

  #ifdef DEBUG
  fprintf(stderr, "******PngCompressedData: Initialization finished.\n");
  #endif

  if (setjmp(png_jmpbuf(png_ptr)))
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! Error while writing the image rows.\n");
    #endif

    png_destroy_write_struct(&png_ptr, &info_ptr);

    SAFE_free(pngCompBuf);
    SAFE_free(image_index);

    return NULL;
  }

  int count;
  if (color_type == PNG_COLOR_TYPE_PALETTE)
  {
    count = w;
  }
  else
  {
    count = 3 * w;
  }

  srcBuf = (CARD8 *) calloc(count, sizeof(CARD8));

  if (srcBuf == NULL)
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! Cannot allocate [%d] bytes.\n",
                  (int) (count * sizeof(CARD8)));
    #endif

    SAFE_free(pngCompBuf);
    SAFE_free(image_index);

    return NULL;
  }

  /*
   * TODO: Be sure the padded bytes are cleaned.
   * It would be better to set to zero the bytes
   * that are not aligned to the word boundary
   * at the end of the procedure.
   */

  for (dy = 0; dy < h; dy++)
  {
    if (color_type == PNG_COLOR_TYPE_RGB)
    {
      PrepareRowForPng(srcBuf, dy, w);
    }
    else
    {
      memcpy(srcBuf, image_index + (dy * w), w);
    }

    png_write_row(png_ptr, srcBuf);
  }

  #ifdef DEBUG
  fprintf(stderr, "******PngCompressedData: Compression finished. Lines handled [%d,%d].\n",
              dy, h);
  #endif

  SAFE_free(srcBuf);
  SAFE_free(image_index);

  if (setjmp(png_jmpbuf(png_ptr)))
  {
    #ifdef PANIC
    fprintf(stderr, "******PngCompressData: PANIC! error during end of write.\n");
    #endif

    png_destroy_write_struct(&png_ptr, &info_ptr);

    SAFE_free(pngCompBuf);

    return NULL;
  }

  png_write_end(png_ptr, NULL);

  if (color_type == PNG_COLOR_TYPE_PALETTE)
  {
    png_free(png_ptr, palette);
  }

  png_destroy_write_struct(&png_ptr, &info_ptr);

  /*
   * Check the size of the resulting data.
   */

  if (pngDataLen > 0)
  {
    #ifdef DEBUG

    int i = 0;

    fprintf(stderr, "******PngCompressedData: Compressed size [%d].\n",
                pngDataLen);

    pngId++;
    sprintf(pngName, "png%d", pngId);
    pngFile = fopen(pngName, "w");

    for (i = 0; i < pngDataLen; i++)
    {
      fprintf(pngFile, "%c", *(pngCompBuf + i));
    }

    fclose(pngFile);

    #endif

    *compressed_size = pngDataLen;

    return pngCompBuf;
  }
  else
  {
    #ifdef DEBUG
    fprintf(stderr, "******PngCompressedData: PANIC! Invalid size of the compressed data [%d].\n",
                pngDataLen);
    #endif

    SAFE_free(pngCompBuf);

    return NULL;
  }
}

static void PngWriteData(png_structp _png_ptr, png_bytep data, png_size_t length)
{
  memcpy(((char *) png_get_io_ptr(_png_ptr) + pngDataLen), data, length);

  pngDataLen += length;
}

static void PngFlushData(png_structp _png_ptr)
{
}

void PrepareRowForPng(CARD8 *dst, int y, int count)
{
  if (bitsPerPixel == 32)
  {
    if (redMax == 0xff &&
           greenMax == 0xff &&
               blueMax == 0xff)
    {
      PrepareRowForPng24(dst, y, count);
    }
    else
    {
      PrepareRowForPng32(dst, y, count);
    }
  }
  else if (bitsPerPixel == 24)
  {
    memcpy(dst, pngBeforeBuf + y * bytesPerLine, count * 3);
  }
  else
  {
    /*
     * 16 bpp assumed.
     */

    PrepareRowForPng16(dst, y, count);
  }
}



void PrepareRowForPng24(CARD8 *dst, int y, int count)
{
  CARD8 *fbptr;
  CARD32 pix;

  fbptr = (CARD8 *) (pngBeforeBuf + y * bytesPerLine);

  while (count--)
  {
    if (byteOrder == LSBFirst)
    {
      pix = (CARD32) *(fbptr + 2);
      pix = (pix << 8) | (CARD32) *(fbptr+1);
      pix = (pix << 8) | (CARD32) *fbptr;
    }
    else
    {
      pix = (CARD32) *(fbptr + 1);
      pix = (pix << 8) | (CARD32) *(fbptr + 2);
      pix = (pix << 8) | (CARD32) *(fbptr + 3);
    }

    *dst++ = (CARD8)(pix >> redShift);
    *dst++ = (CARD8)(pix >> greenShift);
    *dst++ = (CARD8)(pix >> blueShift);

    fbptr+=4;
  }
}

#define DEFINE_PNG_GET_ROW_FUNCTION(bpp)                                    \
                                                                            \
void PrepareRowForPng##bpp(CARD8 *dst, int y, int count)                    \
{                                                                           \
  CARD8 *fbptr;                                                             \
  CARD##bpp pix;                                                            \
  int inRed, inGreen, inBlue;                                               \
  int i;                                                                    \
                                                                            \
  fbptr = (CARD8 *) (pngBeforeBuf + y * bytesPerLine);                      \
                                                                            \
  while (count--)                                                           \
  {                                                                         \
    pix = 0;                                                                \
                                                                            \
    if (byteOrder == LSBFirst)                                              \
    {                                                                       \
      for (i = (bpp >> 3) - 1; i >= 0; i--)                                 \
      {                                                                     \
        pix = (pix << 8) | (CARD32) *(fbptr + i);                           \
      }                                                                     \
    }                                                                       \
    else                                                                    \
    {                                                                       \
      for (i = 0; i < (bpp >> 3); i++)                                      \
      {                                                                     \
        pix = (pix << 8) | (CARD32) *(fbptr + i);                           \
      }                                                                     \
    }                                                                       \
                                                                            \
    fbptr += (bpp >> 3);                                                    \
                                                                            \
    inRed = (int)                                                           \
            (pix >> redShift   & redMax);                                   \
    inGreen = (int)                                                         \
            (pix >> greenShift & greenMax);                                 \
    inBlue  = (int)                                                         \
            (pix >> blueShift  & blueMax);                                  \
    *dst++ = (CARD8)((inRed   * 255 + redMax / 2) /                         \
                         redMax);                                           \
    *dst++ = (CARD8)((inGreen * 255 + greenMax / 2) /                       \
                         greenMax);                                         \
    *dst++ = (CARD8)((inBlue  * 255 + blueMax / 2) /                        \
                         blueMax);                                          \
  }                                                                         \
}

DEFINE_PNG_GET_ROW_FUNCTION(16)
DEFINE_PNG_GET_ROW_FUNCTION(32)
