/*
 * jpeg - This file provides interface routines to connect xloadimage to
 * the jpeg decoding routines taken from the Independent JPEG Group's
 * free JPEG software.  See jpeg.README for more information.
 *
 * This code is based on example.c from the IJG v4 distribution.
 * 1998/08/19: Change for IJG v6.0a. dump Progressive JPEG support.
 */

#include "image.h"	/* xloadimage declarations */
#ifdef HAVE_LIBJPEG
#include "options.h"
#include <jpeglib.h>
#include <jerror.h>
#include <setjmp.h>	/* need setjmp/longjmp */

#undef  DEBUG
/* #define  DEBUG */
#undef  debug

#ifdef DEBUG
# define debug(xx)	fprintf(stderr,xx)
#else
# define debug(xx)
#endif

static Image *image;    /* xloadimage image being returned */

static char *filename;
static jmp_buf setjmp_buffer;	/* for return to caller */
ZFILE * zinput_file;	/* tells input routine where to read JPEG */
static JOCTET jpeg_read_buff[1024 * 16];

/*
 * source manager
 */
static void init_source( j_decompress_ptr cinfo)
{

    debug("init_source()");
}

static boolean fill_input_buffer( j_decompress_ptr cinfo)
{
    struct jpeg_source_mgr *src = cinfo->src;

    debug("fill_input_buffer()");
#ifdef DEBUG
    fprintf( stderr,"fill_input_buffer(): %d  ",src->bytes_in_buffer);
#endif
    src->next_input_byte = jpeg_read_buff;
    src->bytes_in_buffer = zread(zinput_file,
				 jpeg_read_buff, sizeof(jpeg_read_buff));
    if(src->bytes_in_buffer <= 0){
	WARNMS(cinfo, JWRN_JPEG_EOF);
	jpeg_read_buff[0] = 0xFF;
	jpeg_read_buff[1] = JPEG_EOI;
	src->bytes_in_buffer = 2;
    }
    return TRUE;
}

static void skip_input_data( j_decompress_ptr cinfo, long num_bytes)
{
    int rest;
    struct jpeg_source_mgr *src = cinfo->src;

    debug("skip_input_data()");
#ifdef DEBUG
    fprintf(stderr,": %ld,%d  ", num_bytes, src->bytes_in_buffer);
#endif
    if( num_bytes < 1) return;
    rest = src->bytes_in_buffer;
    if( num_bytes < rest) {
	src->next_input_byte += num_bytes;
	src->bytes_in_buffer -= num_bytes;;
	return;
    }
    num_bytes -= rest;
    while( num_bytes--) {
	zgetc(zinput_file);
    }
    fill_input_buffer(cinfo);
}

static boolean resync_to_restart( j_decompress_ptr cinfo, int desired)
{
    return jpeg_resync_to_restart( cinfo, desired);
}

static void term_source( j_decompress_ptr cinfo)
{
    debug("term_source()");
}

/*
 *  error manager
 */
static void
output_message ( j_common_ptr cominfo)
{
    char buf[JMSG_LENGTH_MAX];

    (*cominfo->err->format_message)(cominfo, buf);
    fprintf(stderr, "jpegLoad: %s - %s\n", filename, buf);
}


static void error_exit (j_common_ptr cominfo)
{
    output_message( cominfo);
    longjmp(setjmp_buffer, 1);	/* return control to outer routine */
}


static void
jpegInfo (cinfo)
     j_decompress_ptr cinfo;
{
    /* Create display of image parameters */
    printf("%s is a %dx%d JPEG image, color space ", filename,
	   cinfo->image_width, cinfo->image_height);
    switch (cinfo->jpeg_color_space) {
    case JCS_GRAYSCALE:
	printf("Grayscale");
	break;
    case JCS_RGB:
	printf("RGB");
	break;
    case JCS_YCbCr:
	printf("YCbCr");
	break;
    case JCS_CMYK:
	printf("CMYK");
	break;
    case JCS_YCCK:
	printf("YCCK");
	break;
    case JCS_UNKNOWN:
    default:
	printf("Unknown");
	break;
    }
    printf(", %d comp%s,", cinfo->num_components,
	   (cinfo->num_components - 1) ? "s" : "");
    if (cinfo->progressive_mode)
	printf(" Progressive,");
    if (cinfo->arith_code)
	printf(" Arithmetic coding.\n");
    else
	printf(" Huffman coding.\n");
}


/* Main control routine for loading */

