#include "img.h"
#include "img_conv.h"
#include "Icon.h"

#if PRIMA_PLATFORM == apcUnix
#include <unistd.h>
#else
#include <stdlib.h>
#endif 

#ifdef __cplusplus
extern "C" {
#endif

static Bool initialized = false;   

void 
apc_img_init( void)
{
	if ( initialized) croak("Attempt to initialize image subsystem twice");
	list_create( &imgCodecs, 8, 8);
	initialized = true;
}   

#define CHK if ( !initialized) croak("Image subsystem is not initialized");

void 
apc_img_done( void)
{
	int i;
	
	CHK;   
	for ( i = 0; i < imgCodecs. count; i++) {
		PImgCodec c = ( PImgCodec)( imgCodecs. items[ i]);
		if ( c-> instance)
			c-> vmt-> done( c); 
		free( c);
	}
	list_destroy( &imgCodecs);
	initialized = false;
}   

Bool
apc_img_register( PImgCodecVMT codec, void * initParam)
{
	PImgCodec c; 

	CHK;
	if ( !codec) return false;
	c = ( PImgCodec) malloc( sizeof( struct ImgCodec) + codec-> size);
	if ( !c) return false;
	
	memset( c, 0, sizeof( struct ImgCodec));
	c-> vmt = ( PImgCodecVMT) ((( Byte *) c) + sizeof( struct ImgCodec));
	c-> initParam = initParam;
	memcpy( c-> vmt, codec, codec-> size);
	list_add( &imgCodecs, ( Handle) c);
	return true;
}   

static void * 
init( PImgCodecInfo * info, void * param)
{
	return nil;
}   

static void
done( PImgCodec instance) 
{
}   

static HV *   
defaults(  PImgCodec instance)
{
	return newHV();
}

static void   
check_in(  PImgCodec instance, HV * system, HV * user)
{
}

static void * 
open_load(  PImgCodec instance, PImgLoadFileInstance fi)
{
	return nil;
}

static Bool   
load( PImgCodec instance, PImgLoadFileInstance fi)
{
	return false;
}   

static void
close_load( PImgCodec instance, PImgLoadFileInstance fi)
{
	free( fi-> instance);  
}

static void * 
open_save( PImgCodec instance, PImgSaveFileInstance fi)
{
	return nil;
}

static Bool   
save( PImgCodec instance, PImgSaveFileInstance fi)
{
	return false;
}   

static void 
close_save( PImgCodec instance, PImgSaveFileInstance fi)
{
	free( fi-> instance);
}


List imgCodecs;
struct ImgCodecVMT CNullImgCodecVMT = {
	sizeof( struct ImgCodecVMT),
	init,
	done,
	defaults,
	check_in,
	open_load,
	load,
	close_load,
	defaults,
	check_in,
	open_save,
	save,
	close_save
};

void
apc_img_profile_add( HV * to, HV * from, HV * keys)
{
	HE *he;
	hv_iterinit(( HV*) keys);
	for (;;)
	{
		char *key;
		int  keyLen;
		SV ** holder;
		if (( he = hv_iternext( keys)) == nil)
			return;
		key    = (char*) HeKEY( he);
		keyLen = HeKLEN( he);
		if ( !hv_exists( from, key, keyLen))
			continue;
		holder = hv_fetch( from, key, keyLen, 0);
		if ( holder) 
			(void) hv_store( to, key, keyLen, newSVsv( *holder), 0);
	}
}   

static ssize_t 
stdio_read( void * f, size_t bufsize, void * buffer)
{
	return fread( buffer, 1, bufsize, ( FILE*) f);
}

static ssize_t 
stdio_write( void * f, size_t bufsize, void * buffer)
{
	return fwrite( buffer, 1, bufsize, ( FILE*) f);
}

static int
stdio_seek( void * f, long offset, int whence)
{
	return fseek( ( FILE*) f, offset, whence);
}

static long
stdio_tell( void * f)
{
	return ftell( ( FILE*) f);
}


static ImgIORequest std_ioreq = {
	stdio_read,
	stdio_write,
	stdio_seek,
	stdio_tell,
	(void*) fflush,
	(void*) ferror
};

PList 
apc_img_load( Handle self, char * fileName, PImgIORequest ioreq,  HV * profile, char * error)
{
	dPROFILE;
	int i, profiles_len = 0, lastFrame = -2, codecID = -1;
	PList ret;
	PImgCodec c = nil;
	ImgLoadFileInstance fi;
	AV * profiles = nil;
	HV * def = nil, * firstObjectExtras = nil, * commonHV = nil;
	Bool err = false;
	Bool loadExtras = false, noImageData = false;
	Bool incrementalLoad = false;
	Bool iconUnmask = false;
	Bool noIncomplete = false;
	char * baseClassName = "Prima::Image";
	ImgIORequest sioreq;
	int  load_mask;


#define out(x){ err = true;\
	strncpy( error, x, 256);\
	goto EXIT_NOW;}

#define outd(x,d){ err = true;\
	snprintf( error, 256, x, d);\
	goto EXIT_NOW;}   
	
	CHK;
	memset( &fi, 0, sizeof( fi));
	ret = plist_create( 8, 8);
	if ( !ret) out("Not enough memory")

	strcpy( error, "Internal error");
	fi. errbuf = error;

	/* open file */
	if ( ioreq == NULL) {
		memcpy( &sioreq, &std_ioreq, sizeof( sioreq));
		if (( sioreq. handle = fopen( fileName, "rb")) == NULL)
			out( strerror( errno));
		fi. req = &sioreq;
		fi. req_is_stdio = true;
		load_mask = IMG_LOAD_FROM_FILE;
	} else {
		fi. req = ioreq;
		fi. req_is_stdio = false;
		load_mask = IMG_LOAD_FROM_STREAM;
	}
	fi. fileName = fileName;
	fi. stop = false;

	/* assigning user file profile */
	if ( pexist( index)) {
		fi. frameMapSize = 1;
		if ( !( fi. frameMap  = (int*) malloc( sizeof( int))))
			out("Not enough memory");
		if ((*fi. frameMap = pget_i( index)) < 0)
			out("Invalid index");
	} else if ( pexist( map)) {
		SV * sv = pget_sv( map);
		if ( SvOK( sv)) {
			if ( SvROK( sv) && SvTYPE( SvRV( sv)) == SVt_PVAV) {
				AV * av = ( AV*) SvRV( sv);
				int len = av_len( av) + 1;
				if ( !( fi. frameMap = ( int *) malloc( sizeof( int) * len)))
					out("Not enough memory");
				for ( i = 0; i < len; i++) {
					SV ** holder = av_fetch( av, i, 0);
					if ( !holder) out("Array panic on 'map' property");
					if (( fi. frameMap[ i] = SvIV( *holder)) < 0)
						out("Invalid index on 'map' property");
				}   
				fi. frameMapSize = len;
			} else 
				out("Not an array passed to 'map' property");
		}
	} else if ( pexist( loadAll) && pget_B( loadAll)) {
		fi. loadAll = true;
	} else {
		fi. frameMapSize = 1;
		if ( ! (fi. frameMap = ( int*) malloc( sizeof( int))))
			out("Not enough memory");
	*fi. frameMap = 0;
	}   

	if ( pexist( loadExtras) && pget_B( loadExtras))
		fi. loadExtras = loadExtras = true;

	if ( pexist( noImageData) && pget_B( noImageData))
		fi. noImageData = noImageData = true;
	
	if ( pexist( iconUnmask) && pget_B( iconUnmask))
		fi. iconUnmask = iconUnmask = true;
	
	if ( pexist( noIncomplete) && pget_B( noIncomplete))
		fi. noIncomplete = noIncomplete = true;
	
	if ( pexist( eventMask))
		fi. eventMask = pget_i( eventMask);
	
	if ( pexist( eventDelay))
		fi. eventDelay = 1000.0 * pget_f( eventDelay);
	if ( fi. eventDelay <= 0)
		fi. eventDelay = 100; /* 100 ms. reasonable? */
	EVENT_SCANLINES_RESET(&fi);
	
	if ( pexist( profiles)) {
		SV * sv = pget_sv( profiles);
		if ( SvOK( sv) && SvROK( sv) && SvTYPE( SvRV( sv)) == SVt_PVAV) {
			profiles = ( AV *) SvRV( sv);
			profiles_len = av_len( profiles);
		} else 
			out("Not an array passed to 'profiles' property");
	}  

	if ( pexist( className)) {
		PVMT vmt;
		baseClassName = pget_c( className);
		vmt = gimme_the_vmt( baseClassName);
		while ( vmt && vmt != (PVMT)CImage) 
			vmt = vmt-> base;
		if ( !vmt) 
			outd("class '%s' is not a Prima::Image descendant", baseClassName);
	}      

	/* all other properties to be parsed by codec */
	fi. extras = profile;

	fi. fileProperties = newHV(); 
	fi. frameCount = -1;

	/* finding codec */
	{
		Bool * loadmap = ( Bool *) malloc( sizeof( Bool) * imgCodecs. count);

		if ( !loadmap) 
			out("Not enough memory");
		memset( loadmap, 0, sizeof( Bool) * imgCodecs. count);
		for ( i = 0; i < imgCodecs. count; i++) {
			c = ( PImgCodec ) ( imgCodecs. items[ i]);
			if ( !c-> instance)
				c-> instance = c-> vmt-> init( &c->info, c-> initParam);
			if ( !c-> instance) { /* failed to initialize, retry next time */
				loadmap[ i] = true;
				continue;
			} 
		}
		c = nil;
		
		/* finding by extension first */
		if ( fileName) {
			int fileNameLen = strlen( fileName);
			for ( i = 0; i < imgCodecs. count; i++) {
				int j = 0, found = false;
				if ( loadmap[ i]) continue;
				c = ( PImgCodec ) ( imgCodecs. items[ i]);
				while ( c-> info-> fileExtensions[ j]) {
					char * ext = c-> info-> fileExtensions[ j];
					int extLen = strlen( ext);
					if ( extLen < fileNameLen && stricmp( fileName + fileNameLen - extLen, ext) == 0) {
						found = true;
						break;
					}   
					j++;
				} 
				if ( found) {
					loadmap[ i] = true;

					if ( !( c-> info-> IOFlags & load_mask)) {
						c = nil;
						continue;
					}	  
					if (( fi. instance = c-> vmt-> open_load( c, &fi)) != NULL) {
						codecID = i;
						break;
					}   

					if ( fi. stop) { 
						err = true; 
						free( loadmap);
						goto EXIT_NOW; 
					}
				}   
				c = nil;
			}
		}
		
		/* use first suitable codec */
		if ( c == nil) {
			for ( i = 0; i < imgCodecs. count; i++) {
				if ( loadmap[ i]) continue;
				c = ( PImgCodec ) ( imgCodecs. items[ i]);
				if ( !( c-> info-> IOFlags & load_mask)) {
						c = nil;
						continue;
				}
				if (( fi. instance = c-> vmt-> open_load( c, &fi)) != NULL) {
					codecID = i;
					break;
				}   
				if ( fi. stop) { 
					err = true; 
					free( loadmap);
					goto EXIT_NOW; 
				}
				c = nil;
			}
		}
		free( loadmap);
		if ( !c) out("No appropriate codec found");
	}

	if ( fi. loadAll) {
		if ( fi. frameCount >= 0) {
			fi. frameMapSize = fi. frameCount;
			if ( !( fi. frameMap  = (int*) malloc( fi. frameCount * sizeof(int))))
				out("Not enough memory");
			for ( i = 0; i < fi. frameCount; i++)
				fi. frameMap[i] = i;
		} else {
			fi. frameMapSize = INT_MAX;
			incrementalLoad = true;
		}   
	}   


	/* use common profile */
	def = c-> vmt-> load_defaults( c); 
	commonHV = newHV();
	if ( profile) {
		c-> vmt-> load_check_in( c, commonHV, profile);
		apc_img_profile_add( commonHV, profile, def);
	}

	if ( fi. loadExtras && c-> info-> fileType) 
		(void) hv_store( fi. fileProperties, "codecID", 7, newSViv( codecID), 0);

	/* loading */
	for ( i = 0; i < fi. frameMapSize; i++) {
		HV * profile = commonHV;
		char * className = baseClassName;

		fi. frame = incrementalLoad ? i : fi. frameMap[ i];
		if (( fi. frameCount >= 0 && fi. frame >= fi. frameCount) || 
			( !(c-> info-> IOFlags & IMG_LOAD_MULTIFRAME) && fi. frame > 0)) { 
			if ( !(c-> info-> IOFlags & IMG_LOAD_MULTIFRAME) && fi. frameCount < 0)
				fi. frameCount = i; 
			if ( incrementalLoad) 
				/* that means, codec bothered to set frameCount at last - report no error then */
				goto EXIT_NOW;
			c-> vmt-> close_load( c, &fi);
			out("Frame index out of range");
		}   

		fi. loadExtras   = loadExtras;
		fi. noImageData  = noImageData;
		fi. iconUnmask   = iconUnmask;
		fi. noIncomplete = noIncomplete;
		fi. wasTruncated = false;

		/* query profile */
		if ( profiles && ( i <= profiles_len)) {
			HV * hv;
			SV ** holder = av_fetch( profiles, i, 0);
			if ( !holder) outd("Array panic on 'profiles[%d]' property", i);
			if ( SvOK( *holder)) {
				if ( SvROK( *holder) && SvTYPE( SvRV( *holder)) == SVt_PVHV) 
					hv = ( HV*) SvRV( *holder);
				else
					outd("Not a hash passed to 'profiles[%d]' property", i); 
				profile = newHV();
				apc_img_profile_add( profile, commonHV, commonHV);
				c-> vmt-> load_check_in( c, profile, hv);
				apc_img_profile_add( profile, hv, def);
				{ 
					HV * profile = hv;
					if ( pexist( loadExtras))
					fi. loadExtras  = pget_B( loadExtras);
					if ( pexist( noImageData))
					fi. noImageData = pget_B( noImageData);
					if ( pexist( iconUnmask))
					fi. iconUnmask = pget_B( iconUnmask);
				}   
			}
		}

		fi. jointFrame = ( fi. frame == lastFrame + 1);
		fi. profile    = profile;
		lastFrame = fi. frame;
		
		/* query className */
		if ( pexist( className)) {
			PVMT vmt;
			className = pget_c( className);
			vmt = gimme_the_vmt( className);
			while ( vmt && vmt != (PVMT)CImage) 
				vmt = vmt-> base;
			if ( !vmt) {
				if ( fi. profile != commonHV) sv_free(( SV *) fi. profile);      
				outd("class '%s' is not a Prima::Image descendant", className);
			}   
		}      

		/* create storage */
		if (( i > 0) || ( self == nilHandle)) {
			HV * profile = newHV();
			fi. object = Object_create( className, profile);
			sv_free(( SV *) profile); 
			if ( !fi. object) {
				if ( fi. profile != commonHV) sv_free(( SV *) fi. profile);
				outd("Failed to create object '%s'", className);
			}   
		} else
			fi. object = self;

		if ( fi. iconUnmask && kind_of( fi. object, CIcon))
			PIcon( fi. object)-> autoMasking = amNone;

		fi. frameProperties = newHV();
		if ( fi. loadExtras && c-> info-> fileType) 
			(void) hv_store( fi. frameProperties, "codecID", 7, newSViv( codecID), 0);

		/* loading image */
		if ( !c-> vmt-> load( c, &fi)) {
			c-> vmt-> close_load( c, &fi);
			sv_free(( SV *) fi. frameProperties);
			if ( fi. object != self)
				Object_destroy( fi. object);
			if ( fi. profile != commonHV) sv_free(( SV *) fi. profile);
			if ( incrementalLoad) {
				if ( fi. frameCount < 0) fi. frameCount = fi. frame;
				goto EXIT_NOW; /* EOF, report no error */
			}   
			err = true;
			goto EXIT_NOW;
		}
	
		if ( fi. loadExtras && fi. wasTruncated)
			(void) hv_store( fi. frameProperties, "truncated", 9, newSViv(1), 0);

		/* checking for grayscale */
		{
			PImage i = ( PImage) fi. object;
			if ( !( i-> type & imGrayScale)) 
				switch ( i-> type & imBPP) {
				case imbpp1:
					if ( i-> palSize == 2 && memcmp( i-> palette, stdmono_palette, sizeof( stdmono_palette)) == 0)
						i-> type |= imGrayScale;
					break;
				case imbpp4:
					if ( i-> palSize == 16 && memcmp( i-> palette, std16gray_palette, sizeof( std16gray_palette)) == 0)
						i-> type |= imGrayScale;
					break;
				case imbpp8:
					if ( i-> palSize == 256 && memcmp( i-> palette, std256gray_palette, sizeof( std256gray_palette)) == 0)
						i-> type |= imGrayScale;
					break;
				}
		}

		/* updating image */
		if ( !fi. noImageData)
			CImage( fi. object)-> update_change( fi. object);

		/* applying extras */
		if ( fi. loadExtras) {
			HV * extras = newHV();
			SV * sv = newRV_noinc(( SV *) extras);
			
			apc_img_profile_add( extras, fi. fileProperties,  fi. fileProperties);
			apc_img_profile_add( extras, fi. frameProperties, fi. frameProperties);
			if ( i == 0) firstObjectExtras = extras; 
			(void) hv_store(( HV* )SvRV((( PAnyObject) fi. object)-> mate), "extras", 6, newSVsv( sv), 0);
			sv_free( sv);
		} else if ( fi. noImageData) { /* no extras, report dimensions only */
			HV * extras = newHV();
			SV * sv = newRV_noinc(( SV *) extras), **item;
			if (( item = hv_fetch( fi. frameProperties, "width", 5, 0)) && SvOK( *item)) 
				(void) hv_store( extras, "width", 5, newSVsv( *item), 0);
			else
				(void) hv_store( extras, "width", 5, newSViv(PImage(fi.object)-> w), 0);
			if (( item = hv_fetch( fi. frameProperties, "height", 6, 0)) && SvOK( *item)) 
				(void) hv_store( extras, "height", 6, newSVsv( *item), 0);
			else
				(void) hv_store( extras, "height", 6, newSViv(PImage(fi.object)-> h), 0);
			(void) hv_store(( HV* )SvRV((( PAnyObject) fi. object)-> mate), "extras", 6, newSVsv( sv), 0);
			sv_free( sv);
		}

		sv_free(( SV *) fi. frameProperties);
		if ( fi. profile != commonHV) sv_free(( SV *) fi. profile);

		list_add( ret, fi. object);
	}

	c-> vmt-> close_load( c, &fi);

	/* returning info for null load request  */
	if ( self && loadExtras && fi. frameMapSize == 0) {
		HV * extras = newHV();
		SV * sv = newRV_noinc(( SV *) extras);
		apc_img_profile_add( extras, fi. fileProperties,  fi. fileProperties);
		firstObjectExtras = extras; 
		(void) hv_store(( HV* )SvRV((( PAnyObject) self)-> mate), "extras", 6, newSVsv( sv), 0);
		sv_free( sv);
	}   
	
