/*********************************************************/
/* This source code copyright (c) 1991-2001, Aaron Giles */
/* See the Read Me file for licensing information.       */
/* Contact email: mac@aarongiles.com                     */
/*********************************************************/

//=====================================================================================
// Generic includes for Macintosh headers
//=====================================================================================

#if THINK_C
#include "THINK.Header"
#elif applec
#pragma load ":Headers:MPW.Header"
#elif __MWERKS__
//#include "MW.Header"
#else
#include "JPEGView.h"
#endif

//=====================================================================================
// Includes specific to this module
//=====================================================================================

#include <stdio.h>
#include <setjmp.h>
#include <string.h>
#include "jpeglib.h"
#include "jerror.h"
#include "JPEGNoQuickTime.h"
extern const char * const jpeg_message_table[];

//=====================================================================================
// Prototypes for functions local to this module
//=====================================================================================

static OSErr DecompressJPEGImage(struct jpeg_decompress_struct *cinfo, PixMapHandle dstPixMap, ICMProgressProcRecordPtr prog);
static CTabHandle MakeGrayscaleColorTable(void);
static void CopyPixMapToDestination(PixMapHandle srcPixMap, short startRow);

GLOBAL void
InitIJGSource (j_decompress_ptr cinfo, Handle theHandle);
GLOBAL struct jpeg_error_mgr *
InitIJGErrors (struct jpeg_error_mgr * err);

static jmp_buf gJPEGErrorReturn;

extern OSErr DrawJPEGNoQuickTime(Handle theHandle, JVDrawParamsHandle theParams)
{
	NestedProgress theProgress = (*theParams)->progress;
	struct jpeg_decompress_struct cinfo;
	char hState = HGetState(theHandle);
	struct jpeg_error_mgr jerr;
	CTabHandle theColors = nil;
	GWorldPtr theGWorld = nil;
	OSErr theErr;

	if (theProgress.prog.progressProc) CallICMProgressProc(theProgress.prog.progressProc, 
				codecProgressUpdatePercent, 0x00000000L, theProgress.prog.progressRefCon);
	HLock(theHandle);
	cinfo.err = InitIJGErrors(&jerr);
	theErr = setjmp(gJPEGErrorReturn);
	if (theErr == noErr) {
		jpeg_create_decompress(&cinfo);
		InitIJGSource(&cinfo, theHandle);
		theErr = setjmp(gJPEGErrorReturn);
		if (theErr == noErr) {
			jpeg_read_header(&cinfo, true);
			jpeg_start_decompress(&cinfo);
			if (cinfo.out_color_components == 1) theColors = MakeGrayscaleColorTable();
			theGWorld = NewTempGWorld(cinfo.output_width, cinfo.output_height, (cinfo.out_color_components == 1) ? 8 : 32, theColors);
			if (cinfo.out_color_components == 1) DisposCTable(theColors);
			if (theGWorld) {
				theErr = DecompressJPEGImage(&cinfo, GetGWorldPixMap(theGWorld), (ICMProgressProcRecordPtr)&theProgress);
				if ((*theParams)->progress.aborted) theErr = codecAbortErr;
			} else gIntError = errNoDrawMemory, theErr = memFullErr;
		}
		/*if (theErr == noErr) {
			theErr = setjmp(gJPEGErrorReturn);
			if (theErr == noErr) jpeg_finish_decompress(&cinfo);
		}*/
		jpeg_destroy_decompress(&cinfo);
	}
	if (theGWorld) DisposeGWorld(theGWorld);
	HSetState(theHandle, hState);
	if (theProgress.prog.progressProc) CallICMProgressProc(theProgress.prog.progressProc, 
				codecProgressUpdatePercent, 0x00010000L, theProgress.prog.progressRefCon);
	return theErr;
}

