//  Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved.
//  Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
//
//  This file is part of the VNC system.
//
//  The VNC system is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
//  USA.
//
// TightVNC distribution homepage on the Web: http://www.tightvnc.com/
//
// If the source code for the VNC system is not available from the place 
// whence you received this file, check http://www.uk.research.att.com/vnc or contact
// the authors on vnc@uk.research.att.com for information on obtaining it.

// Tight Encoding
//
// The bits of the ClientConnection object to do with Tight.

#include <pspkernel.h>
#include <string.h>
#include "pspnet.h"
#include "vnc.h"

extern int rfbsock;

typedef struct {
    CARD32 nBytes;
} rfbZlibHeader;
#define sz_rfbZlibHeader 4
    
extern CARD8 m_netbuf[0x40000];
extern CARD8 m_zlibbuf[0x40000];

#define CheckBufferSize(x) if(x<sizeof(m_netbuf)) return false;
#define CheckZlibBufferSize(x) if(x<sizeof(m_zlibbuf)) return false;
#define Check_Rectangle_borders(x,y,w,h)
    
#define TIGHT_MIN_TO_COMPRESS 12
#define TIGHT_BUFFER_SIZE (2048 * 200)

int ReadTightRect(rfbFramebufferUpdateRectHeader *pfburh)
{
  CARD8 comp_ctl;
  ReadExact(rfbsock, (char *)&comp_ctl, 1);

  /* Flush zlib streams if we are told by the server to do so. */
  for (int i = 0; i < 4; i++) {
    if ((comp_ctl & 1) && m_tightZlibStreamActive[i]) {
      int err = inflateEnd (&m_tightZlibStream[i]);
      if (err != Z_OK) {
        if (m_tightZlibStream[i].msg != NULL) {
          LogPrintf("zlib inflateEnd() error: %s\n",
                       m_tightZlibStream[i].msg);
        } else {
          LogPrintf("zlib inflateEnd() error: %d\n", err);
        }
        return false;
      }
      m_tightZlibStreamActive[i] = FALSE;
    }
    comp_ctl >>= 1;
  }

  /* Handle solid rectangles. */
  if (comp_ctl == rfbTightFill) {
    COLORREF fillColour;
    CARD32 fillColourBuf;
    ReadExact(rfbsock, (char *)&fillColourBuf, m_myFormat.bitsPerPixel / 8);

    SETUP_COLOR_SHORTCUTS;

    fillColour = COLOR_FROM_PIXEL16_ADDRESS(&fillColourBuf);

//    omni_mutex_lock l(m_bitmapdcMutex);
//    ObjectSelector b(m_hBitmapDC, m_hBitmap);
//    PaletteSelector p(m_hBitmapDC, m_hPalette);

    FillSolidRect(pfburh->r.x, pfburh->r.y, pfburh->r.w, pfburh->r.h,
                  fillColour);
    return true;
  }

  if (comp_ctl == rfbTightJpeg) {
    DecompressJpegRect(pfburh->r.x, pfburh->r.y, pfburh->r.w, pfburh->r.h);
    return true;
  }


  /* Quit on unsupported subencoding value. */
  if (comp_ctl >= rfbTightMaxSubencoding) {
    LogPrintf("Tight encoding: bad subencoding value received.\n");
    return false;
  }

  /*
   * Here primary compression mode handling begins.
   * Data was processed with optional filter + zlib compression.
   */

  /* First, we should identify a filter to use. */
  int bitsPixel;
  if ((comp_ctl & rfbTightExplicitFilter) != 0) {
    CARD8 filter_id;
    ReadExact(rfbsock, (char *)&filter_id, 1);

    switch (filter_id) {
    case rfbTightFilterCopy:
      bitsPixel = InitFilterCopy(pfburh->r.w, pfburh->r.h);
      break;
    case rfbTightFilterPalette:
      bitsPixel = InitFilterPalette(pfburh->r.w, pfburh->r.h);
      break;
    case rfbTightFilterGradient:
      bitsPixel = InitFilterGradient(pfburh->r.w, pfburh->r.h);
      break;
    default:
      LogPrintf("Tight encoding: unknown filter code received.\n");
      return false;
    }
  } else {
    bitsPixel = InitFilterCopy(pfburh->r.w, pfburh->r.h);
  }
  if (bitsPixel == 0) {
    LogPrintf("Tight encoding: error receiving palette.\n");
    return false;
  }

  /* Determine if the data should be decompressed or just copied. */
  int rowSize = (pfburh->r.w * bitsPixel + 7) / 8;
  if (pfburh->r.h * rowSize < TIGHT_MIN_TO_COMPRESS) {
    CheckBufferSize(pfburh->r.h * rowSize);
    ReadExact(rfbsock, m_netbuf, pfburh->r.h * rowSize);

    CheckZlibBufferSize(pfburh->r.w * pfburh->r.h * 4);
    (this->*m_tightCurrentFilter)(pfburh->r.h);

//    omni_mutex_lock l(m_bitmapdcMutex);
//    ObjectSelector b(m_hBitmapDC, m_hBitmap);
//    PaletteSelector p(m_hBitmapDC, m_hPalette);

    SETPIXELS_NOCONV(m_zlibbuf, pfburh->r.x, pfburh->r.y,
                     pfburh->r.w, pfburh->r.h);

    return true;
  }

  /* Read the length (1..3 bytes) of compressed data following. */

  int compressedLen = ReadCompactLen();
  if (compressedLen <= 0) {
    LogPrintf("Tight encoding: bad data received from server.\n");
    return false;
  }

  /* Now let's initialize compression stream if needed. */
  int stream_id = comp_ctl & 0x03;
  z_streamp zs = &m_tightZlibStream[stream_id];
  if (!m_tightZlibStreamActive[stream_id]) {
    zs->zalloc = Z_NULL;
    zs->zfree = Z_NULL;
    zs->opaque = Z_NULL;
    int err = inflateInit(zs);
    if (err != Z_OK) {
      if (zs->msg != NULL) {
        LogPrintf("zlib inflateInit() error: %s.\n", zs->msg);
      } else {
        LogPrintf("zlib inflateInit() error: %d.\n", err);
      }
      return false;
    }
    m_tightZlibStreamActive[stream_id] = TRUE;
  }

  /* Read, decode and draw actual pixel data in a loop. */

  int beforeBufferSize =
    TIGHT_BUFFER_SIZE * bitsPixel / (bitsPixel + sizeof(COLORREF) * 8)
      & 0xFFFFFFFC;
  CheckBufferSize(beforeBufferSize);

  int afterBufferSize = TIGHT_BUFFER_SIZE - beforeBufferSize;
  CheckZlibBufferSize(afterBufferSize);

  int rowsProcessed = 0, extraBytes = 0;
  int err, numRows, portionLen;

  while (compressedLen > 0) {
    if (compressedLen > TIGHT_ZLIB_BUFFER_SIZE)
      portionLen = TIGHT_ZLIB_BUFFER_SIZE;
    else
      portionLen = compressedLen;

    ReadExact(rfbsock, m_tightbuf, portionLen);

    compressedLen -= portionLen;

    zs->next_in = (Bytef *)m_tightbuf;
    zs->avail_in = portionLen;

    do {
      zs->next_out = (Bytef *)&m_netbuf[extraBytes];
      zs->avail_out = beforeBufferSize - extraBytes;

      err = inflate(zs, Z_SYNC_FLUSH);
      if (err == Z_BUF_ERROR)   // Input exhausted -- no problem
        break;
      if (err != Z_OK && err != Z_STREAM_END) {
        if (zs->msg != NULL) {
          LogPrintf("zlib inflate() error: %s.\n", zs->msg);
        } else {
          LogPrintf("zlib inflate() error: %d.\n", err);
        }
        return false;
      }

      numRows = (beforeBufferSize - zs->avail_out) / rowSize;

      (this->*m_tightCurrentFilter)(numRows);

      extraBytes = beforeBufferSize - zs->avail_out - numRows * rowSize;
      if (extraBytes > 0)
        memcpy(m_netbuf, &m_netbuf[numRows * rowSize], extraBytes);

      omni_mutex_lock l(m_bitmapdcMutex);
      ObjectSelector b(m_hBitmapDC, m_hBitmap);
      PaletteSelector p(m_hBitmapDC, m_hPalette);

      SETPIXELS_NOCONV(m_zlibbuf, pfburh->r.x, pfburh->r.y + rowsProcessed,
                       pfburh->r.w, numRows);

      rowsProcessed += numRows;
    }
    while (zs->avail_out == 0);
  }

  if (rowsProcessed != pfburh->r.h) {
    LogPrintf("Tight encoding: wrong number of scan lines.\n");
    return false;
  }

  return true;
}

