/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 *
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 *
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/*
 *	  jpeg.c --- Glue code to Independent JPEG Group decoder library
 *    $Id: jpeg.c,v 3.1 1998/03/28 03:35:03 ltabb Exp $
 */


/*
#include "xp.h"
*/

#include "if.h"

#include "xp_core.h"
#include "merrors.h"

#include "il.h"

#include <ctype.h>		/* to declare isprint() */
#ifndef XP_MAC
#	 include <sys/types.h>
#endif

#include "jinclude.h"
#include "jpeglib.h"
#include "jerror.h"

extern int MK_OUT_OF_MEMORY;

#ifdef XP_OS2_HACK
/* IBM-MAS: We removed setjmp.h from XP_core.h, now we need it here. */
/*          We need to see if we can fix hwthreads/XP_CORE correctly.. */
#include <setjmp.h>
#endif

#ifdef PROFILE
#	 pragma profile on
#endif


/* Normal JFIF markers can't have more bytes than this. */
#define MAX_JPEG_MARKER_LENGTH	(((uint32)1 << 16) - 1)

/*
 * States that the jpeg decoder might be in
 */
typedef enum {
	JPEG_HEADER,                          /* Reading JFIF headers */
    JPEG_START_DECOMPRESS,
	JPEG_DECOMPRESS_PROGRESSIVE,          /* Output progressive pixels */
	JPEG_DECOMPRESS_SEQUENTIAL,           /* Output sequential pixels */
    JPEG_FINAL_PROGRESSIVE_SCAN_OUTPUT,
	JPEG_DONE,
    JPEG_SINK_NON_JPEG_TRAILER           /* Some image files have a */
                                         /* non-JPEG trailer */
} jstate;
	
typedef struct {
	struct jpeg_error_mgr pub;	/* "public" fields for IJG library*/
	jmp_buf setjmp_buffer;		/* For handling catastropic errors */
} il_error_mgr;

/*
 * Structure used to manage the JPEG decoder stream.
 */
typedef struct jpeg_struct {
	jstate state;				/* Decoder FSM state */
    int pass_num;
    int completed_output_passes;

    int rows_per_chunk;
    void *timeout;
    
	/* One scanline's worth of post-processed sample data */
	JSAMPARRAY samples;
	JSAMPARRAY samples3;
	/* IJG JPEG library decompressor state */
	struct jpeg_decompress_struct jd;
	il_error_mgr jerr;
	il_container *ic;
} jpeg_struct;




/* Possible states for JPEG source manager */
enum data_source_state {
	dss_consuming_backtrack_buffer = 0, /* Must be zero for init purposes */
	dss_consuming_netlib_buffer
};

/*
 *	Implementation of a JPEG src object that understands our state machine
 */
typedef struct {
	/* public fields; must be first in this struct! */
	struct jpeg_source_mgr pub;

	jpeg_struct *js;              /* pointer to netlib stream object */

	int bytes_to_skip;            /* remaining bytes to skip */

	enum data_source_state state;

	JOCTET *netlib_buffer;        /* next buffer for fill_input_buffer */
	uint32 netlib_buflen;

	/*
	 * Buffer of "remaining" characters left over after a call to 
	 * fill_input_buffer(), when no additional data is available.
	 */ 
	JOCTET *backtrack_buffer;
	size_t backtrack_buffer_size; /* Allocated size of backtrack_buffer		*/
	size_t backtrack_buflen;	  /* Offset of end of active backtrack data */
	size_t backtrack_num_unread_bytes; /* Length of active backtrack data	*/
} il_source_mgr;


/* Override the standard error method in the IJG JPEG decoder code. */
void PR_CALLBACK
il_error_exit (j_common_ptr cinfo)
{
    int error_code;
	il_error_mgr *err = (il_error_mgr *) cinfo->err;

#ifdef DEBUG
    if (il_debug >= 1) {
        char buffer[JMSG_LENGTH_MAX];

        /* Create the message */
        (*cinfo->err->format_message) (cinfo, buffer);

        XP_TRACE(("%s\n", buffer));
    }
#endif

    /* Convert error to a browser error code */
    if (cinfo->err->msg_code == JERR_OUT_OF_MEMORY)
        error_code = MK_OUT_OF_MEMORY;
    else
        error_code = MK_IMAGE_LOSSAGE;
        
	/* Return control to the setjmp point. */
	longjmp(err->setjmp_buffer, error_code);
}


