//========================================================================
//
// JPEG2000Stream.cc
//
// A JPX stream decoder using OpenJPEG
//
// Copyright 2008-2010 Albert Astals Cid <aacid@kde.org>
// Copyright 2011 Daniel Glöckner <daniel-gl@gmx.net>
//
// Licensed under GPLv2 or later
//
//========================================================================

#include "JPEG2000Stream.h"

JPXStream::JPXStream(Stream *strA) : FilterStream(strA)
{
  inited = gFalse;
  image = NULL;
  dinfo = NULL;
  npixels = 0;
  ncomps = 0;
}

JPXStream::~JPXStream() {
  delete str;
  close();
}

void JPXStream::reset() {
  counter = 0;
  ccounter = 0;
}

void JPXStream::close() {
  if (image != NULL) {
    opj_image_destroy(image);
    image = NULL;
    npixels = 0;
  }
  if (dinfo != NULL) {
    opj_destroy_decompress(dinfo);
    dinfo = NULL;
  }
}

int JPXStream::getPos() {
  return counter * ncomps + ccounter;
}

int JPXStream::getChars(int nChars, Guchar *buffer) {
  for (int i = 0; i < nChars; ++i) {
    const int c = doGetChar();
    if (likely(c != EOF)) buffer[i] = c;
    else return i;
  }
  return nChars;
}

int JPXStream::getChar() {
  return doGetChar();
}

#define BUFFER_INITIAL_SIZE 4096

void JPXStream::init()
{
  Object oLen;
  if (getDict()) getDict()->lookup("Length", &oLen);

  int bufSize = BUFFER_INITIAL_SIZE;
  if (oLen.isInt()) bufSize = oLen.getInt();
  oLen.free();

  
  int length = 0;
  unsigned char *buf = str->toUnsignedChars(&length, bufSize);
  init2(buf, length, CODEC_JP2);
  free(buf);

  if (image) {
    npixels = image->comps[0].w * image->comps[0].h;
    ncomps = image->numcomps;
    for (int component = 0; component < ncomps; component++) {
      if (image->comps[component].data == NULL) {
        close();
        break;
      }
      unsigned char *cdata = (unsigned char *)image->comps[component].data;
      int adjust = 0;
      if (image->comps[component].prec > 8)
	adjust = image->comps[component].prec - 8;
      int sgndcorr = 0;
      if (image->comps[component].sgnd)
	sgndcorr = 1 << (image->comps[0].prec - 1);
      for (int i = 0; i < npixels; i++) {
	int r = image->comps[component].data[i];
	r += sgndcorr;
	if (adjust) {
	  r = (r >> adjust)+((r >> (adjust-1))%2);
	  if (unlikely(r > 255))
	    r = 255;
        }
	*(cdata++) = r;
      }
    }
  } else
    npixels = 0;

  counter = 0;
  ccounter = 0;
  inited = gTrue;
}

static void libopenjpeg_error_callback(const char *msg, void * /*client_data*/) {
  error(-1, "%s", msg);
}

static void libopenjpeg_warning_callback(const char *msg, void * /*client_data*/) {
  error(-1, "%s", msg);
}

void JPXStream::init2(unsigned char *buf, int bufLen, OPJ_CODEC_FORMAT format)
{
  opj_cio_t *cio = NULL;

  /* Use default decompression parameters */
  opj_dparameters_t parameters;
  opj_set_default_decoder_parameters(&parameters);

  /* Configure the event manager to receive errors and warnings */
  opj_event_mgr_t event_mgr;
  memset(&event_mgr, 0, sizeof(opj_event_mgr_t));
  event_mgr.error_handler = libopenjpeg_error_callback;
  event_mgr.warning_handler = libopenjpeg_warning_callback;

  /* Get the decoder handle of the format */
  dinfo = opj_create_decompress(format);
  if (dinfo == NULL) goto error;

  /* Catch events using our callbacks */
  opj_set_event_mgr((opj_common_ptr)dinfo, &event_mgr, NULL);

  /* Setup the decoder decoding parameters */
  opj_setup_decoder(dinfo, &parameters);

  /* Open a byte stream */
  cio = opj_cio_open((opj_common_ptr)dinfo, buf, bufLen);
  if (cio == NULL) goto error;

  /* Decode the stream and fill the image structure */
  image = opj_decode(dinfo, cio);

  /* Close the byte stream */
  opj_cio_close(cio);

  if (image == NULL) goto error;
  else return;

error:
  if (format == CODEC_JP2) {
    error(-1, "Did no succeed opening JPX Stream as JP2, trying as J2K.");
    init2(buf, bufLen, CODEC_J2K);
  } else if (format == CODEC_J2K) {
    error(-1, "Did no succeed opening JPX Stream as J2K, trying as JPT.");
    init2(buf, bufLen, CODEC_JPT);
  } else {
    error(-1, "Did no succeed opening JPX Stream.");
  }
}

int JPXStream::lookChar() {
  return doLookChar();
}

GooString *JPXStream::getPSFilter(int psLevel, char *indent) {
  return NULL;
}

GBool JPXStream::isBinary(GBool last) {
  return str->isBinary(gTrue);
}

void JPXStream::getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode) {
  if (inited == gFalse) init();

  *bitsPerComponent = 8;
  if (image && image->numcomps == 3) *csMode = streamCSDeviceRGB;
  else *csMode = streamCSDeviceGray;
}

