/* This is a JPG image file loading framework */

#include <me_defines.h>
#include <me_c_video.h>
#include <me_c_video_driver_interface.h>
#include <me_c_video_surface.h>
#include <string>
#include <iostream>

namespace SDL
{
#include "SDL.h"
#include "SDL_rwops.h"
}
extern "C"
{
#include <stdlib.h>
#include <stdio.h>
#include <setjmp.h>

#include <jpeglib.h>
}
/* See if an image is contained in a data source */
int IMG_isJPG( SDL::SDL_RWops *src )
{
	int is_JPG;
	Uint8 magic[ 4 ];

	is_JPG = 0;
	if ( SDL_RWread( src, magic, 2, 1 ) )
	{
		if ( ( magic[ 0 ] == 0xFF ) && ( magic[ 1 ] == 0xD8 ) )
		{
			SDL_RWread( src, magic, 4, 1 );
			SDL_RWread( src, magic, 4, 1 );
			if ( memcmp( ( char * ) magic, "JFIF", 4 ) == 0 ||
			        memcmp( ( char * ) magic, "Exif", 4 ) == 0 )
			{
				is_JPG = 1;
			}
		}
	}
	return ( is_JPG );
}
#define INPUT_BUFFER_SIZE	4096
typedef struct
{
	struct jpeg_source_mgr pub;

	SDL::SDL_RWops *ctx;
	Uint8 buffer[ INPUT_BUFFER_SIZE ];
}
my_source_mgr;

/*
 * Initialize source --- called by jpeg_read_header
 * before any data is actually read.
 */
static void init_source ( j_decompress_ptr cinfo )
{
	/* We don't actually need to do anything */
	return ;
}

/*
 * Fill the input buffer --- called whenever buffer is emptied.
 */
static int fill_input_buffer ( j_decompress_ptr cinfo )
{
	my_source_mgr * src = ( my_source_mgr * ) cinfo->src;
	int nbytes;

	nbytes = SDL_RWread( src->ctx, src->buffer, 1, INPUT_BUFFER_SIZE );
	if ( nbytes <= 0 )
	{
		/* Insert a fake EOI marker */
		src->buffer[ 0 ] = ( Uint8 ) 0xFF;
		src->buffer[ 1 ] = ( Uint8 ) JPEG_EOI;
		nbytes = 2;
	}
	src->pub.next_input_byte = src->buffer;
	src->pub.bytes_in_buffer = nbytes;

	return TRUE;
}


/*
 * Skip data --- used to skip over a potentially large amount of
 * uninteresting data (such as an APPn marker).
 *
 * Writers of suspendable-input applications must note that skip_input_data
 * is not granted the right to give a suspension return.  If the skip extends
 * beyond the data currently in the buffer, the buffer can be marked empty so
 * that the next read will cause a fill_input_buffer call that can suspend.
 * Arranging for additional bytes to be discarded before reloading the input
 * buffer is the application writer's problem.
 */
static void skip_input_data ( j_decompress_ptr cinfo, long num_bytes )
{
	my_source_mgr * src = ( my_source_mgr * ) 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 ) src->pub.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;
	}
}

/*
 * Terminate source --- called by jpeg_finish_decompress
 * after all data has been read.
 */
static void term_source ( j_decompress_ptr cinfo )
{
	/* We don't actually need to do anything */
	return ;
}

/*
 * Prepare for input from a stdio stream.
 * The caller must have already opened the stream, and is responsible
 * for closing it after finishing decompression.
 */
static void jpeg_SDL_RW_src ( j_decompress_ptr cinfo, SDL::SDL_RWops *ctx )
{
	my_source_mgr * 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_source_mgr * ) cinfo->src;
	}

	src = ( my_source_mgr * ) 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->ctx = ctx;
	src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
	src->pub.next_input_byte = NULL; /* until buffer loaded */
}

struct my_error_mgr
{
	struct jpeg_error_mgr errmgr;
	jmp_buf escape;
};

static void my_error_exit( j_common_ptr cinfo )
{
	struct my_error_mgr * err = ( struct my_error_mgr * ) cinfo->err;
	longjmp( err->escape, 1 );
}

static void output_no_message( j_common_ptr cinfo )
{
	/* do nothing */
}

/* Load a JPEG type image from an SDL datasource */
ME_n_Video::ME_c_Video_Surface *IMG_LoadJPG_RW( std::string fname, std::string grp, std::string name )
{
	struct jpeg_decompress_struct cinfo;
	Uint8 *data1;
	Uint16 ls1;
	Uint16 ls;
	Uint16 bpp;
	JSAMPROW rowptr[ 1 ];
	ME_n_Video::ME_c_Video_Surface *surface;
	ME_n_Video::ME_c_Video video;
	struct my_error_mgr jerr;
	SDL::SDL_RWops * src;
	src = SDL::SDL_RWFromFile( fname.c_str(),"rb" );

	if ( !src )
	{
		/* The error message has been set in SDL_RWFromFile */
		return NULL;
	}

	/* Create a decompression structure and load the JPEG header */
	cinfo.err = jpeg_std_error( &jerr.errmgr );
	jerr.errmgr.error_exit = my_error_exit;
	jerr.errmgr.output_message = output_no_message;
	if ( setjmp( jerr.escape ) )
	{
		/* If we get here, libjpeg found an error */
		jpeg_destroy_decompress( &cinfo );
		return NULL;
	}

	jpeg_create_decompress( &cinfo );
	jpeg_SDL_RW_src( &cinfo, src );
	jpeg_read_header( &cinfo, true );

	/* Set 24-bit RGB output */
	cinfo.out_color_space = JCS_RGB;
	cinfo.quantize_colors = false;
	jpeg_calc_output_dimensions( &cinfo );

	/* Allocate an output surface to hold the image */
	surface = &( *video )._create_surface( ( Sint16 ) cinfo.output_width, ( Sint16 ) cinfo.output_height, grp, false, name );
	Uint8 *data;
	data=new Uint8[cinfo.output_width*cinfo.output_height*3];
	if ( surface == NULL )
	{
		goto done;
	}

	/* Decompress the image */
	jpeg_start_decompress( &cinfo );
	while ( cinfo.output_scanline < cinfo.output_height )
	{
	rowptr[ 0 ] = ( JSAMPROW ) ( Uint8 * ) data +
		              cinfo.output_scanline * 3*cinfo.output_width;
		jpeg_read_scanlines( &cinfo, rowptr, ( JDIMENSION ) 1 );
	}
	ls1=3*cinfo.output_width;
	ls=surface->_get_line_size();
	bpp=surface->_get_bpp();
	data1=static_cast<Uint8*>(surface->_get_surface_data());
	for(Sint16 h=cinfo.output_height-1,h1=0 ;h>=0;h--,h1++)
	{
		for(Sint16 w=0;w<cinfo.output_width;w++)
		{
			*(data1+h1*ls+w*bpp)=*(data+h*ls1+w*3);
			*(data1+h1*ls+w*bpp+1)=*(data+h*ls1+w*3+1);
			*(data1+h1*ls+w*bpp+2)=*(data+h*ls1+w*3+2);
			*(data1+h1*ls+w*bpp+3)=0;
		}
	}
	delete[] data;
	
	jpeg_finish_decompress( &cinfo );

	/* Clean up and return */
done:
	jpeg_destroy_decompress( &cinfo );
	
	SDL_FreeRW(src);
	return ( surface );
}

