/*  
** Copyright (c) 1995 Netsurfer Inc.  All Rights Reserved.
**
** Author: <bbum@friday.com>
*/

/*  This object is included in the MiscKit by permission from the author
**  and its use is governed by the MiscKit license, found in the file
**  "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
**  for a list of all applicable permissions and restrictions.
*/

#import <appkit/appkit.h>

#import "SurfJPEGSupport.h"
#import "SurfJPEGDecoder.Internal.h"

#import <jpeglib.h>
#import <jerror.h>
#import <jconfig.h>

#import <mach/mach.h>

#ifdef VERBOSE
#undef VERBOSE
#undef METHOD
#undef METHODnl
#endif

#define VERBOSE 		if(_SDFlags.verboseMode)
#define METHOD  		fprintf(stderr, "[%s %s%s] ", \
                                object_getClassName(self), \
                 		        (self == [self class]) ? "+" : "-", \
				                SELNAME(_cmd))
#define METHODnl  		fprintf(stderr, "[%s %s%s]\n", \
                                object_getClassName(self), \
                 		        (self == [self class]) ? "+" : "-", \
				                SELNAME(_cmd))

#define INPUT_BUF_SIZE  vm_page_size

typedef struct {
	struct jpeg_source_mgr 	pub;			/* public fields */
	NXStream 				*infile;		/* source stream */
	JOCTET		 			*buffer;		/* start of buffer */
	boolean 				start_of_file;	/* have we gotten any data yet? */
} my_source_mgr;

typedef my_source_mgr * my_src_ptr;


@implementation SurfJPEGDecoder (InternalAPI)
/*"
 * This category implements the internals of the JPEG/JFIF decoder.  It
 * basically provides an interface between the #{SurfDecoder} API and the
 * IJG libjpeg API.
"*/

void _init_source(j_decompress_ptr cinfo)
{
	my_src_ptr src	   = (my_src_ptr) cinfo->src;
	src->start_of_file = TRUE;
}

boolean _fill_input_buffer (j_decompress_ptr cinfo)
{
	my_src_ptr src = (my_src_ptr) cinfo->src;
	size_t nbytes;

	nbytes = NXRead(src->infile, src->buffer, INPUT_BUF_SIZE);

	if (nbytes <= 0) {
		if (src->start_of_file)	/* Treat empty input file as fatal error */
			ERREXIT(cinfo, JERR_INPUT_EMPTY);
		WARNMS(cinfo, JWRN_JPEG_EOF);
		/* Insert a fake EOI marker */
		src->buffer[0] = (JOCTET) 0xFF;
		src->buffer[1] = (JOCTET) JPEG_EOI;
		nbytes = 2;
	}

	src->pub.next_input_byte = src->buffer;
	src->pub.bytes_in_buffer = nbytes;
	src->start_of_file = FALSE;

	return TRUE;
}

static void _skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
  my_src_ptr src = (my_src_ptr) cinfo->src;

  /* Just a dumb implementation for now.  Could use fseek() except
   * it doesn't work on pipes.  Not clear that being smart is worth
   * any trouble anyway --- large skips are infrequent.
   */
  if (num_bytes > 0) {
    while (num_bytes > (long) src->pub.bytes_in_buffer) {
      num_bytes -= (long) src->pub.bytes_in_buffer;
      (void) _fill_input_buffer(cinfo);
      /* note we assume that fill_input_buffer will never return FALSE,
       * so suspension need not be handled.
       */
    }
    src->pub.next_input_byte += (size_t) num_bytes;
    src->pub.bytes_in_buffer -= (size_t) num_bytes;
  }
}

void _term_source (j_decompress_ptr cinfo)
{
  /* no work necessary here */
}
	
void _nxstream_stdio_src (j_decompress_ptr cinfo, NXStream *infile)
{
  my_src_ptr src;

  /* The source object and input buffer are made permanent so that a series
   * of JPEG images can be read from the same file by calling jpeg_stdio_src
   * only before the first one.  (If we discarded the buffer at the end of
   * one image, we'd likely lose the start of the next one.)
   * This makes it unsafe to use this manager and a different source
   * manager serially with the same JPEG object.  Caveat programmer.
   */
  if (cinfo->src == NULL) {	/* first time for this JPEG object? */
	  cinfo->src = (struct jpeg_source_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
								  sizeof(my_source_mgr));
	  src = (my_src_ptr) cinfo->src;
	  src->buffer = (JOCTET *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
								  INPUT_BUF_SIZE * sizeof(JOCTET));
  }

  src						 = (my_src_ptr) cinfo->src;
  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; /* use default method */
  src->pub.term_source		 = _term_source;

  src->infile				 = infile;
  src->pub.bytes_in_buffer	 = 0; /* forces fill_input_buffer on first read */
  src->pub.next_input_byte	 = NULL; /* until buffer loaded */
}