EXIT_NOW:;
	if ( fi. frameCount < 0 && pexist( wantFrames) && pget_i( wantFrames)) {
		if ( ioreq != NULL) 
			req_seek( ioreq, 0, SEEK_SET);
		fi. frameCount = apc_img_frame_count( fileName, ioreq);
	}
	if ( firstObjectExtras)
		(void) hv_store( firstObjectExtras, "frames", 6, newSViv( fi. frameCount), 0);
	if ( err && ret)
		list_add( ret, nilHandle); /* indicate the error */
	if ( def)
		sv_free(( SV *) def);
	if ( commonHV)
		sv_free(( SV *) commonHV);
	if ( fi. fileProperties) 
		sv_free((SV *) fi. fileProperties); 
	if ( ioreq == NULL && fi.req != NULL && fi. req-> handle != NULL) 
		fclose(( FILE*) fi. req-> handle);
	free( fi. frameMap);
	return ret;
#undef out   
#undef outd
}

int 
apc_img_frame_count( char * fileName, PImgIORequest ioreq )
{
	PImgCodec c = nil;
	ImgLoadFileInstance fi;
	int i, frameMap, ret = 0;
	char error[256];
	ImgIORequest sioreq;
	int load_mask;

	CHK;
	memset( &fi, 0, sizeof( fi));
	/* open file */
	if ( ioreq == NULL) {
		memcpy( &sioreq, &std_ioreq, sizeof( sioreq));
		if (( sioreq. handle = fopen( fileName, "rb")) == NULL)
			goto EXIT_NOW;
		fi. req = &sioreq;
		fi. req_is_stdio = true;
		load_mask = IMG_LOAD_FROM_FILE;
	} else {
		fi. req = ioreq;
		fi. req_is_stdio = false;
		load_mask = IMG_LOAD_FROM_STREAM;
	}
	
	/* assigning request */
	fi. fileName = fileName;
	fi. frameMapSize   = frameMap = 0;
	fi. frameMap       = &frameMap;
	fi. loadExtras     = true;
	fi. noImageData    = true;
	fi. iconUnmask     = false;
	fi. noIncomplete   = false;
	fi. extras         = newHV();
	fi. fileProperties = newHV(); 
	fi. frameCount = -1;
	fi. errbuf     = error;
	fi. stop       = false;

	/* finding codec */
	{
		Bool * loadmap = ( Bool*) malloc( sizeof( Bool) * imgCodecs. count);

		if ( !loadmap) 
			return 0;
		memset( loadmap, 0, sizeof( Bool) * imgCodecs. count);
		for ( i = 0; i < imgCodecs. count; i++) {
			c = ( PImgCodec ) ( imgCodecs. items[ i]);
			if ( !c-> instance)
				c-> instance = c-> vmt-> init( &c->info, c-> initParam);
			if ( !c-> instance) { /* failed to initialize, retry next time */
				loadmap[ i] = true;
				continue;
			} 
		}
		
		c = nil;

		/* finding by extension first */
		if ( fileName) {
			int fileNameLen = strlen( fileName);
			for ( i = 0; i < imgCodecs. count; i++) {
				int j = 0, found = false;
				if ( loadmap[ i]) continue;
				c = ( PImgCodec ) ( imgCodecs. items[ i]);
				while ( c-> info-> fileExtensions[ j]) {
					char * ext = c-> info-> fileExtensions[ j];
					int extLen = strlen( ext);
					if ( extLen < fileNameLen && stricmp( fileName + fileNameLen - extLen, ext) == 0) {
						found = true;
						break;
					}   
					j++;
				} 
				if ( found) {
					loadmap[ i] = true;

					if ( !( c-> info-> IOFlags & load_mask)) {
						c = nil;
						continue;
					}
					if (( fi. instance = c-> vmt-> open_load( c, &fi)) != NULL)
						break;
					
					if ( fi. stop) { 
						free( loadmap);
						goto EXIT_NOW; 
					}
				}   
				c = nil;
			}
		}
		
		if ( c == nil) {
			for ( i = 0; i < imgCodecs. count; i++) {        
				if ( loadmap[ i]) continue;
				c = ( PImgCodec ) ( imgCodecs. items[ i]);
				if ( !( c-> info-> IOFlags & load_mask)) {
						c = nil;
						continue;
				}  
				if (( fi. instance = c-> vmt-> open_load( c, &fi)) != NULL)
					break;
				if ( fi. stop) { 
					free( loadmap);
					goto EXIT_NOW; 
				}
				c = nil;
			}
		}
		free( loadmap);
		if ( !c) goto EXIT_NOW;
	}  
	
	/* can tell now? */
	
	if ( fi. frameCount >= 0) {
		c-> vmt-> close_load( c, &fi);
		ret = fi. frameCount;
		goto EXIT_NOW;
	}  

	if ( !( c-> info-> IOFlags & IMG_LOAD_MULTIFRAME)) {
		c-> vmt-> close_load( c, &fi);
		ret = 1; /* single-framed file. what else? */
		goto EXIT_NOW;
	}   

	/* if can't, trying to load huge index, hoping that if */
	/* codec have a sequential access, it eventually meet the  */
	/* EOF and report the frame count */
	{
		HV * profile = newHV();
		fi. object = Object_create( "Prima::Image", profile);
		sv_free(( SV *) profile); 
		frameMap = fi. frame = INT_MAX;
		fi. frameProperties = newHV();
	}                  
	
	/* loading image */
	if ( c-> vmt-> load( c, &fi) || fi. frameCount >= 0) {
		/* well, INT_MAX frame is ok, and maybe more, but can't report more anyway */
		c-> vmt-> close_load( c, &fi);
		ret = ( fi. frameCount < 0) ? INT_MAX : fi. frameCount;
		goto EXIT_NOW;
	}   

	/* can't report again - so loading as may as we can */
	fi. loadAll = true;
	for ( i = 0; i < INT_MAX; i++) {
		fi. jointFrame = i > 0;
		frameMap = fi. frame = i;
		if ( !( c-> info-> IOFlags & IMG_LOAD_MULTIFRAME)) {
			c-> vmt-> close_load( c, &fi);
			if ( !( fi. instance = c-> vmt-> open_load( c, &fi))) {
				ret = i;
				goto EXIT_NOW;
			}   
		}   
		if ( !c-> vmt-> load( c, &fi) || fi. frameCount >= 0) {
			c-> vmt-> close_load( c, &fi);   
			ret = ( fi. frameCount < 0) ? i : fi. frameCount;
			goto EXIT_NOW;
		}   
	}   

	c-> vmt-> close_load( c, &fi);
	
EXIT_NOW:;
	if ( fi. object)
		Object_destroy( fi. object);
	if ( fi. extras)
		sv_free(( SV *) fi. extras);
	if ( fi. frameProperties)
		sv_free(( SV *) fi. frameProperties);
	if ( fi. fileProperties)
		sv_free(( SV *) fi. fileProperties);
	if ( ioreq == NULL && fi.req != NULL && fi. req-> handle != NULL)
		fclose(( FILE*) fi. req-> handle);
	return ret;
}   