void PR_CALLBACK
init_source (j_decompress_ptr jd)
{
}

/*-----------------------------------------------------------------------------
 * This is the callback routine from the IJG JPEG library used to supply new
 * data to the decompressor when its input buffer is exhausted.	 It juggles
 * multiple buffers in an attempt to avoid unnecessary copying of input data.
 *
 * (A simpler scheme is possible: It's much easier to use only a single
 * buffer; when fill_input_buffer() is called, move any unconsumed data
 * (beyond the current pointer/count) down to the beginning of this buffer and
 * then load new data into the remaining buffer space.	This approach requires
 * a little more data copying but is far easier to get right.)
 *
 * At any one time, the JPEG decompressor is either reading from the netlib
 * input buffer, which is volatile across top-level calls to the IJG library,
 * or the "backtrack" buffer.  The backtrack buffer contains the remaining
 * unconsumed data from the netlib buffer after parsing was suspended due
 * to insufficient data in some previous call to the IJG library.
 *
 * When suspending, the decompressor will back up to a convenient restart
 * point (typically the start of the current MCU). The variables
 * next_input_byte & bytes_in_buffer indicate where the restart point will be
 * if the current call returns FALSE.  Data beyond this point must be
 * rescanned after resumption, so it must be preserved in case the decompressor
 * decides to backtrack.
 *
 * Returns:
 *	TRUE if additional data is available, FALSE if no data present and
 *	 the JPEG library should therefore suspend processing of input stream
 *---------------------------------------------------------------------------*/