static int ReadCompactLen() {
  CARD8 len_byte;
  ReadExact(rfbsock, (char *)&len_byte, 1);
  int compressedLen = (int)len_byte & 0x7F;
  if (len_byte & 0x80) {
    ReadExact(rfbsock, (char *)&len_byte, 1);
    compressedLen |= ((int)len_byte & 0x7F) << 7;
    if (len_byte & 0x80) {
      ReadExact(rfbsock, (char *)&len_byte, 1);
      compressedLen |= ((int)len_byte & 0xFF) << 14;
    }
  }
  return compressedLen;
}

//----------------------------------------------------------------------------
//
// Filter stuff.
//

// The following variables are defined in the class declaration:
//   tightFilterFunc m_tightCurrentFilter;
//   Bool m_tightCutZeros;
//   int m_tightRectWidth, m_tightRectColors;
//   COLORREF m_tightPalette[256];
//   CARD8 m_tightPrevRow[2048*3*sizeof(CARD16)];

static int InitFilterCopy (int rw, int rh)
{
  tightFilterFunc funcArray[3] = {
    &ClientConnection::FilterCopy8,
    &ClientConnection::FilterCopy16,
    &ClientConnection::FilterCopy32
  };

  m_tightCurrentFilter = funcArray[m_myFormat.bitsPerPixel/16];
  m_tightRectWidth = rw;

  if (m_myFormat.depth == 24 && m_myFormat.redMax == 0xFF &&
      m_myFormat.greenMax == 0xFF && m_myFormat.blueMax == 0xFF) {
    m_tightCutZeros = TRUE;
    m_tightCurrentFilter = &ClientConnection::FilterCopy24;
    return 24;
  }

  m_tightCutZeros = FALSE;
  return m_myFormat.bitsPerPixel;
}