Image *
jpegLoad (fullname, name, verbose)
     char *fullname, *name;
     unsigned int verbose;
{
    struct jpeg_decompress_struct cinfo;
    struct jpeg_source_mgr src_mgr;
    struct jpeg_error_mgr err_mgr;
    int i, row_stride;
    byte *bufp;

    zinput_file = zopen(fullname);	/* Open the input file */
    if (zinput_file == NULL)
	return NULL;
    filename = name;		/* copy parms to static vars */
    image = NULL;		/* in case we fail before creating image */

    jpeg_create_decompress(&cinfo);
    src_mgr.init_source = init_source;
    src_mgr.fill_input_buffer = fill_input_buffer;
    src_mgr.skip_input_data = skip_input_data;
    src_mgr.resync_to_restart = resync_to_restart;
    src_mgr.term_source = term_source;
    cinfo.src = &src_mgr;	/* links to method structs */
    err_mgr.error_exit = error_exit; /* supply error-exit routine */
    err_mgr.output_message = output_message;
    err_mgr.trace_level = 0;	/* default = no tracing */
    err_mgr.num_warnings = 0;	/* no warnings emitted yet */
    cinfo.err = jpeg_std_error(&err_mgr);

    src_mgr.bytes_in_buffer = 0;
    fill_input_buffer( &cinfo);
    /* Quick check to see if file starts with JPEG SOI marker */
    if(jpeg_read_buff[0] != 0xFF || jpeg_read_buff[1] != 0xD8) {
	zclose(zinput_file);
	return NULL;
    }

    /* prepare setjmp context for possible exit from error_exit */
    if (setjmp(setjmp_buffer)) {
	/* If we get here, the JPEG code has signaled an error. */
	/* Return as much of the image as we could get. */
	jpeg_destroy_decompress(&cinfo);
	zclose(zinput_file);
	return image;
    }
      
    jpeg_read_header(&cinfo, TRUE);
    if (verbose) jpegInfo(&cinfo);
    /* Turn off caching beyond this point of the file */
    znocache(zinput_file);
    jpeg_start_decompress(&cinfo);

    switch (cinfo.out_color_space) {
    case JCS_GRAYSCALE:
	image = newRGBImage(cinfo.image_width,cinfo.image_height,8);
	image->title = dupString(filename);
	/* set a linear map */
	for(i=0;i<256;i++) {
	    *(image->rgb.red + i) = 
		*(image->rgb.green + i) = 
		*(image->rgb.blue + i) = i << 8;
	}
	image->rgb.used = 256;
	break;
    case JCS_RGB:
	image = newTrueImage(cinfo.image_width,cinfo.image_height);
	image->title = dupString(filename);
	break;
    default:
	image = NULL;
	ERREXITS(&cinfo, 1, "Cannot cope with JPEG image colorspace");
    }

    row_stride = cinfo.output_width * cinfo.output_components;
    bufp = image->data;
    while (cinfo.output_scanline < cinfo.output_height) {
	jpeg_read_scanlines(&cinfo, &bufp, 1);
	bufp += row_stride;
    }

    jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    zclose(zinput_file);		/* Done, close the input file */

    return image;
}


/*
  Main control routine for identifying JPEG without loading
  return 0: Not jpeg file.
  */
int
jpegIdent (fullname, name)
     char *fullname, *name;
{
    struct jpeg_decompress_struct cinfo;
    struct jpeg_source_mgr src_mgr;
    struct jpeg_error_mgr err_mgr;

    zinput_file = zopen(fullname);	/* Open the input file */
    if (zinput_file == NULL)
	return 0;

    filename = name;		/* copy parms to static vars */

    jpeg_create_decompress(&cinfo);
    src_mgr.init_source = init_source;
    src_mgr.fill_input_buffer = fill_input_buffer;
    src_mgr.skip_input_data = skip_input_data;
    src_mgr.resync_to_restart = resync_to_restart;
    src_mgr.term_source = term_source;
    cinfo.src = &src_mgr;	/* links to method structs */
    err_mgr.error_exit = error_exit; /* supply error-exit routine */
    err_mgr.output_message = output_message;
    err_mgr.trace_level = 0;	/* default = no tracing */
    err_mgr.num_warnings = 0;	/* no warnings emitted yet */
    cinfo.err = jpeg_std_error(&err_mgr);

    src_mgr.bytes_in_buffer = 0;
    fill_input_buffer( &cinfo);
    /* Quick check to see if file starts with JPEG SOI marker */
    if(jpeg_read_buff[0] != 0xFF || jpeg_read_buff[1] != 0xD8) {
	jpeg_destroy_decompress(&cinfo);
	zclose(zinput_file);
	return 0;
    }

    /* prepare setjmp context for expected exit via longjmp */
    if (setjmp(setjmp_buffer)) {
	/* If we get here, the JPEG code has signaled an error. */
	/* Return 0 since error in the headers means the image is unloadable. */
	jpeg_destroy_decompress(&cinfo);
	zclose(zinput_file);
	return 0;
    }

    jpeg_read_header(&cinfo, TRUE);
    jpegInfo(&cinfo);
    /* Turn off caching beyond this point of the file */
    znocache(zinput_file);
    jpeg_destroy_decompress(&cinfo);
    zclose(zinput_file);

    return 1;
}