int
apc_img_save( Handle self, char * fileName, PImgIORequest ioreq, HV * profile, char * error)
{
	dPROFILE;
	int i;
	PImgCodec c = nil;
	ImgSaveFileInstance fi;
	AV * images = nil;
	HV * def = nil, * commonHV = nil;
	Bool err = false;
	int codecID = -1;
	int xself = self ? 1 : 0;
	int ret = 0;
	Bool autoConvert = true;
	ImgIORequest sioreq;
	int save_mask;

#define out(x){ err = true;\
	strncpy( error, x, 256);\
	goto EXIT_NOW;}

#define outd(x,d){ err = true;\
	snprintf( error, 256, x, d);\
	goto EXIT_NOW;}   
	
	CHK;
	memset( &fi, 0, sizeof( fi));

	if ( pexist( append) && pget_B( append))
		fi. append = true;

	if ( pexist( autoConvert))
		autoConvert = pget_B( autoConvert);

	/* open file */
	if ( fi. append && ioreq == NULL) {
		FILE * f = ( FILE *) fopen( fileName, "rb");
		if ( !f)
			fi. append = false;
		else
			fclose( f);
	}   
	
	fi. errbuf = error;
	if ( ioreq == NULL) {
		memcpy( &sioreq, &std_ioreq, sizeof( sioreq));
		if (( sioreq. handle = fopen( fileName, fi. append ? "rb+" : "wb+" )) == NULL)
			out( strerror( errno));
		fi. req = &sioreq;
		fi. req_is_stdio = true;
		save_mask = IMG_SAVE_TO_FILE;
	} else {
		fi. req = ioreq;
		fi. req_is_stdio = false;
		save_mask = IMG_SAVE_TO_STREAM;
	}

	fi. fileName     = fileName;
	
	fi. frameMapSize = xself;
	if ( pexist( images)) {
		SV * sv = pget_sv( images);
		if ( SvOK( sv) && SvROK( sv) && SvTYPE( SvRV( sv)) == SVt_PVAV) {
			images = ( AV *) SvRV( sv);
			fi. frameMapSize += av_len( images) + 1;
		} else 
			out("Not an array passed to 'images' property");
	}   
	if ( fi. frameMapSize == 0)
		out("Nothing to save");

	/* fill array of objects */
	if ( !( fi. frameMap = ( Handle *) malloc( sizeof( Handle) * fi. frameMapSize)))
		out("Not enough memory");
	memset( fi. frameMap, 0, sizeof( Handle) * fi. frameMapSize);
	
	for ( i = 0; i < fi. frameMapSize; i++) {
		Handle obj = nilHandle;

		/* query profile */
		if ( self && (i == 0)) {
			obj = self;
			if ( !kind_of( obj, CImage))
				out("Not a Prima::Image descendant passed"); 
			if ( PImage(obj)-> w == 0 || PImage(obj)-> h == 0)
				out("Cannot save a null image"); 
		} else if ( images) {
			SV ** holder = av_fetch( images, i - xself, 0);
			if ( !holder) outd("Array panic on 'images[%d]' property", i - xself);
			obj = gimme_the_mate( *holder);
			if ( !obj) 
				outd("Invalid object reference passed in 'images[%d]'", i - xself);
			if ( !kind_of( obj, CImage))
				outd("Not a Prima::Image descendant passed in 'images[%d]'", i - xself);
			if ( PImage(obj)-> w == 0 || PImage(obj)-> h == 0)
				out("Cannot save a null image"); 
		} else
			out("Logic error");
		fi. frameMap[ i] = obj;
	} 

	/* all other properties to be parsed by codec */
	fi. extras = profile;

	/* finding codec */
	strcpy( error, "No appropriate codec found");
	{
		Bool * savemap = ( Bool*) malloc( sizeof( Bool) * imgCodecs. count);

		if ( !savemap)
			out("Not enough memory");
		memset( savemap, 0, sizeof( Bool) * imgCodecs. count);
		
		for ( i = 0; i < imgCodecs. count; i++) {
			c = ( PImgCodec ) ( imgCodecs. items[ i]);
			if ( !c-> instance)
				c-> instance = c-> vmt-> init( &c->info, c-> initParam);
			if ( !c-> instance) { /* failed to initialize, retry next time */
				savemap[ i] = true;
				continue;
			} 
		}

		/* checking 'codecID', if available */
		{
			SV * c = nil;
			if ( pexist( codecID))
				c = pget_sv( codecID);
			else if ( self && 
						hv_exists(( HV*)SvRV((( PAnyObject) self)-> mate), 
										"extras", 6)) {
				SV ** sv = hv_fetch(( HV*)SvRV((( PAnyObject) self)-> mate), "extras", 6, 0);
				if ( sv && SvOK( *sv) && SvROK( *sv) && SvTYPE( SvRV( *sv)) == SVt_PVHV) {
					HV * profile = ( HV *) SvRV( *sv);
					if ( pexist( codecID)) 
						c = pget_sv( codecID);
				}   
			}
			if ( c && SvOK( c)) { /* accept undef */
				codecID = SvIV( c);
				if ( codecID < 0) codecID = imgCodecs. count - codecID;
			}
		}
			
		/* find codec */
		c = nil;
		if ( codecID >= 0) {
			if ( codecID >= imgCodecs. count) 
				out("Codec index out of range");

			c = ( PImgCodec ) ( imgCodecs. items[ codecID]);
			if ( !( c-> info-> IOFlags & save_mask))
					out( ioreq ? 
						"Codec cannot save images to streams" : 
						"Codec cannot save images");
			
			if ( fi. frameMapSize > 1 && 
					!( c-> info-> IOFlags & IMG_SAVE_MULTIFRAME))
				out("Codec cannot save mutiframe images");
			if ( fi. append && 
					!( c-> info-> IOFlags & IMG_SAVE_APPEND))
				out("Codec cannot append frames");
			
			if (( fi. instance = c-> vmt-> open_save( c, &fi)) == NULL) 
				out("Codec cannot handle this file");

			if ( !autoConvert) {
				int j, *k = c-> info-> saveTypes, ok = 0;
				for ( j = 0; j < fi. frameMapSize; j++) {
					int type = PImage( fi. frameMap[j])-> type;
					while ( *k) {
						if ( type == *k) {
							ok = 1;
							break;
						}   
						k++;
					}   
				}   
				if ( !ok) 
					out("Image type(s) not supported by the codec specified");
			}      
		}   
	
		if ( !c && fileName) {
			int fileNameLen = strlen( fileName);
			/* finding codec by extension  */
			for ( i = 0; i < imgCodecs. count; i++) {
				int j = 0, found = false;
				if ( savemap[ i]) continue;
				c = ( PImgCodec ) ( imgCodecs. items[ i]);
				while ( c-> info-> fileExtensions[ j]) {
					char * ext = c-> info-> fileExtensions[ j];
					int extLen = strlen( ext);
					if ( extLen < fileNameLen && stricmp( fileName + fileNameLen - extLen, ext) == 0) {
						found = true;
						break;
					}   
					j++;
				} 

				if ( found) {
					savemap[ i] = true;
					if ( !( c-> info-> IOFlags & save_mask)) {
						c = nil;
						continue;
					}
					
					if ( fi. frameMapSize > 1
						&& !( c-> info-> IOFlags & IMG_SAVE_MULTIFRAME)) {
						c = nil;
						continue;
					}
					if ( fi. append 
						&& !( c-> info-> IOFlags & IMG_SAVE_APPEND)) {
						c = nil;
						continue;
					}
					
					if ( !autoConvert) {
						int j, *k = c-> info-> saveTypes, ok = 0;
						for ( j = 0; j < fi. frameMapSize; j++) {
							int type = PImage( fi. frameMap[j])-> type;
							while ( *k) {
								if ( type == *k) {
									ok = 1;
									break;
								}   
								k++;
							}   
						}   
						if ( !ok) {
							c = nil;
							continue;
						}
					}
				
					if (( fi. instance = c-> vmt-> open_save( c, &fi)) != NULL)
						break;
				}
				c = nil;
			}   
		}
		
		free( savemap);
		if ( !c) { /* use pre-formatted error string */
			err = true;
			goto EXIT_NOW;
		}   
	}   

	snprintf( error, 256, "Error saving %s", fileName ? fileName : "to stream");

	/* use common profile */
	def = c-> vmt-> save_defaults( c); 
	commonHV = newHV();
	if ( profile) {
		c-> vmt-> save_check_in( c, commonHV, profile);
		apc_img_profile_add( commonHV, profile, def);
	}
	
	/* saving */
	for ( i = 0; i < fi. frameMapSize; i++) {
		HV * profile = commonHV;
		PImage im;

		im = ( PImage) fi. frameMap[ i];
		if ( hv_exists(( HV*)SvRV( im-> mate), "extras", 6)) {
			SV ** sv = hv_fetch(( HV*)SvRV( im-> mate), "extras", 6, 0);
			if ( sv && SvOK( *sv) && SvROK( *sv) && SvTYPE( SvRV( *sv)) == SVt_PVHV) {
				HV * hv = ( HV *) SvRV( *sv);
				profile = newHV();
				apc_img_profile_add( profile, commonHV, commonHV);
				c-> vmt-> save_check_in( c, profile, hv); 
				apc_img_profile_add( profile, hv, def);
			}   
		}   
		
		fi. frame  = i;
		fi. object = fi. frameMap[ i];
		fi. objectExtras = profile;

		/* converting image to format with maximum bit depth and category flags match */
		if ( autoConvert) {
			int *k = c-> info-> saveTypes;
			int max = *k & imBPP, best = *k, supported = false;
			int flags = im-> type & imCategory, bestflags = *k & imCategory, bestmatch;
#define dBITS(a) int i = 0x80, match = ( flags & (a)) >> 8
#define CALCBITS(x) { \
	x = 0;\
	while ( i >>= 1 ) if ( match & i ) x++; \
}
			{
				dBITS( bestflags );
				CALCBITS( bestmatch )
			}
			while ( *k) {
				if ( im-> type == *k) {
					supported = true;
					break;
				}   
				if ( max < ( *k & imBPP)) {
					dBITS( bestflags = ( *k & imCategory));
					max       = *k & imBPP;
					best      = *k;
					CALCBITS( bestmatch );
				} else if ( max == ( *k & imBPP)) {
					dBITS( *k );
					int testmatch;
					CALCBITS( testmatch );
					if ( testmatch > bestmatch ) {
						best      = *k;
						bestflags = *k & imCategory;
						bestmatch = testmatch;
					}
				}
				k++;
			}
			if ( !supported) {
				im-> self-> set_type(( Handle) im, best);
				if ( best != im-> type) outd("Failed converting image to type '%04x'", best);
			}
		}      
		
		/* saving image */
		if ( !c-> vmt-> save( c, &fi)) {
			c-> vmt-> close_save( c, &fi);
			if ( fi. objectExtras != commonHV) sv_free(( SV *) fi. objectExtras);
			err = true;
			goto EXIT_NOW;
		}  

		if ( fi. objectExtras != commonHV) sv_free(( SV *) fi. objectExtras);
		ret++;
	}

	c-> vmt-> close_save( c, &fi);
	