boolean PR_CALLBACK
fill_input_buffer (j_decompress_ptr jd)
{
	il_source_mgr *src = (il_source_mgr *)jd->src;
	enum data_source_state data_source_state = src->state;
    uint32 bytesToSkip, new_backtrack_buflen, new_buflen, roundup_buflen;
	unsigned char *new_buffer;

	ILTRACE(5,("il:jpeg: fill, state=%d, nib=0x%x, bib=%d", data_source_state,
               src->pub.next_input_byte, src->pub.bytes_in_buffer));

	switch (data_source_state) {

	/* Decompressor reached end of backtrack buffer. Return netlib buffer.*/
	case dss_consuming_backtrack_buffer:
		new_buffer = (unsigned char *)src->netlib_buffer;
		new_buflen = src->netlib_buflen;
        if ((new_buffer == NULL) || (new_buflen == 0))
            goto suspend;

        /*
         * Clear, so that repeated calls to fill_input_buffer() do not
         * deliver the same netlib buffer more than once.
         */
        src->netlib_buflen = 0;
        
		/* Discard data if asked by skip_input_data(). */
		bytesToSkip = src->bytes_to_skip;
		if (bytesToSkip) {
			if (bytesToSkip < new_buflen) {
				/* All done skipping bytes; Return what's left. */
				new_buffer += bytesToSkip;
				new_buflen -= bytesToSkip;
				src->bytes_to_skip = 0;
			} else {
				/* Still need to skip some more data in the future */
				src->bytes_to_skip -= (size_t)new_buflen;
                goto suspend;
			}
		}

		/* Save old backtrack buffer parameters, in case the decompressor
		 * backtracks and we're forced to restore its contents.
		 */
		src->backtrack_num_unread_bytes = src->pub.bytes_in_buffer;

		src->pub.next_input_byte = new_buffer;
		src->pub.bytes_in_buffer = (size_t)new_buflen;
		src->state = dss_consuming_netlib_buffer;
		return TRUE;

	/* Reached end of netlib buffer. Suspend */
	case dss_consuming_netlib_buffer:
		if (src->pub.next_input_byte != src->netlib_buffer) {
			/* Backtrack data has been permanently consumed. */
			src->backtrack_num_unread_bytes = 0;
			src->backtrack_buflen = 0;
		}

		/* Save remainder of netlib buffer in backtrack buffer */
		new_backtrack_buflen = src->pub.bytes_in_buffer + src->backtrack_buflen;
				
		/* Make sure backtrack buffer is big enough to hold new data. */
		if (src->backtrack_buffer_size < new_backtrack_buflen) {

			/* Round up to multiple of 16 bytes. */
			roundup_buflen = ((new_backtrack_buflen + 15) >> 4) << 4;
			if (src->backtrack_buffer_size) {
				src->backtrack_buffer =
					(JOCTET *)XP_REALLOC(src->backtrack_buffer, roundup_buflen);
			} else {
				src->backtrack_buffer = (JOCTET*)XP_ALLOC(roundup_buflen);
			}

            /* Check for OOM */
            if (! src->backtrack_buffer) {
                j_common_ptr cinfo = (j_common_ptr)(&src->js->jd);
                cinfo->err->msg_code = JERR_OUT_OF_MEMORY;
				il_error_exit(cinfo);
            }
                
			src->backtrack_buffer_size = (size_t)roundup_buflen;

			/* Check for malformed MARKER segment lengths. */
			if (new_backtrack_buflen > MAX_JPEG_MARKER_LENGTH)
				il_error_exit((j_common_ptr)(&src->js->jd));
		}

		/* Copy remainder of netlib buffer into backtrack buffer. */
		XP_BCOPY (src->pub.next_input_byte,
				  src->backtrack_buffer + src->backtrack_buflen,
				  src->pub.bytes_in_buffer);

		/* Point to start of data to be rescanned. */
		src->pub.next_input_byte = src->backtrack_buffer +
			src->backtrack_buflen - src->backtrack_num_unread_bytes;
		src->pub.bytes_in_buffer += src->backtrack_num_unread_bytes;
		src->backtrack_buflen = (size_t)new_backtrack_buflen;
		
		src->state = dss_consuming_backtrack_buffer;
        goto suspend;
			
	default:
		XP_ASSERT(0);
        return FALSE;
	}

  suspend:
    ILTRACE(5,("         Suspending, bib=%d", src->pub.bytes_in_buffer));
    return FALSE;
}

void PR_CALLBACK
skip_input_data (j_decompress_ptr jd, long num_bytes)
{
	il_source_mgr *src = (il_source_mgr *)jd->src;

#ifdef DEBUG
    ILTRACE(5, ("il:jpeg: skip_input_data js->buf=0x%x js->buflen=%d skip=%d",
                src->netlib_buffer, src->netlib_buflen,
                num_bytes));
#endif

	if (num_bytes > (long)src->pub.bytes_in_buffer) {
		/*
		 * Can't skip it all right now until we get more data from
		 * network stream. Set things up so that fill_input_buffer
		 * will skip remaining amount.
		 */
		src->bytes_to_skip = (size_t)num_bytes - src->pub.bytes_in_buffer;
		src->pub.next_input_byte += src->pub.bytes_in_buffer;
		src->pub.bytes_in_buffer = 0;
	} else {
		/* Simple case. Just advance buffer pointer */
		src->pub.bytes_in_buffer -= (size_t)num_bytes;
		src->pub.next_input_byte += num_bytes;
	}
}


/*
 * Terminate source --- called by jpeg_finish_decompress() after all
 * data has been read to clean up JPEG source manager.
 */
void PR_CALLBACK
term_source (j_decompress_ptr jd)
{
	/* No work necessary here */
}


/*-----------------------------------------------------------------------------
 * Setup a JPEG source object for streaming data in a demand-driven
 * fashion into the IJG JPEG decompression library.	 A JPEG source
 * object consists of a set of callback functions which the
 * decompressor library calls when it needs more data or to seek ahead
 * in the input stream.
 *
 * Returns:
 *	TRUE if setup succeeds, FALSE otherwise.
 *---------------------------------------------------------------------------*/