/*
 * Dump Jpeg
 */

/* parse options passed to jpegDump
 */
static void parseOptions(j_compress_ptr cinfo, char *options, int verbose)
{
  char *name, *value;

  while (getNextTypeOption(&options, &name, &value) > 0) {
    if (!strncmp("arithmetic", name, strlen(name))) {
      /* Use arithmetic coding. */
#ifdef C_ARITH_CODING_SUPPORTED
      if (verbose)
	printf("  Using arithmetic coding.\n");
      cinfo->arith_code = TRUE;
#else
      fprintf(stderr, "jpegDump: sorry, arithmetic coding not supported\n");
#endif
    }
    else if (!strncmp("grayscale", name, strlen(name)) ||
	     !strncmp("greyscale", name, strlen(name)) ||
	     !strncmp("monochrome", name, strlen(name))) {
      /* Force a monochrome JPEG file to be generated. */
      if (verbose)
	printf("  Creating a grayscale/monochrome file.\n");
      jpeg_set_colorspace(cinfo, JCS_GRAYSCALE);
    }
    else if (!strncmp("nointerleave", name, strlen(name))) {
      /* Create noninterleaved file. */
#ifdef C_MULTISCAN_FILES_SUPPORTED
      if (verbose)
	printf("  Creating a noninterleaved file.\n");
      cinfo->interleave = FALSE;
#else
      fprintf(stderr, "jpegDump: sorry, multiple-scan support was not compiled\n");
#endif
    }
    else if (!strncmp("progressive", name, strlen(name))) {
      /* Enable progressive JPEG. */
      if (verbose)
	printf("  Progressive JPEG.\n");
      jpeg_simple_progression (cinfo);
    }
    else if (!strncmp("optimize", name, strlen(name)) ||
	     !strncmp("optimise", name, strlen(name))) {
      /* Enable entropy parm optimization. */
      if (verbose)
	printf("  Optimizing entropy.\n");
      cinfo->optimize_coding = TRUE;
    }
    else if (!strncmp("quality", name, strlen(name))) {
      /* Quality factor (quantization table scaling factor). */
      int val;

      if (!value || (sscanf(value, "%d", &val) != 1))
	fprintf(stderr, "jpegDump: quality: Invalid quality factor specified.\n");
      else {
	/* Set quantization tables (will be overridden if -qtables also given).
	 * Note: we make force_baseline FALSE.
	 * This means non-baseline JPEG files can be created with low Q values.
	 * To ensure only baseline files are generated, pass TRUE instead.
	 */
	if (verbose)
	  printf("  Using a quality factor of %d.\n", val);
	jpeg_set_quality(cinfo, val, FALSE);
#if 0
	/* Change scale factor in case -qtables is present. */
	q_scale_factor = j_quality_scaling(val);
#endif
      }
    }
#if 0
    else if (!strncmp("qtables", name, strlen(name))) {
      /* Quantization tables fetched from file. */
      if (!value || !*value)
	fprintf(stderr, "jpegDump: No file specified for quantization tables.\n");
      else {
	if (verbose)
	  printf("  Using quantization tables loaded from %s.\n", qtablefile);
	qtablefile = value;
      }
      /* we postpone actually reading the file in case -quality comes later */
    }
#endif
    else if (!strncmp("restart", name, strlen(name))) {
      /* Restart interval in MCU rows (or in MCUs with 'b'). */
      long lval;
      char ch = 'x';

      if (!value || (sscanf(value, "%ld%c", &lval, &ch) < 1) ||
	  (lval < 0) || (lval > 65535L)) {
	fprintf(stderr, "jpegDump: restart: Invalid restart interval specified.\n");
	continue;
      }
      if (verbose)
	printf("  Using a restart interval of %s.\n", value);
      if (ch == 'b' || ch == 'B')
	cinfo->restart_interval = (UINT16) lval;
      else
	cinfo->restart_in_rows = (int) lval;
    }
#if 0
    else if (!strncmp("sample", name, strlen(name))) {
      /* Set sampling factors. */
      if (!value)
	fprintf(stderr, "jpegDump: sample: No sampling factors specified.\n");
      if (verbose)
	printf("  Using sampling factors of %s.\n", value);
      set_sample_factors(cinfo, value);
    }
#endif
    else if (!strncmp("smooth", name, strlen(name))) {
      /* Set input smoothing factor. */
      int val;

      if (!value || (sscanf(value, "%d", &val) != 1) ||
	  (val < 0) || (val > 100))
	fprintf(stderr, "jpegDump: smooth: Invalid smoothing factor specified.\n");
      else {
	if (verbose)
	  printf("  Using a smoothing factor of %d.\n", val);
	cinfo->smoothing_factor = val;
      }
    }
    else
      fprintf(stderr, "jpegDump: Unknown option '%s'.\n", name);
  }
}