EXIT_NOW:;
	free( fi. frameMap);
	if ( ioreq == NULL && fi. req != NULL && fi. req-> handle != NULL)
		fclose(( FILE*) fi. req-> handle);
	if ( err && fileName)
		unlink( fileName);
	if ( def)
		sv_free(( SV *) def);
	if ( commonHV)
		sv_free(( SV *) commonHV);
	return err ? -ret : ret;
#undef out   
#undef outd
}   
void
apc_img_codecs( PList ret)
{
	int i;
	PImgCodec c;
	
	CHK;
	for ( i = 0; i < imgCodecs. count; i++) {
		c = ( PImgCodec ) ( imgCodecs. items[ i]);
		if ( !c-> instance)
			c-> instance = c-> vmt-> init( &c->info, c-> initParam);
		if ( !c-> instance)  /* failed to initialize, retry next time */
			continue;
		list_add( ret, ( Handle) c);
	}  
}

int
apc_img_read_palette( PRGBColor palBuf, SV * palette, Bool triplets)
{
	AV * av;
	int i, count;
	Byte buf[768];

	if ( !SvROK( palette) || ( SvTYPE( SvRV( palette)) != SVt_PVAV))
		return 0;
	av = (AV *) SvRV( palette);
	count = av_len( av) + 1;

	if ( triplets) {
		if ( count > 768) count = 768;
		count -= count % 3;

		for ( i = 0; i < count; i++)
		{
			SV **itemHolder = av_fetch( av, i, 0);
			if ( itemHolder == nil) return 0;
			buf[ i] = SvIV( *itemHolder);
		}
		memcpy( palBuf, buf, count);
		return count/3;
	} else {
		int j;
		if ( count > 256) count = 256;

		for ( i = 0, j = 0; i < count; i++)
		{
			Color c;
			SV **itemHolder = av_fetch( av, i, 0);
			if ( itemHolder == nil) return 0;
			c = (Color)(SvIV( *itemHolder));
			buf[j++] = c & 0xFF;
			buf[j++] = (c >> 8) & 0xFF;
			buf[j++] = (c >> 16) & 0xFF;
		}
		memcpy( palBuf, buf, j);
		return count;
	}
}


