/*
 * Implementation of class JpgReader
 *
 * $Date$
 * $Revision$
 *
 */

#include "WImage/ImageArray.hh"
#include "WImage/JpgReader.hh"
#include "WImage/debug.h"
#include <cstdio>
extern "C" {
#include <jpeglib.h>
}
#include <setjmp.h>
#include <cassert>
#include <stdexcept>
#include <iostream>
#include <cstring>

#if !(defined __WATCOMC__ && __WATCOMC__ < 1230)
using namespace std;
#endif

static pixel *readJpegFile (FILE *inFile, int &ysize, int &xsize,
                            int &channels);

int
JpgReader::readfmt (void)
{
  FILE *inFile = NULL;

  int xsize = 0;
  int ysize = 0;
  int xysize = 0;
  int channels = 0;

  if (m_fname == NULL)
    {
      inFile = stdin;
    }
  else
    {
      /*
       * Never fails since checked before.
       */
      inFile = fopen (m_fname, "rb");
    }

  /* initialization */
  pixel * allpixels = readJpegFile (inFile, ysize, xsize, channels);

  if (channels != 3 && channels != 1)
    {
      throw invalid_argument ("readfmt: the number of colors is not 3 or 1.");
    }

  /* if we got less channels than assumed before, adapt internal
   * structures accordingly */
  if (channels < m_channels)
    {
      for (int i = channels; i < m_channels; i++)
        {
          DELETENOTNULL (m_images[i]);
        }
      m_channels = channels;
    }
  else if (channels > m_channels)
    {
      throw invalid_argument ("readfmt: can't handle more colors "
                              "than initially expected.");
    }

  fclose (inFile);
  if (allpixels == NULL)
    {
      return -2;
    }
  xysize = xsize * ysize;

  for (int i = 0; i < m_channels; i++)
    {
      if (m_images[i]->rows () != ysize || m_images[i]->cols () != xsize)
        {
          m_images[i]->resize (ysize, xsize);
        }
    }

  /* import */
  for (int i = 0; i < xysize; i++)
    {
      for (int j = 0; j < m_channels; j++)
        {
          m_images[j]->to (i, (coeff)allpixels[m_channels * i + j]);
        }
    }

  /* cleanup */
  DELETEAR (allpixels);

  return 0;
}

/*
 * The following code is derived from the example.c file shipping with
 * the JPEG-lib distribution.
 */

/*
 * ERROR HANDLING:
 *
 * Our example here shows how to override the "error_exit" method so that
 * control is returned to the library's caller when a fatal error occurs,
 * rather than calling exit() as the standard error_exit method does.
 */
struct JpgErrorMgr {
  struct jpeg_error_mgr pub;	/* "public" fields */
  jmp_buf setjmpBuffer;	        /* for return to caller */
};

typedef struct JpgErrorMgr * JpgErrorPtr;

/*
 * Here's the routine that will replace the standard error_exit method:
 */

static void
jpgErrorHandler (j_common_ptr cinfo)
{
  /* cinfo->err really points to a JpgErrorMgr struct, so coerce pointer */
  JpgErrorPtr myerr = (JpgErrorPtr) cinfo->err;

  /* Always display the message. */
  /* We could postpone this until after returning, if we chose. */
  (*cinfo->err->output_message) (cinfo);

  /* Return control to the setjmp point */
  longjmp(myerr->setjmpBuffer, 1);
}


/*
 * Routine for JPEG decompression.  We assume that the source file name
 * is passed in.  We want to return 1 on success, 0 on error.
 */