/* this reads a single raster line
 */

byte *current_row;
unsigned int bytes_per_row;

static byte *read_row(Image *image, byte *pixel_rows)
{
    int x;
    int pixlen;
    byte *src_row_ptr = current_row;
    byte *dest_row_ptr = pixel_rows;
    Pixel pixval;
    byte mask;

    switch (image->type) {
    case IBITMAP:
	mask = 0x80;
	for (x = 0; x < image->width; x++) {
	    pixval = ((*src_row_ptr & mask) > 0 ? 1 : 0);

	    /* we use the "red" color value under the assumption that they
	     * are all equal.  that can be wrong if the user used -foreground
	     * or -background.  I don't care right now.
	     */
	    *dest_row_ptr++ = image->rgb.red[pixval] >> 8;
	    mask >>= 1;
	    if (mask == 0) {
		mask = 0x80;
		src_row_ptr++;
	    }
	}
	break;

    case IRGB:
	/* this expands the pixel value into its components
	 */
	pixlen = image->pixlen;
	for (x = 0; x < image->width; x++) {
	    pixval = memToVal(src_row_ptr, pixlen);
	    *dest_row_ptr++ = image->rgb.red[pixval] >> 8;
	    *dest_row_ptr++ = image->rgb.green[pixval] >> 8;
	    *dest_row_ptr++ = image->rgb.blue[pixval] >> 8;
	    src_row_ptr += pixlen;
	}
	break;

    case ITRUE:
	return current_row;
	break;
    }
    return pixel_rows;
}

void jpegDump(Image *image, char *options, char *file, int verbose)
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE * outfile;		/* target file */
    JSAMPROW row_pointer[1];	/* pointer to JSAMPLE row[s] */

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

    if ((outfile = fopen(file, "w")) == NULL) {
	perror(file);
	return;
    }
    jpeg_stdio_dest(&cinfo, outfile);

    cinfo.image_width = image->width;
    cinfo.image_height = image->height;

    /* set # of color components per pixel & colospace fo input image */
    switch (image->type) {
    case IBITMAP:
	bytes_per_row = (image->width / 8) + (image->width % 8 ? 1 : 0);
	cinfo.input_components = 1;
	cinfo.in_color_space = JCS_GRAYSCALE;
	row_pointer[0] = lmalloc( cinfo.image_width * cinfo.input_components);
	break;
    case IRGB:
	bytes_per_row = image->width * image->pixlen;
	cinfo.input_components = 3;
	cinfo.in_color_space = JCS_RGB;    /* colorspace of input image */
	row_pointer[0] = lmalloc( cinfo.image_width * cinfo.input_components);
	break;
    case ITRUE:
	bytes_per_row = image->width * image->pixlen;
	cinfo.input_components = 3;
	cinfo.in_color_space = JCS_RGB;    /* colorspace of input image */
	row_pointer[0] = image->data;
	break;
    }
    /* Now use the library's routine to set default compression parameters.
     * (You must set at least cinfo.in_color_space before calling this,
     * since the defaults depend on the source color space.)
     */
    jpeg_set_defaults(&cinfo);
    /* Now you can set any non-default parameters you wish to.
     * Here we just illustrate the use of quality (quantization table) scaling:
     */
    jpeg_set_quality(&cinfo, 75, TRUE /* limit to baseline-JPEG values */);
    if( cinfo.in_color_space == JCS_GRAYSCALE)
	jpeg_set_colorspace(&cinfo, JCS_GRAYSCALE);
    parseOptions(&cinfo, options, verbose);

    jpeg_start_compress(&cinfo, TRUE);
  
    current_row = image->data;
    while (cinfo.next_scanline < cinfo.image_height) {
	/* jpeg_write_scanlines expects an array of pointers to scanlines.
	 * Here the array is only one element long, but you could pass
	 * more than one scanline at a time if that's more convenient.
	 */
	row_pointer[0] = read_row(image, row_pointer[0]);
	(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
	current_row += bytes_per_row;
    }

    jpeg_finish_compress(&cinfo);
    fclose(outfile);
    jpeg_destroy_compress(&cinfo);
    if (image->type == IBITMAP || image->type == IRGB) {
	lfree( row_pointer[0]);
    }
}

#else /* !HAVE_LIBJPEG */
static int unused;
#endif /* !HAVE_LIBJPEG */