static int InitFilterGradient (int rw, int rh)
{
  int bits = InitFilterCopy(rw, rh);

  tightFilterFunc funcArray[3] = {
    &ClientConnection::FilterGradient8,
    &ClientConnection::FilterGradient16,
    &ClientConnection::FilterGradient32
  };

  m_tightCurrentFilter = funcArray[m_myFormat.bitsPerPixel/16];

  if (m_tightCutZeros) {
    m_tightCurrentFilter = &ClientConnection::FilterGradient24;
    memset(m_tightPrevRow, 0, rw * 3);
  } else
    memset(m_tightPrevRow, 0, rw * 3 * sizeof(CARD16));

  return bits;
}

static int InitFilterPalette (int rw, int rh)
{
  m_tightCurrentFilter = &ClientConnection::FilterPalette;
  m_tightRectWidth = rw;

  CARD8 numColors;
  ReadExact(rfbsock, (char *)&numColors, 1);

  m_tightRectColors = (int)numColors;
  if (++m_tightRectColors < 2)
    return 0;

  if (m_myFormat.depth == 24 && m_myFormat.redMax == 0xFF &&
      m_myFormat.greenMax == 0xFF && m_myFormat.blueMax == 0xFF) {

    CheckBufferSize(m_tightRectColors * 3);
    ReadExact(rfbsock, m_netbuf, m_tightRectColors * 3);

    for (int i = 0; i < m_tightRectColors; i++)
      m_tightPalette[i] = COLOR_FROM_PIXEL24_ADDRESS(&m_netbuf[i*3]);

  } else {
    CheckBufferSize(m_tightRectColors * (m_myFormat.bitsPerPixel / 8));
    ReadExact(rfbsock, m_netbuf, m_tightRectColors * (m_myFormat.bitsPerPixel / 8));

    SETUP_COLOR_SHORTCUTS;

    int i;
    switch (m_myFormat.bitsPerPixel) {
    case 8:
      for (i = 0; i < m_tightRectColors; i++)
        m_tightPalette[i] = COLOR_FROM_PIXEL8_ADDRESS(&m_netbuf[i]);
      break;
    case 16:
      for (i = 0; i < m_tightRectColors; i++)
        m_tightPalette[i] = COLOR_FROM_PIXEL16_ADDRESS(&m_netbuf[i*2]);
      break;
    default:
      for (i = 0; i < m_tightRectColors; i++)
        m_tightPalette[i] = COLOR_FROM_PIXEL32_ADDRESS(&m_netbuf[i*4]);
    }
  }

  return (m_tightRectColors == 2) ? 1 : 8;
}