static AV * fill_plist( char * key, char ** list, HV * profile)
{
	AV * av = newAV();
	if ( !list) list = imgPVEmptySet;
	while ( *list) {
		av_push( av, newSVpv( *list, 0));
		list++;
	}   
	(void) hv_store( profile, key, strlen( key), newRV_noinc(( SV *) av), 0);
	return av;
}  

static void fill_ilist( char * key, int * list, HV * profile)
{
	AV * av = newAV();
	if ( !list) list = imgIVEmptySet;
	while ( *list) {
		av_push( av, newSViv( *list));
		list++;
	}   
	(void) hv_store( profile, key, strlen( key), newRV_noinc(( SV *) av), 0);
}   


HV *  
apc_img_info2hash( PImgCodec codec)
{
	HV * profile, * hv;
	AV * av;
	PImgCodecInfo c;

	CHK;
	profile = newHV();
	if ( !codec) return profile;

	if ( !codec-> instance)
		codec-> instance = codec-> vmt-> init( &codec->info, codec-> initParam);
	if ( !codec-> instance) 
		return profile;
	c = codec-> info;
		
	pset_c( name,   c-> name);
	pset_c( vendor, c-> vendor);
	pset_i( versionMajor, c-> versionMaj);
	pset_i( versionMinor, c-> versionMin);
	fill_plist( "fileExtensions", c-> fileExtensions, profile);
	pset_c( fileType, c-> fileType);
	pset_c( fileShortType, c-> fileShortType);
	fill_plist( "featuresSupported", c-> featuresSupported, profile);
	pset_c( module,  c-> primaModule);
	pset_c( package, c-> primaPackage);
	pset_i( canLoad,         c-> IOFlags & IMG_LOAD_FROM_FILE);
	pset_i( canLoadStream  , c-> IOFlags & IMG_LOAD_FROM_STREAM);
	pset_i( canLoadMultiple, c-> IOFlags & IMG_LOAD_MULTIFRAME);
	pset_i( canSave        , c-> IOFlags & IMG_SAVE_TO_FILE);
	pset_i( canSaveStream  , c-> IOFlags & IMG_SAVE_TO_STREAM);
	pset_i( canSaveMultiple, c-> IOFlags & IMG_SAVE_MULTIFRAME);
	pset_i( canAppend,       c-> IOFlags & IMG_SAVE_APPEND);
	
	fill_ilist( "types",  c-> saveTypes, profile);

	if ( c-> IOFlags & ( IMG_LOAD_FROM_FILE|IMG_LOAD_FROM_STREAM)) {
		hv = codec-> vmt-> load_defaults( codec);
		if ( c-> IOFlags & IMG_LOAD_MULTIFRAME) {
			(void) hv_store( hv, "index",        5, newSViv(0),     0);
			(void) hv_store( hv, "map",          3, newSVsv(nilSV), 0);
			(void) hv_store( hv, "loadAll",      7, newSViv(0),     0);
			(void) hv_store( hv, "wantFrames",  10, newSViv(0),     0);
		}
		(void) hv_store( hv, "loadExtras",   10, newSViv(0),     0);
		(void) hv_store( hv, "noImageData",  11, newSViv(0),     0);
		(void) hv_store( hv, "iconUnmask",   10, newSViv(0),     0);
		(void) hv_store( hv, "noIncomplete", 12, newSViv(0),     0);
		(void) hv_store( hv, "className",     9, newSVpv("Prima::Image", 0), 0);
	} else
		hv = newHV();
	pset_sv_noinc( loadInput, newRV_noinc(( SV *) hv));
	
	av = fill_plist( "loadOutput", c-> loadOutput, profile);
	if ( c-> IOFlags & ( IMG_LOAD_FROM_FILE|IMG_LOAD_FROM_STREAM)) {
		if ( c-> IOFlags & IMG_LOAD_MULTIFRAME) 
			av_push( av, newSVpv( "frames", 0));
		av_push( av, newSVpv( "height", 0));
		av_push( av, newSVpv( "width",  0));
		av_push( av, newSVpv( "codecID", 0));
		av_push( av, newSVpv( "truncated", 0));
	}
	
	if ( c-> IOFlags & ( IMG_SAVE_TO_FILE|IMG_SAVE_TO_STREAM)) {
		hv = codec-> vmt-> save_defaults( codec);
		if ( c-> IOFlags & IMG_SAVE_MULTIFRAME) 
			(void) hv_store( hv, "append",       6, newSViv(0), 0);
		(void) hv_store( hv, "autoConvert", 11, newSViv(1), 0);
		(void) hv_store( hv, "codecID",     7,  newSVsv( nilSV), 0);
	} else
		hv = newHV();
	pset_sv_noinc( saveInput, newRV_noinc(( SV *) hv));
	return profile;
}   