static int
setup_jpeg_src (j_decompress_ptr jd, jpeg_struct *js)
{
	il_source_mgr *src;

	if (jd->src == NULL) {
		src = XP_NEW_ZAP(il_source_mgr);
		if (!src) {
			XP_TRACE(("il:jpeg: src manager memory lossage"));
			return FALSE;
		}
		jd->src = (struct jpeg_source_mgr *) src;
	}

	src = (il_source_mgr *)jd->src;
	src->js = js;

	/* Setup callback functions. */
	src->pub.init_source = init_source;
	src->pub.fill_input_buffer = fill_input_buffer;
	src->pub.skip_input_data = skip_input_data;
	src->pub.resync_to_restart = jpeg_resync_to_restart;
	src->pub.term_source = term_source;

	return TRUE;
}



/*
 * Macros for fetching data from the data source module.
 *
 * At all times, cinfo->src->next_input_byte and ->bytes_in_buffer reflect
 * the current restart point; we update them only when we have reached a
 * suitable place to restart if a suspension occurs.
 */

/* Declare and initialize local copies of input pointer/count */
#define INPUT_VARS(cinfo)  \
	struct jpeg_source_mgr * datasrc = (cinfo)->src;  \
	const JOCTET * next_input_byte = datasrc->next_input_byte;  \
	size_t bytes_in_buffer = datasrc->bytes_in_buffer

/* Unload the local copies --- do this only at a restart boundary */
#define INPUT_SYNC(cinfo)  \
	( datasrc->next_input_byte = next_input_byte,  \
	  datasrc->bytes_in_buffer = bytes_in_buffer )

/* Reload the local copies --- seldom used except in MAKE_BYTE_AVAIL */
#define INPUT_RELOAD(cinfo)  \
	( next_input_byte = datasrc->next_input_byte,  \
	  bytes_in_buffer = datasrc->bytes_in_buffer )

/* Internal macro for INPUT_BYTE and INPUT_2BYTES: make a byte available.
 * Note we do *not* do INPUT_SYNC before calling fill_input_buffer,
 * but we must reload the local copies after a successful fill.
 */
#define MAKE_BYTE_AVAIL(cinfo,action)  \
	if (bytes_in_buffer == 0) {  \
	  if (! (*datasrc->fill_input_buffer) (cinfo))  \
	    { action; }  \
	  INPUT_RELOAD(cinfo);  \
	}  \
	bytes_in_buffer--

/* Read a byte into variable V.
 * If must suspend, take the specified action (typically "return FALSE").
 */
#define INPUT_BYTE(cinfo,V,action)  \
	MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \
		  V = GETJOCTET(*next_input_byte++); )

/* As above, but read two bytes interpreted as an unsigned 16-bit integer.
 * V should be declared unsigned int or perhaps INT32.
 */
#define INPUT_2BYTES(cinfo,V,action)  \
	MAKESTMT( MAKE_BYTE_AVAIL(cinfo,action); \
		  V = ((unsigned int) GETJOCTET(*next_input_byte++)) << 8; \
		  MAKE_BYTE_AVAIL(cinfo,action); \
		  V += GETJOCTET(*next_input_byte++); )


/* Process the COM marker segment, which contains human-readable comments. */
boolean PR_CALLBACK
il_jpeg_COM_handler (j_decompress_ptr cinfo)
{
    uint length;
    char *comment;
    unsigned int ch;

    il_source_mgr *src = (il_source_mgr*) cinfo->src;
    il_container *ic = src->js->ic;
    static unsigned int lastch = 0;

    INPUT_VARS(cinfo);

    /* Get 16-bit comment length word. */
    INPUT_2BYTES(cinfo, length, return FALSE);
    length -= 2;			/* discount the length word itself */
  
    if (ic->comment)
        XP_FREE(ic->comment);
    comment = ic->comment = (char *)XP_ALLOC(length + 1);
    ic->comment_length = length;
    
    if (!ic->comment) {
        skip_input_data(cinfo, length + 2);
        return TRUE;
    }

    /* Emit the character in a readable form.
     * Newlines in CR, CR/LF, or LF form will be printed as one newline.
     */
    while (length-- > 0) {
        INPUT_BYTE(cinfo, ch, return FALSE);

        if (ch == '\r') {
            *comment++ = '\n';
        } else if (ch == '\n') {
            if (lastch != '\r')
                *comment++ = '\n';
        } else if (isprint(ch) || !ch) {
            *comment++ = ch;
        }
        lastch = ch;
    }

    *comment = 0;

    INPUT_SYNC(cinfo);

    return TRUE;
}