//
// Actual filtering code follows.
//

#define DEFINE_TIGHT_FILTER_COPY(bpp)                                         \
                                                                              \
static void FilterCopy##bpp (int numRows)                          \
{                                                                             \
  COLORREF *dst = (COLORREF *)m_zlibbuf;                                      \
                                                                              \
  SETUP_COLOR_SHORTCUTS;                                                      \
                                                                              \
  int x;                                                                      \
  for (int y = 0; y < numRows; y++) {                                         \
    for (x = 0; x < m_tightRectWidth; x++) {                                  \
      dst[y*m_tightRectWidth+x] =                                             \
        COLOR_FROM_PIXEL##bpp##_ADDRESS(m_netbuf +                            \
                                        (y*m_tightRectWidth+x)*(bpp/8));      \
    }                                                                         \
  }                                                                           \
}

DEFINE_TIGHT_FILTER_COPY(8)
DEFINE_TIGHT_FILTER_COPY(16)
DEFINE_TIGHT_FILTER_COPY(24)
DEFINE_TIGHT_FILTER_COPY(32)

#define DEFINE_TIGHT_FILTER_GRADIENT(bpp)                                     \
                                                                              \
static void FilterGradient##bpp (int numRows)                      \
{                                                                             \
  int x, y, c;                                                                \
  CARD##bpp *src = (CARD##bpp *)m_netbuf;                                     \
  COLORREF *dst = (COLORREF *)m_zlibbuf;                                      \
  CARD16 *thatRow = (CARD16 *)m_tightPrevRow;                                 \
  CARD16 thisRow[2048*3];                                                     \
  CARD16 pix[3];                                                              \
  CARD16 max[3];                                                              \
  int shift[3];                                                               \
  int est[3];                                                                 \
                                                                              \
  max[0] = m_myFormat.redMax;                                                 \
  max[1] = m_myFormat.greenMax;                                               \
  max[2] = m_myFormat.blueMax;                                                \
                                                                              \
  shift[0] = m_myFormat.redShift;                                             \
  shift[1] = m_myFormat.greenShift;                                           \
  shift[2] = m_myFormat.blueShift;                                            \
                                                                              \
  for (y = 0; y < numRows; y++) {                                             \
                                                                              \
    /* First pixel in a row */                                                \
    for (c = 0; c < 3; c++) {                                                 \
      pix[c] = (CARD16)((src[y*m_tightRectWidth] >> shift[c]) +               \
                        thatRow[c] & max[c]);                                 \
      thisRow[c] = pix[c];                                                    \
    }                                                                         \
    dst[y*m_tightRectWidth] = PALETTERGB((CARD32)pix[0] * 255 / max[0],       \
                                         (CARD32)pix[1] * 255 / max[1],       \
                                         (CARD32)pix[2] * 255 / max[2]);      \
                                                                              \
    /* Remaining pixels of a row */                                           \
    for (x = 1; x < m_tightRectWidth; x++) {                                  \
      for (c = 0; c < 3; c++) {                                               \
        est[c] = (int)thatRow[x*3+c] + (int)pix[c]-(int)thatRow[(x-1)*3 + c]; \
        if (est[c] > (int)max[c]) {                                           \
          est[c] = (int)max[c];                                               \
        } else if (est[c] < 0) {                                              \
          est[c] = 0;                                                         \
        }                                                                     \
        pix[c] = (CARD16)((src[y*m_tightRectWidth+x] >> shift[c]) +           \
                          est[c] & max[c]);                                   \
        thisRow[x*3+c] = pix[c];                                              \
      }                                                                       \
      dst[y*m_tightRectWidth+x] = PALETTERGB((CARD32)pix[0] * 255 / max[0],   \
                                             (CARD32)pix[1] * 255 / max[1],   \
                                             (CARD32)pix[2] * 255 / max[2]);  \
    }                                                                         \
    memcpy(thatRow, thisRow, m_tightRectWidth * 3 * sizeof(CARD16));          \
  }                                                                           \
}