static OSErr DecompressJPEGImage(struct jpeg_decompress_struct *cinfo, PixMapHandle dstPixMap, ICMProgressProcRecordPtr prog)
{
	char pState = GetPixelsState(dstPixMap);
	RgnHandle dstRgn = qd.thePort->visRgn;
	ulong bandRow = 0, row, bufferRows = Height(&(*dstPixMap)->bounds), total, count = 1, nextUpdate = 32;
	short lastLine = (*qd.thePort->visRgn)->rgnBBox.bottom;
	JSAMPARRAY scanLines = nil;
	OSErr theErr = noErr;
	uchar *rowStart;
	
	LockPixels(dstPixMap);
	theErr = setjmp(gJPEGErrorReturn);
	if (theErr == noErr) {
		rowStart = (uchar *)GetPixBaseAddr(dstPixMap);
		if (scanLines = (JSAMPARRAY)NewPtrClear(bufferRows * sizeof(JSAMPROW))) {
			for (row = 0; row < bufferRows; row++) scanLines[row] = rowStart, rowStart += (*dstPixMap)->rowBytes & 0x3fff;
			for (row = 0; row < lastLine && count && theErr == noErr; row += total) {
				total = 0;
				while (total < bufferRows && (row + total) < lastLine && theErr == noErr) {
					// note that we don't swap MMU mode here; we assume that since the GWorld was allocated
					// locally, it is always addressable through the current MMU state
					count = jpeg_read_scanlines(cinfo, &scanLines[total], bufferRows - total);
					if (!count) break;
					total += count;
					if ((row + total) > nextUpdate) {
						if (prog->progressProc) 
							theErr = (OSErr)CallICMProgressProc(prog->progressProc, 
									codecProgressUpdatePercent, FixRatio(row + total, lastLine), prog->progressRefCon);
						nextUpdate += 32;
					}
				}
				CopyPixMapToDestination(dstPixMap, row);
			}
		}
	}
	if (scanLines) DisposePtr((Ptr)scanLines);
	SetPixelsState(dstPixMap, pState);
	return theErr;
}

static CTabHandle MakeGrayscaleColorTable(void)
{
	ushort color, count = 256, i;
	CTabHandle theColors;
	
	theColors = (CTabHandle)NewHandleClear(sizeof(ColorTable) + (count - 1) * sizeof(ColorSpec));
	if (theColors) {
		(*theColors)->ctSeed = GetCTSeed();
		(*theColors)->ctFlags = 0x8000;
		(*theColors)->ctSize = count - 1;
		for (i = 0; i < count; i++) {
			color = (ulong)i * 65535L / (long)(count - 1);
			(*theColors)->ctTable[i].rgb.red = (*theColors)->ctTable[i].rgb.green =
					(*theColors)->ctTable[i].rgb.blue = color;
		}
	}
	return theColors;
}

//=====================================================================================
// void CopyPixMapToDestination(PixMapHandle srcPixMap, short startRow)
//=====================================================================================
// Copies the band contained in srcPixMap to the destination, offset from the top by
// startRow.
//=====================================================================================

static void CopyPixMapToDestination(PixMapHandle srcPixMap, short startRow)
{
	Rect srcRect = (*srcPixMap)->bounds, dstRect = (*srcPixMap)->bounds;
	PixMapHandle dstPixMap = GetGWorldPixMap((CGrafPtr)qd.thePort);
	char srcState = HGetState((Handle)srcPixMap);
	char dstState = HGetState((Handle)dstPixMap);
	
	OffsetRect(&dstRect, 0, startRow);
	if (dstRect.bottom > (*qd.thePort->visRgn)->rgnBBox.bottom) {
		srcRect.bottom -= dstRect.bottom - (*qd.thePort->visRgn)->rgnBBox.bottom;
		dstRect.bottom = (*qd.thePort->visRgn)->rgnBBox.bottom;
	}
	HLock((Handle)srcPixMap);
	HLock((Handle)dstPixMap);
	CopyBits((BitMap *)*srcPixMap, (BitMap *)*dstPixMap, &srcRect, &dstRect,
				srcCopy + ditherCopy, qd.thePort->visRgn);
	HSetState((Handle)dstPixMap, dstState);
	HSetState((Handle)srcPixMap, srcState);
}