static pixel *
readJpegFile (FILE *inFile, int &ysize, int &xsize,
              int &channels)
{
  assert (inFile != NULL);

  /*
   * This struct contains the JPEG decompression parameters and pointers to
   * working space (which is allocated as needed by the JPEG library).
   */
  struct jpeg_decompress_struct cinfo;
  /*
   * We use our private extension JPEG error handler.
   * Note that this struct must live as long as the main JPEG parameter
   * struct, to avoid dangling-pointer problems.
   */
  struct JpgErrorMgr jerr;
  /* More stuff */
  JSAMPARRAY buffer;		/* Output row buffer */
  int rowStride;		/* physical row width in output buffer */
  static pixel *ret;            /* buffer for the return value */

  /* Step 1: allocate and initialize JPEG decompression object */

  /* We set up the normal JPEG error routines, then override error_exit. */
  cinfo.err = jpeg_std_error (&jerr.pub);
  jerr.pub.error_exit = jpgErrorHandler;

  ret = NULL;
  /* Establish the setjmp return context for jpgErrorHandler to use. */
  if (setjmp (jerr.setjmpBuffer))
    {
      /* If we get here, the JPEG code has signaled an error.
       * We need to clean up the JPEG object, close the input file, and
       * return. */
      jpeg_destroy_decompress (&cinfo);

      DELETENOTNULL (ret);

      return ret;
    }

  /* Now we can initialize the JPEG decompression object. */
  jpeg_create_decompress (&cinfo);

  /* Step 2: specify data source (eg, a file) */

  jpeg_stdio_src (&cinfo, inFile);

  /*
   * Step 3: read file parameters with jpeg_read_header()
   *
   * We can ignore the return value from jpeg_read_header since
   *   (a) suspension is not possible with the stdio data source, and
   *   (b) we passed TRUE to reject a tables-only JPEG file as an error.
   * See libjpeg.doc for more info.
   */
  (void)jpeg_read_header (&cinfo, TRUE);

  /*
   * Step 4: set parameters for decompression
   *
   * In this example, we don't need to change any of the defaults set by
   * jpeg_read_header(), so we do nothing here.
   */

  /* Step 5: Start decompressor */

  /* We can ignore the return value since suspension is not possible
   * with the stdio data source. */
  (void) jpeg_start_decompress (&cinfo);

  xsize = cinfo.output_width;
  ysize = cinfo.output_height;
  channels = cinfo.output_components;
  //cout << "xsize: " << xsize << ", ysize: " << ysize << ", channels: "
  //     << channels << ", size: " << xsize * ysize * channels << endl;
  ret = NEW (pixel [xsize * ysize * channels]);

  /* We may need to do some setup of our own at this point before reading
   * the data.  After jpeg_start_decompress() we have the correct scaled
   * output image dimensions available, as well as the output colormap
   * if we asked for color quantization.
   * In this case, we need to make an output work buffer of the right size. */

  /* JSAMPLEs per row in output buffer */
  rowStride = cinfo.output_width * cinfo.output_components;

  /* Make a one-row-high sample array that will go away when done with image */
  buffer = (*cinfo.mem->alloc_sarray)
    ((j_common_ptr) &cinfo, JPOOL_IMAGE, rowStride, 1);

  /*
   * Step 6: while (scan lines remain to be read)
   *             jpeg_read_scanlines(...);
   */

  /* Here we use the library's state variable cinfo.output_scanline as the
   * loop counter, so that we don't have to keep track ourselves. */
  while (cinfo.output_scanline < cinfo.output_height)
    {
      /* jpeg_read_scanlines expects an array of pointers to scanlines.
       * Here the array is only one element long, but you could ask for
       * more than one scanline at a time if that's more convenient. */
      (void)jpeg_read_scanlines (&cinfo, buffer, 1);

      //cout << cinfo.output_scanline - 1 << " * " << rowStride << endl;
      /* Assume put_scanline_someplace wants a pointer and sample count. */
      memcpy(&(ret[(cinfo.output_scanline - 1) * rowStride]),
             buffer[0], rowStride);
      //cout << "done" << endl;
    }

  /* Step 7: Finish decompression */

  (void)jpeg_finish_decompress (&cinfo);
  /*
   * We can ignore the return value since suspension is not possible
   * with the stdio data source.
   */

  /* Step 8: Release JPEG decompression object */

  /* This is an important step since it will release a good deal of memory. */
  jpeg_destroy_decompress (&cinfo);

  /*
   * At this point you may want to check to see whether any corrupt-data
   * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
   */

  /* And we're done! */
  return ret;
}