DEFINE_TIGHT_FILTER_GRADIENT(8)
DEFINE_TIGHT_FILTER_GRADIENT(16)
DEFINE_TIGHT_FILTER_GRADIENT(32)

static void FilterGradient24 (int numRows)
{
  CARD8 thisRow[2048*3];
  CARD8 pix[3];
  int est[3];

  COLORREF *dst = (COLORREF *)m_zlibbuf;

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

    // First pixel in a row
    for (int c = 0; c < 3; c++) {
      pix[c] = m_tightPrevRow[c] + m_netbuf[y*m_tightRectWidth*3+c];
      thisRow[c] = pix[c];
    }
    dst[y*m_tightRectWidth] = COLOR_FROM_PIXEL24_ADDRESS(pix);

    // Remaining pixels of a row
    for (int x = 1; x < m_tightRectWidth; x++) {
      for (int c = 0; c < 3; c++) {
        est[c] = (int)m_tightPrevRow[x*3+c] + (int)pix[c] -
                 (int)m_tightPrevRow[(x-1)*3+c];
        if (est[c] > 0xFF) {
          est[c] = 0xFF;
        } else if (est[c] < 0x00) {
          est[c] = 0x00;
        }
        pix[c] = (CARD8)est[c] + m_netbuf[(y*m_tightRectWidth+x)*3+c];
        thisRow[x*3+c] = pix[c];
      }
      dst[y*m_tightRectWidth+x] = COLOR_FROM_PIXEL24_ADDRESS(pix);
    }

    memcpy(m_tightPrevRow, thisRow, m_tightRectWidth * 3);
  }
}

static void FilterPalette (int numRows)
{
  int x, y, b, w;
  CARD8 *src = (CARD8 *)m_netbuf;
  COLORREF *dst = (COLORREF *)m_zlibbuf;

  if (m_tightRectColors == 2) {
    w = (m_tightRectWidth + 7) / 8;
    for (y = 0; y < numRows; y++) {
      for (x = 0; x < m_tightRectWidth / 8; x++) {
        for (b = 7; b >= 0; b--)
          dst[y*m_tightRectWidth+x*8+7-b] = m_tightPalette[src[y*w+x] >> b & 1];
      }
      for (b = 7; b >= 8 - m_tightRectWidth % 8; b--) {
        dst[y*m_tightRectWidth+x*8+7-b] = m_tightPalette[src[y*w+x] >> b & 1];
      }
    }
  } else {
    for (y = 0; y < numRows; y++)
      for (x = 0; x < m_tightRectWidth; x++)
        dst[y*m_tightRectWidth+x] = m_tightPalette[(int)src[y*m_tightRectWidth+x]];
  }
}

//
// JPEG decompression code.
//

static bool jpegError;

static void JpegSetSrcManager(j_decompress_ptr cinfo, char *compressedData,
							  int compressedLen);