//============================================================================================
//============================================================================================
//===========================                               ==================================
//===========================  IJG Data Source Object Code  ==================================
//===========================                               ==================================
//============================================================================================
//============================================================================================

/* Expanded data source object for stdio input */

typedef struct {
  struct jpeg_source_mgr pub;	/* public fields */
} my_source_mgr;

typedef my_source_mgr * my_src_ptr;

/*
 * Initialize source --- called by jpeg_read_header
 * before any data is actually read.
 */

METHODDEF void
init_source (j_decompress_ptr cinfo)
{
}

/*
 * Fill the input buffer --- called whenever buffer is emptied.
 */

METHODDEF boolean
fill_input_buffer (j_decompress_ptr cinfo)
{
  static JOCTET dummyBuffer[] = { (JOCTET)0xff, (JOCTET)JPEG_EOI };
  my_src_ptr src = (my_src_ptr) cinfo->src;

  WARNMS(cinfo, JWRN_JPEG_EOF);
  src->pub.next_input_byte = (JOCTET *)&dummyBuffer;
  src->pub.bytes_in_buffer = 2;

  return TRUE;
}


/*
 * Skip data --- used to skip over a potentially large amount of
 * uninteresting data (such as an APPn marker).
 */

METHODDEF void
skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
	my_src_ptr src = (my_src_ptr) cinfo->src;

	src->pub.next_input_byte += num_bytes;
	src->pub.bytes_in_buffer -= num_bytes;
}

/*
 * An additional method that can be provided by data source modules is the
 * resync_to_restart method for error recovery in the presence of RST markers.
 * For the moment, this source module just uses the default resync method
 * provided by the JPEG library.  That method assumes that no backtracking
 * is possible.
 */


/*
 * Terminate source --- called by jpeg_finish_decompress
 * after all data has been read.  Often a no-op.
 */

METHODDEF void
term_source (j_decompress_ptr cinfo)
{
}

/*
 * Prepare for input from a memory handle.
 */

GLOBAL void
InitIJGSource (j_decompress_ptr cinfo, Handle theHandle)
{
	my_src_ptr src;

	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->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->pub.bytes_in_buffer = GetHandleSize(theHandle);
	src->pub.next_input_byte = (JOCTET *)StripAddress(*theHandle);
}

//============================================================================================
//============================================================================================
//===========================                               ==================================
//===========================     IJG Error Object Code     ==================================
//===========================                               ==================================
//============================================================================================
//============================================================================================

/*
 * Error exit handler: must not return to caller.
 */

METHODDEF void
error_exit (j_common_ptr cinfo)
{
	(*cinfo->err->output_message) (cinfo);
	if (cinfo->err->msg_code == JERR_OUT_OF_MEMORY) {
		gIntError = errNoDrawMemory;
		longjmp(gJPEGErrorReturn, memFullErr);
	} else {
    	longjmp(gJPEGErrorReturn, codecBadDataErr);
	}
}

/*
 * Actual output of an error or trace message.
 */

METHODDEF void
output_message (j_common_ptr cinfo)
{
	Str255 theString;
	short length;

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

	/* Display it in a dialog box */
	if (!gDrawing || !Corrupt(gDrawing)) {
		length = strlen((char *)theString);
		BlockMove(theString, &theString[1], length);
		*theString = length;
		PushPort();
		StringError(errJPEGError, theString);
		PopPort();
	}
}

/*
 * Decide whether to emit a trace or warning message.
 * msg_level is one of:
 *   -1: recoverable corrupt-data warning, may want to abort.
 *    0: important advisory messages (always display to user).
 *    1: first level of tracing detail.
 *    2,3,...: successively more detailed tracing messages.
 * An application might override this method if it wanted to abort on warnings
 * or change the policy about which messages to display.
 */