int il_jpeg_init(il_container *ic)
{
	jpeg_struct *js;
	j_decompress_ptr jd;
    NI_ColorSpace *src_color_space = ic->src_header->color_space;
    NI_RGBBits *rgb = &src_color_space->bit_alloc.rgb;

	js = XP_NEW_ZAP(jpeg_struct);
	if (!js) {
		XP_TRACE(("il:jpeg: jpeg_struct memory lossage"));
		return FALSE;
	}

	/* Init jpeg_struct */
	ic->ds = js;
	js->state = JPEG_HEADER;
	js->samples = NULL;
	js->samples3 = NULL;
	js->ic = ic;

	jd = &js->jd;

	/* Install our error handler because the default is to exit(). */
	jd->err = jpeg_std_error(&js->jerr.pub);
	js->jerr.pub.error_exit = il_error_exit;

	/* Control returns here if an error occurs before setup completes. */
	if(setjmp(js->jerr.setjmp_buffer)) {
        /* Free up all the data structures */
        il_jpeg_abort(ic);
		return FALSE;
	}

#ifdef DEBUG
	if (il_debug > 20) {
		jd->err->trace_level = 99;
	}
#endif

	
	jpeg_create_decompress(jd);

	/* Setup jpeg data source object */
	if (!setup_jpeg_src(jd, js)) {
		XP_TRACE(("il:jpeg: jpeg source memory lossage"));
        /* Free up all the data structures */
        il_jpeg_abort(ic);
		return FALSE;
	}

    /* Insert custom COM comment marker processor. */
    jpeg_set_marker_processor(jd, JPEG_COM, il_jpeg_COM_handler);


    /* Initialize the container's source image header. */
    src_color_space->type = NI_TrueColor;
    src_color_space->pixmap_depth = 24;
    rgb->red_bits = 8;
    rgb->red_shift = 16;
    rgb->green_bits = 8;
    rgb->green_shift = 8;
    rgb->blue_bits = 8;
    rgb->blue_shift = 0;

	return TRUE;
}


/*-----------------------------------------------------------------------------
 * Calling this routine sends scanlines to the front-end for display.
 * Scanlines will be emitted until the entire scan has been displayed or
 * until insufficient input data is available to continue output.
 *
 * The maximum number of scanlines to be output is controlled by the
 * `num_scanlines' parameter.  Set num_scanlines to -1 to output scanlines
 * until input data is exhausted.
 *
 * Returns:
 *	TRUE if output was discontinued due to lack of input data
 *  FALSE, otherwise
 *---------------------------------------------------------------------------*/