static void DecompressJpegRect(int x, int y, int w, int h)
{
  struct jpeg_decompress_struct cinfo;
  struct jpeg_error_mgr jerr;

  int compressedLen = (int)ReadCompactLen();
  if (compressedLen <= 0) {
    LogPrintf("Incorrect data received from the server.\n");
    return;
  }

  CheckBufferSize(compressedLen);
  ReadExact(rfbsock, m_netbuf, compressedLen);

  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_decompress(&cinfo);

  JpegSetSrcManager(&cinfo, m_netbuf, compressedLen);

  jpeg_read_header(&cinfo, TRUE);
  cinfo.out_color_space = JCS_RGB;

  jpeg_start_decompress(&cinfo);
  if ((int)cinfo.output_width != w || (int)cinfo.output_height != h ||
      cinfo.output_components != 3) {
    LogPrintf("Tight Encoding: Wrong JPEG data received.\n");
    jpeg_destroy_decompress(&cinfo);
    return;
  }

  omni_mutex_lock l(m_bitmapdcMutex);
  ObjectSelector b(m_hBitmapDC, m_hBitmap);
  PaletteSelector p(m_hBitmapDC, m_hPalette);

  // Two scanlines: for 24bit and COLORREF samples
  CheckZlibBufferSize(2*2048*4);

  JSAMPROW rowPointer[1];
  rowPointer[0] = (JSAMPROW)m_zlibbuf;

  COLORREF *pixelPtr;
  for (int dy = 0; cinfo.output_scanline < cinfo.output_height; dy++) {
    jpeg_read_scanlines(&cinfo, rowPointer, 1);
    if (jpegError) {
      break;
    }
    pixelPtr = (COLORREF *)&m_zlibbuf[2048*4];
    for (int dx = 0; dx < w; dx++) {
      *pixelPtr++ = COLOR_FROM_PIXEL24_ADDRESS(&m_zlibbuf[dx*3]);
    }
    SETPIXELS_NOCONV(&m_zlibbuf[2048*4], x, y + dy, w, 1);
  }

  if (!jpegError)
    jpeg_finish_decompress(&cinfo);

  jpeg_destroy_decompress(&cinfo);
}

//
// A "Source manager" for the JPEG library.
//

static struct jpeg_source_mgr jpegSrcManager;
static JOCTET *jpegBufferPtr;
static size_t jpegBufferLen;

static void JpegInitSource(j_decompress_ptr cinfo);
static boolean JpegFillInputBuffer(j_decompress_ptr cinfo);
static void JpegSkipInputData(j_decompress_ptr cinfo, long num_bytes);
static void JpegTermSource(j_decompress_ptr cinfo);

static void
JpegInitSource(j_decompress_ptr cinfo)
{
  jpegError = false;
}

static boolean
JpegFillInputBuffer(j_decompress_ptr cinfo)
{
  jpegError = true;
  jpegSrcManager.bytes_in_buffer = jpegBufferLen;
  jpegSrcManager.next_input_byte = (JOCTET *)jpegBufferPtr;

  return TRUE;
}

static void
JpegSkipInputData(j_decompress_ptr cinfo, long num_bytes)
{
  if (num_bytes < 0 || (size_t)num_bytes > jpegSrcManager.bytes_in_buffer) {
    jpegError = true;
    jpegSrcManager.bytes_in_buffer = jpegBufferLen;
    jpegSrcManager.next_input_byte = (JOCTET *)jpegBufferPtr;
  } else {
    jpegSrcManager.next_input_byte += (size_t) num_bytes;
    jpegSrcManager.bytes_in_buffer -= (size_t) num_bytes;
  }
}

static void
JpegTermSource(j_decompress_ptr cinfo)
{
  /* No work necessary here. */
}

static void
JpegSetSrcManager(j_decompress_ptr cinfo, char *compressedData, int compressedLen)
{
  jpegBufferPtr = (JOCTET *)compressedData;
  jpegBufferLen = (size_t)compressedLen;

  jpegSrcManager.init_source = JpegInitSource;
  jpegSrcManager.fill_input_buffer = JpegFillInputBuffer;
  jpegSrcManager.skip_input_data = JpegSkipInputData;
  jpegSrcManager.resync_to_restart = jpeg_resync_to_restart;
  jpegSrcManager.term_source = JpegTermSource;
  jpegSrcManager.next_input_byte = jpegBufferPtr;
  jpegSrcManager.bytes_in_buffer = jpegBufferLen;

  cinfo->src = &jpegSrcManager;
}