METHODDEF void
emit_message (j_common_ptr cinfo, int msg_level)
{
  struct jpeg_error_mgr * err = cinfo->err;
#if NOWARNINGS
  if (msg_level < 0) {
    /* It's a warning message.  Since corrupt files may generate many warnings,
     * the policy implemented here is to show only the first warning,
     * unless trace_level >= 3.
     */
    if (err->num_warnings == 0 || err->trace_level >= 3)
      (*err->output_message) (cinfo);
    /* Always count warnings in num_warnings. */
    err->num_warnings++;
  } else {
    /* It's a trace message.  Show it if trace_level >= msg_level. */
    if (err->trace_level >= msg_level)
      (*err->output_message) (cinfo);
  }
#endif
}


/*
 * Format a message string for the most recent JPEG error or message.
 * The message is stored into buffer, which should be at least JMSG_LENGTH_MAX
 * characters.  Note that no '\n' character is added to the string.
 * Few applications should need to override this method.
 */

METHODDEF void
format_message (j_common_ptr cinfo, char * buffer)
{
  struct jpeg_error_mgr * err = cinfo->err;
  int msg_code = err->msg_code;
  const char * msgtext = NULL;
  const char * msgptr;
  char ch;
  boolean isstring;

  /* Look up message string in proper table */
  if (msg_code > 0 && msg_code <= err->last_jpeg_message) {
    msgtext = err->jpeg_message_table[msg_code];
  } else if (err->addon_message_table != NULL &&
	     msg_code >= err->first_addon_message &&
	     msg_code <= err->last_addon_message) {
    msgtext = err->addon_message_table[msg_code - err->first_addon_message];
  }

  /* Defend against bogus message number */
  if (msgtext == NULL) {
    err->msg_parm.i[0] = msg_code;
    msgtext = err->jpeg_message_table[0];
  }

  /* Check for string parameter, as indicated by %s in the message text */
  isstring = FALSE;
  msgptr = msgtext;
  while ((ch = *msgptr++) != '\0') {
    if (ch == '%') {
      if (*msgptr == 's') isstring = TRUE;
      break;
    }
  }

  /* Format the message into the passed buffer */
  if (isstring)
    sprintf(buffer, msgtext, err->msg_parm.s);
  else
    sprintf(buffer, msgtext,
	    err->msg_parm.i[0], err->msg_parm.i[1],
	    err->msg_parm.i[2], err->msg_parm.i[3],
	    err->msg_parm.i[4], err->msg_parm.i[5],
	    err->msg_parm.i[6], err->msg_parm.i[7]);
}


/*
 * Reset error state variables at start of a new image.
 * This is called during compression startup to reset trace/error
 * processing to default state, without losing any application-specific
 * method pointers.  An application might possibly want to override
 * this method if it has additional error processing state.
 */

METHODDEF void
reset_error_mgr (j_common_ptr cinfo)
{
  cinfo->err->num_warnings = 0;
  /* trace_level is not reset since it is an application-supplied parameter */
  cinfo->err->msg_code = 0;	/* may be useful as a flag for "no error" */
}


/*
 * Fill in the standard error-handling methods in a jpeg_error_mgr object.
 * Typical call is:
 *	struct jpeg_compress_struct cinfo;
 *	struct jpeg_error_mgr err;
 *
 *	cinfo.err = jpeg_std_error(&err);
 * after which the application may override some of the methods.
 */

GLOBAL struct jpeg_error_mgr *
InitIJGErrors (struct jpeg_error_mgr * err)
{
  err->error_exit = error_exit;
  err->emit_message = emit_message;
  err->output_message = output_message;
  err->format_message = format_message;
  err->reset_error_mgr = reset_error_mgr;

  err->trace_level = 0;		/* default = no tracing */
  err->num_warnings = 0;	/* no warnings emitted yet */
  err->msg_code = 0;		/* may be useful as a flag for "no error" */

  /* Initialize message table pointers */
  err->jpeg_message_table = jpeg_message_table;
  err->last_jpeg_message = (int) JMSG_LASTMSGCODE - 1;

  err->addon_message_table = NULL;
  err->first_addon_message = 0;	/* for safety */
  err->last_addon_message = 0;

  return err;
}