int
output_jpeg_scanlines(il_container *ic, int num_scanlines)
{
	jpeg_struct *js = (jpeg_struct *)ic->ds;
	j_decompress_ptr jd = &js->jd;
    int input_exhausted;
    int pass;
    
#ifdef DEBUG
    uint start_scanline = jd->output_scanline;
#endif

    if (js->state == JPEG_FINAL_PROGRESSIVE_SCAN_OUTPUT)
        pass = IL_FINAL_PASS;
    else
        pass = js->completed_output_passes + 1;

    while ((jd->output_scanline < jd->output_height) && num_scanlines--) {
        JSAMPROW samples;
        
        /* Request one scanline.  Returns 0 or 1 scanlines. */
        int ns = jpeg_read_scanlines(jd, js->samples, 1);
        ILTRACE(15,("il:jpeg: scanline %d, ns = %d",
                    jd->output_scanline, ns));
        if (ns != 1) {
            ILTRACE(5,("il:jpeg: suspending scanline"));
            input_exhausted = TRUE;
            goto done;
        }

        /* If grayscale image ... */
        if (jd->output_components == 1) {
            JSAMPLE j, *j1, *j1end, *j3;

            /* Convert from grayscale to RGB. */
            j1 = js->samples[0];
            j1end = j1 + jd->output_width;
            j3 = js->samples3[0];
            while (j1 < j1end) {
                j = *j1++;
                j3[0] = j;
                j3[1] = j;
                j3[2] = j;
                j3 += 3;
            }
            samples = js->samples3[0];
        } else {		/* 24-bit color image */
            samples = js->samples[0];
        }

        il_emit_row(ic, 0, samples, 0, jd->output_width, jd->output_scanline-1,
                    1, ilErase, pass);
    }

    input_exhausted = FALSE;

  done:

#ifdef DEBUG
    if (start_scanline != jd->output_scanline)
        ILTRACE(4, ("il: jpeg: Input pass=%2d, next input scanline=%3d,"
                    " emitted %3d - %3d\n",
                    jd->input_scan_number, jd->input_iMCU_row * 16,
                    start_scanline, jd->output_scanline - 1));

#endif
    
    return input_exhausted;
}

#define JPEG_OUTPUT_CHUNK_SIZE     50000

/* Timeout durations, in milliseconds */

/* Delay between displaying chunks of pixels for the first scan. */
#define JPEG_TIMEOUT_INITIAL_DELAY   100

/* Delay between displaying chunks of pixels for subsequent scans */
#define JPEG_TIMEOUT_DELAY           200

static void
jpeg_timeout_callback(void *closure)
{
    uint32 delay;
	jpeg_struct *js = (jpeg_struct *)closure;
	j_decompress_ptr jd = &js->jd;

    if (jd->input_scan_number == 1)
        delay = JPEG_TIMEOUT_INITIAL_DELAY;
    else
        delay = JPEG_TIMEOUT_DELAY;

    /*
     * Perform incremental display of progressive scans,
     * except don't display unless enough time has elapsed
     * since the previous scan was displayed.
     */

    if (js->pass_num != js->completed_output_passes + 1) {
        if (! jpeg_start_output(jd, jd->input_scan_number)) {
            ILTRACE(1, ("il: jpeg: jpeg_start_output returned"
                        "FALSE!\n"));
            goto done;
        }
        js->pass_num = js->completed_output_passes + 1;
    }
    
    js->timeout = NULL;

    /* If there's no more data to process for this scan,
       wait until jpeg_write() wakes us up by scheduling a
       new timeout */
    if (output_jpeg_scanlines(js->ic, js->rows_per_chunk))
        return;
    
    /* If we're at the end of this progressive scan ... */
    if (jd->output_scanline == jd->output_height) {
        if (jpeg_finish_output(jd))
            js->completed_output_passes++;
    }

  done:
    js->timeout = FE_SetTimeout(jpeg_timeout_callback, js, delay);
}

/*
 * Force the display of scans, even if no data has entered the netlib
 * buffer since jpeg_timeout_callback() was run.  This will flush
 * pixels to the screen during a long pause in a bursty data source.
 */
#if 0
static void
jpeg_idle_callback(void *closure)
{
    il_container *ic = (il_container *)closure;
    il_jpeg_write(ic, NULL, 0);
}
#endif