char * imgPVEmptySet[] = { nil };
int    imgIVEmptySet[] = { 0 };

void
apc_img_notify_header_ready( PImgLoadFileInstance fi)
{
	Event e = { cmImageHeaderReady };
	e. gen. p = fi-> frameProperties;
	CImage( fi-> object)-> message( fi-> object, &e);
}

void
apc_img_notify_scanlines_ready( PImgLoadFileInstance fi, int scanlines, int direction)
{
	Event e;
	int height, width;
	unsigned int dt;
	struct timeval t;

	fi-> lastCachedScanline += scanlines;
	gettimeofday( &t, nil);
	dt = 
		t.tv_sec * 1000 + t.tv_usec / 1000 -
		fi-> lastEventTime.tv_sec * 1000 - fi-> lastEventTime.tv_usec / 1000;

	if ( dt < fi-> eventDelay) return;
	if ( fi-> lastEventScanline == fi-> lastCachedScanline) return;

	e. cmd = cmImageDataReady;
	height = PImage( fi-> object)-> h;
	width  = PImage( fi-> object)-> w;

	switch ( direction ) {
	case SCANLINES_DIR_TOP_TO_BOTTOM:
		e. gen. R. left   = 0;
		e. gen. R. right  = width - 1;
		e. gen. R. bottom = height - fi-> lastCachedScanline;
		e. gen. R. top    = height - fi-> lastEventScanline  - 1;
		break;
	case SCANLINES_DIR_BOTTOM_TO_TOP:
		e. gen. R. left   = 0;
		e. gen. R. right  = width - 1;
		e. gen. R. bottom = fi-> lastEventScanline;
		e. gen. R. top    = fi-> lastCachedScanline - 1;
		break;
	case SCANLINES_DIR_LEFT_TO_RIGHT:
		e. gen. R. left   = fi-> lastEventScanline;
		e. gen. R. right  = fi-> lastCachedScanline - 1;
		e. gen. R. bottom = 0;
		e. gen. R. top    = height - 1;
		break;
	case SCANLINES_DIR_RIGHT_TO_LEFT:
		e. gen. R. left    = width - fi-> lastCachedScanline;
		e. gen. R. right   = width - fi-> lastEventScanline - 1;
		e. gen. R. bottom  = 0;
		e. gen. R. top     = height - 1;
		break;
	}
	CImage( fi-> object)-> message( fi-> object, &e);

	gettimeofday( &fi-> lastEventTime, nil);
	fi-> lastEventScanline = fi-> lastCachedScanline;
}
#ifdef __cplusplus
}
#endif