void _update_row_ptr_buf(unsigned char **rowPtrBuf,
						 unsigned totalRows,
						 unsigned rowsRead,
						 unsigned readCount,
						 unsigned bytesPerRow,
						 unsigned char *data)
{
		unsigned row;
		unsigned bufIndex;
		unsigned maxRow = (rowsRead + readCount);
		
		maxRow = (maxRow < totalRows) ? maxRow : totalRows; 

		for(row=rowsRead, bufIndex = 0; row < maxRow; row++, bufIndex++)
			rowPtrBuf[bufIndex] = &(data[row * bytesPerRow]);
	}
	


- _decodeImage
{
	NXBitmapImageRep *imageRep;
	void *data;
	int bytesPerRow;

	VERBOSE {
		METHODnl;
	}

	totalRows = jpegDecompInfo.pub.output_height;
	imageRep  = [[NXBitmapImageRep allocFromZone:[self zone]]
					 initDataPlanes:NULL // instance will allocate
					 pixelsWide: jpegDecompInfo.pub.output_width
					 pixelsHigh: totalRows
					 bitsPerSample:8 
					 samplesPerPixel:jpegDecompInfo.pub.output_components
					 hasAlpha:NO
					 isPlanar:NO
					 colorSpace:NX_RGBColorSpace
					 bytesPerRow:0 // instance will figure it out
					 bitsPerPixel:0];

	if(!imageRep) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Failed to allocate imageRep.\n");
		}
		return nil;
	}

	data		= [imageRep data];
	bytesPerRow	= [imageRep bytesPerRow];
	rowsRead	= 0;
	while (jpegDecompInfo.pub.output_scanline <
		   jpegDecompInfo.pub.output_height) {
		_update_row_ptr_buf(rowPtrBuf,
							totalRows,
							rowsRead,
							readCount,
							bytesPerRow,
							data);
		rowsRead += jpeg_read_scanlines(&jpegDecompInfo.pub,
										(JSAMPARRAY) rowPtrBuf,
										1);
	}

	[returnImage useRepresentation:imageRep];
	
	return returnImage;
}

void surfjpeg_error_exit (j_common_ptr cinfo)
{
  surfjpeg_error_ptr theErr = (surfjpeg_error_ptr) cinfo->err;

  /* Always display the message. */
//  (*cinfo->err->output_message) (cinfo);

  longjmp(theErr->setjmp_buffer, 1); // bounce to setjmp() point
}

void surfjpeg_error_msg  (j_common_ptr cinfo)
{
	char buffer[JMSG_LENGTH_MAX];

	surfjpeg_decompress_ptr decompInfo = (surfjpeg_decompress_ptr) cinfo;

	/* Create the message */
	(*cinfo->err->format_message) (cinfo, buffer);
	
	[decompInfo->jpegDecoder setLastImageCorrupt: YES];
	if(decompInfo->jpegDecoder)
		[decompInfo->jpegDecoder
					spewMessage:buffer
					withSeverity:SEV_Failure];
}

- _decodeFromStream:(NXStream *) theStream
{
	id theImage;
	
	// initialize decompressor 
	jpegDecompInfo.pub.err		= jpeg_std_error(&jpegErrMgr.pub);
	jpegDecompInfo.jpegDecoder	= self;
	jpeg_create_decompress(&jpegDecompInfo.pub);

	// set source to theStream
	_nxstream_stdio_src(&jpegDecompInfo.pub, theStream);

	// set up jpeg error handlers
	jpegErrMgr.pub.output_message = surfjpeg_error_msg;
	jpegErrMgr.pub.error_exit	  = surfjpeg_error_exit;
	if(setjmp(jpegErrMgr.setjmp_buffer)) {
		[self setLastImageCorrupt: YES];
		jpeg_destroy_decompress(&jpegDecompInfo.pub);
		return (rowsRead < (totalRows / 2)) ? nil : theImage;
	}

	// read header and start decompression
	jpeg_read_header(&jpegDecompInfo.pub, TRUE);
	jpeg_start_decompress(&jpegDecompInfo.pub);

	// dump verbosity
	VERBOSE {
		METHOD;
		fprintf(stderr,
				"(%d x %d); %d components\n",
				jpegDecompInfo.pub.image_width,
				jpegDecompInfo.pub.image_height,
				jpegDecompInfo.pub.num_components);
	}

	// decode image
	theImage = [self _decodeImage];

	// finish decompression
	jpeg_finish_decompress(&jpegDecompInfo.pub);

	// destroy decompression context
	jpeg_destroy_decompress(&jpegDecompInfo.pub);

	// return theImage
	return theImage; // will either by returnImage or nil
}
@end