int
il_jpeg_write(il_container *ic, const unsigned char *buf, int32 len)
{
	int row_stride, status;
	int input_exhausted;
    int error_code;

	jpeg_struct *js = (jpeg_struct *)ic->ds;
	j_decompress_ptr jd = &js->jd;
    il_source_mgr *src = (il_source_mgr*) js->jd.src;
    NI_PixmapHeader *img_header = &ic->image->header;
#ifndef M12N                    /* XXXM12N Get rid of this */
    NI_PixmapHeader *src_header = ic->src_header;
    NI_ColorMap *cmap = &src_header->color_space->cmap;
#endif /* M12N */

    /* If this assert fires, chances are the netlib 
       continued to send data after the image stream was closed. */
    XP_ASSERT(js);
    if (!js) {
#ifdef DEBUG
        XP_TRACE(("Netlib sent data after the image stream was closed\n"));
#endif
        return MK_IMAGE_LOSSAGE;
    }
    
	/* Return here if there is a fatal error. */
	if ((error_code = setjmp(js->jerr.setjmp_buffer))) {
        /* Free up all the data structures */
        il_jpeg_abort(ic);
		return error_code;
	}

	/* Register new buffer contents with data source manager. */
	src->netlib_buffer = (JOCTET*)buf;
	src->netlib_buflen = (uint32)len;

    input_exhausted = 0;
	while (! input_exhausted) {
		ILTRACE(5,("il:jpeg: write, state=%d, buf=0x%x, len=%d",
				   js->state, buf, len));

		switch (js->state) {
		case JPEG_HEADER:
			if (jpeg_read_header(jd, TRUE) != JPEG_SUSPENDED) {
#ifndef M12N                    /* XXXM12N Get rid of this */
				cmap->map = 0;
				cmap->num_colors = ic->cs->default_map_size;
#endif /* M12N */
                ic->src_header->width = jd->image_width;
                ic->src_header->height = jd->image_height;
				if ((status = il_size(ic))) {
					XP_TRACE(("il:jpeg: MEM il_size"));
					return status;
				}

                il_setup_color_space_converter(ic); /* XXXM12N Should check
                                                       return code. */

                js->rows_per_chunk =
                    JPEG_OUTPUT_CHUNK_SIZE / img_header->widthBytes;

				/* FIXME -- Should reset dct_method and dither mode
				 * for final pass of progressive JPEG
				 */
				jd->dct_method = JDCT_FASTEST;
				jd->dither_mode = JDITHER_ORDERED;
				jd->do_fancy_upsampling = FALSE;
				jd->enable_2pass_quant = FALSE;
				jd->do_block_smoothing = TRUE;
				
				/*
				 * Don't allocate a giant and superfluous memory buffer
				 * when the image is a sequential JPEG.
				 */
				jd->buffered_image = jpeg_has_multiple_scans(jd);

				/* Used to set up image size so arrays can be allocated */
				jpeg_calc_output_dimensions(jd);

				/*
				 * Make a one-row-high sample array that will go away
				 * when done with image. Always make it big enough to
				 * hold an RGB row.	 Since this uses the IJG memory
				 * manager, it must be allocated before the call to
				 * jpeg_start_compress().
				 */
				row_stride = jd->output_width * jd->output_components;
				js->samples = (*jd->mem->alloc_sarray)((j_common_ptr) jd,
													   JPOOL_IMAGE,
													   row_stride, 1);

				/* Allocate RGB buffer for conversion from greyscale. */
				if (jd->output_components != 3) {
					row_stride = jd->output_width * 3;
					js->samples3 = (*jd->mem->alloc_sarray)((j_common_ptr) jd,
															JPOOL_IMAGE,
															row_stride, 1);
				}
                js->state = JPEG_START_DECOMPRESS;

			} else {
				ILTRACE(5,("il:jpeg: suspending header"));
				input_exhausted = TRUE;
			}
			break;

        case JPEG_START_DECOMPRESS:
            if (jpeg_start_decompress(jd)) {

                /* If this is a progressive JPEG ... */
                if (jd->buffered_image) {
                    js->state = JPEG_DECOMPRESS_PROGRESSIVE;
                } else {
                    js->state = JPEG_DECOMPRESS_SEQUENTIAL;
                }
            }
                   
            break;

        case JPEG_DECOMPRESS_SEQUENTIAL:
            input_exhausted = output_jpeg_scanlines(ic, -1);

			/* If we've completed image output ... */
			if (jd->output_scanline == jd->output_height)
                js->state = JPEG_DONE;

            break;
            
		case JPEG_DECOMPRESS_PROGRESSIVE:

            /*
             * Set a timeout to trigger display of the next progressive scan.
             * Any scans which arrive in the intervening time will be displayed
             * instead.  Thus, the decoder adapts to the data arrival rate.
             */
            if (js->timeout == NULL) {
                uint32 delay;

                /*
                 * First time around, display the scan a little
                 * quicker than in subsequent scans.
                 */
                if (jd->input_scan_number == 1)
                    delay = JPEG_TIMEOUT_INITIAL_DELAY;
                else {
                    delay = JPEG_TIMEOUT_DELAY;
                }
                
                js->timeout = FE_SetTimeout(jpeg_timeout_callback, js, delay);
            
            }

            /* Eat all the available input data in the netlib buffer. */
            do {
                status = jpeg_consume_input(jd);
            } while (!((status == JPEG_SUSPENDED) ||
                       (status == JPEG_REACHED_EOI)));

            /* If we've parsed the whole input, do final display immediately. */
            if (status == JPEG_REACHED_EOI) {
                js->state = JPEG_FINAL_PROGRESSIVE_SCAN_OUTPUT;
                break;
            }
            
            input_exhausted = TRUE;
            
			break;

		case JPEG_FINAL_PROGRESSIVE_SCAN_OUTPUT:

            if ((jd->input_scan_number == jd->output_scan_number) &&
                (js->pass_num == js->completed_output_passes + 1)) {
                output_jpeg_scanlines(ic, -1);
                jpeg_finish_output(jd);
            } else {

                /* Abort the last output scan.
                 * We need to redraw the whole image.
                 */
                if (js->pass_num == js->completed_output_passes + 1)
                    jpeg_finish_output(jd);

                jpeg_start_output(jd, jd->input_scan_number);
                output_jpeg_scanlines(ic, -1);
                jpeg_finish_output(jd);
            }

            js->state = JPEG_DONE;

            /* Fall through ... */

        case JPEG_DONE:
			status = jpeg_finish_decompress(jd);
            
            /* Clear any pending timeouts */
            if (js->timeout) {
                FE_ClearTimeout(js->timeout);
                js->timeout = NULL;
            }

			input_exhausted = TRUE;
            js->state = JPEG_SINK_NON_JPEG_TRAILER;  /* Be prepared for     */
                                                     /* non-JPEG data after */
                                                     /* EOI marker.         */
            break;

        case JPEG_SINK_NON_JPEG_TRAILER: /* Ignore non-JPEG trailer, if any */
            input_exhausted = TRUE;
            break;

		default:
			XP_ASSERT(0);
			break;
		}
	}

	return 0;
}

void
il_jpeg_abort(il_container *ic)
{
    jpeg_struct *js = (jpeg_struct *)ic->ds;
    
	if (js) {
		il_source_mgr *src = (il_source_mgr*) js->jd.src;

		/*
		 * Free up the memory that we allocated ourselves (not memory we
		 * allocated using the IJG memory manager - it will be freed in
		 * a moment.)
		 */
		if (src) {
			if (src->backtrack_buffer) {
				XP_FREE(src->backtrack_buffer);
				src->backtrack_buffer = NULL;
			}
			XP_FREE(src);
			js->jd.src = NULL;
		}

        /* Clear any pending timeouts */
        if (js->timeout) {
            FE_ClearTimeout(js->timeout);
            js->timeout = NULL;
        }
        
		/*
		 * Free all the ancillary memory used during JPEG decoding by the
		 * IJG JPEG library. This has the side effect of freeing up js->samples
         * and js->samples3 which were allocated using the IJG memory manager.
		 */
        jpeg_destroy_decompress(&js->jd);
		js->samples = NULL;
		js->samples3 = NULL;

		/* Finally, free up our private decoder structure. */
		XP_FREE(js);
		ic->ds = NULL;
	}
}

void
il_jpeg_complete(il_container *ic)
{
    il_jpeg_abort(ic);
    il_image_complete(ic);
    il_frame_complete_notify(ic);
}

#ifdef PROFILE
#	 pragma profile off
#endif

