/*  
** 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 "SurfImageDecoder.h"

#import <objc/objc-runtime.h>

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

#define VERBOSE 		if(_SIDFlags.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 BOOL2STR(_b_)	(_b_ ? "Yes": "No")

@implementation SurfImageDecoder
/*"
 * A controller for image decoding.  This class implements a generic image
 * decoder that can be used to decde any of a number of different image
 * formats depending on what decoder classes have been registered with the
 * instance.
 * 
 * Given an arbitrary path or stream,  an instance of #SurfImageDecoder
 * will determine the type of the image, attempt to decode it, and, upon
 * success, will return an instance of #NXImage containing the decoded
 * image.
 * 
 * Instances are designed to be used in a multi-threaded context.  Any
 * instance can be decoding image in any thread of execution while other
 * instances are simultaneously decoding images.  A single instance
 * %{cannot} decode multiple images from different threads;  to attempt
 * this guarantees catastrophic failure of the environment.
 * 
 * #{Registrering Custom Decoders}
 * 
 * A class can register to be a potential decoder by calling
 * #{SurfImageDecoder}'s #{-addDecoderClass:} method.  When instantiated,
 * an instance of SurfImageDecoder automatically registers the
 * #{SurfGIFDecoder} and #{SurfJPEGDecoder} classes.
 * 
 * #{How Image Formats Are Determined}
 * 
 * The #{-loadFromStream:...} family of methods determines the contents of
 * the stream by calling {+canLoadFromStream:} within each potentially
 * eligible decoder's class.  The first decoder to return YES decodes the
 * stream.
 * 
 * #{-loadFromFile:...} and friends try to determine the image type by
 * looking up the file's extension in the #{decoderByType} HashTable.  If
 * not found, the stream is mapped into memory and passed to
 * #{-loadFromStream:...}.
 * 
 * Note: the #{-lastImageCorrupt} method assumes that the last decoder used
 * still exists and is still viable;  ie:  don't call #{-lastImageCorrupt}
 * if you are using external decoders that may have been freed between the
 * time an image decoding attempt was made and the time that
 * #{-lastImageCorrupt} is called!.
 * 
"*/

+ sharedInstance
/*"
 * Returns the shared instance of SurfImageDecoder.  Should only be used in
 * applications that require a single image decoder.
"*/
{
	static id sharedInstance = nil;

	if (!sharedInstance) {
		sharedInstance = [self alloc];
		[sharedInstance init];
	}

	return sharedInstance;		
}

- init
/*"
 * Designated intializer.  Creates entries in %decoderByType for each
 * decoder type, but does not allocate/initialize any decoders.
"*/
{
	VERBOSE {
		METHOD;
	}
	decoderByType	   = [[HashTable allocFromZone:[self zone]]
								 initKeyDesc:"*" valueDesc:"@"];
	decoderClassByType = [[HashTable allocFromZone:[self zone]]
								 initKeyDesc:"*" valueDesc:"@"];
	decoderList		   = [[List allocFromZone:[self zone]] init];
	_decoderReferences = [[HashTable allocFromZone:[self zone]]
								 initKeyDesc:"@" valueDesc:"i"];

	[self addDecoderClass: objc_lookUpClass("SurfGIFDecoder")];
	[self addDecoderClass: objc_lookUpClass("SurfJPEGDecoder")];
	[self addDecoderClass: objc_lookUpClass("SurfTIFFDecoder")];
	[self addDecoderClass: objc_lookUpClass("SurfPNGDecoder")];
	
	return self;
}

- free
/*"
 * Frees the image decoder and all specific type decoders.
"*/
{
	[[decoderByType empty] free];
	[[decoderClassByType empty] free];
	[[decoderList freeObjects] free];
	NX_FREE((char **) _unfilteredFileTypes);

	[[_decoderReferences empty] free];

	return [super free];
}

- (const char *const *)imageUnfilteredFileTypes
/*"
 * Returns NULL terminated array of strings containing all of the image
 * types an instance of SurfImageDecoder can decode.
"*/
{
	if(!_unfilteredFileTypes) {
		NXHashState theState;
		const char *type;
		id value;
		int index;

		_unfilteredFileTypes = NXZoneMalloc([self zone],
											  sizeof(char *) *
											  ([decoderByType count] + 1));
		theState = [decoderByType initState];
		index	 = 0;
		while([decoderByType nextState:&theState
							 key: (void **) &type
							 value: (void **) &value]) {
			_unfilteredFileTypes[index] = type;
			index++;
		}
		_unfilteredFileTypes[index] = NULL;		
	}
	return _unfilteredFileTypes;
}

- (BOOL)canLoadFromStream:(NXStream *)aStream
/*"
 * Returns YES if the decoder thinks it can decode the data in aStream.
 * This  forwards aStream to each specific format decoding class, if any
 * return YES, this method immediately returns YES.
 * 
 * The current position in the stream is unchanged.
"*/
{
	return [self decoderForStream: aStream] ? YES : NO;
}

- decoderForType:(const char *) imageType
/*"
 * Returns the decoder for images of type imageType.  This can be used to
 * customize the particular image decoder.
"*/
{
	if(!imageType)
		return nil;
	VERBOSE {
		METHODnl;
		fprintf(stderr, "{%s}\n", imageType);
	}
	return [decoderByType valueForKey: NXUniqueString(imageType)];
}

- (Class) decoderClassForType:(const char *) imageType
/*"
 * Returns the decoder class for images of type imageType.  This can be
 * used to customize the particular image decoder.
"*/
{
	if(!imageType)
		return nil;
	VERBOSE {
		METHODnl;
		fprintf(stderr, "{%s}\n", imageType);
	}
	return [decoderClassByType valueForKey: NXUniqueString(imageType)];
}

- decoderForStream:(NXStream *) aStream
/*"
 * Returns the decoder object that should be used to decode aStream.  If an
 * appropriate decoder object is not available, returns nil.
"*/
{
 /* -----------------------------------------------------------------------
  * Traverse decoderList: call +canLoadFromStream: on each.  If one
  * returns YES, return that instance.
  * -----------------------------------------------------------------------
  */
	id *ida;
	id *max;

	ida = NX_ADDRESS(decoderList);
	max = ida + [decoderList count];
	for(;ida<max;ida++)
       /*
        * if is the implementor's responsibility to ensure that the
        * stream's seek is unaltered upon return!!!
        */
		if([[*ida class] canLoadFromStream: aStream])
			return *ida;

	return nil;
}

- (Class) decoderClassForStream:(NXStream *) aStream
/*"
 * Returns the decoder Class used to decode aStream.  If no class is
 * available, returns nil.
"*/
{
	return [[self decoderForStream: aStream] class];
}

- addDecoderClass: aClass
/*"
 * Adds aClass as a possible decoder.  This is accomplished by retrieving
 * the types accepted by the decoder class by calling
 * #{+imageUnfilteredFileTypes}.  The returned types are appended to the
 * list of types that can be converted by this image converter object.  An
 * instance of the class is allocated and an entry is created in
 * %hashedExtensions for each type the decoder can decode.  If a decoder
 * that can decode that particular type already exists in
 * %hashedExtensions, it is replaced (but not freed).
"*/
{
	id newDecoder;
	int refCount;
	const char * const *decoderTypes;

	VERBOSE {
		METHOD;
	}
	
   /*
    * check to see if aClass is defined and responds to some set of methods
    * that define it to be a decoder.  Should we create a protocol that
    * defines decoder-ness?
    */
	if(!aClass)
		return nil;

   /*
    * Set the _lastDecoder to nil.  This prevents anyone from doing
    * something that causes the _lastDecoder to be sent a message when the
    * _lastDecoder has already been freed!
    */
	_lastDecoder = nil;

   /*
    * allocate instance of decoder
    */
	newDecoder = [[aClass allocFromZone:[self zone]] init];

   /*
    * add to _decoderReferences hashtable
    */
	refCount = 0;

   /*
    * ask decoder for +imageUnfilteredFileTypes;  traverse array of types
    */
	decoderTypes = [aClass imageUnfilteredFileTypes];
	for (;*decoderTypes;decoderTypes++) {
		const char *theType = *decoderTypes;
		int oldRefCount;
		id oldDecoder;

		VERBOSE {
			METHODnl;
			fprintf(stderr, "Adding type: {%s}\n", theType);
		}
		
      /*
       * insert decoder and decoder class in decoderByType hash;  with type
       * as key.
       */
		oldDecoder = [decoderByType valueForKey: theType];
		[decoderByType insertKey: theType value: newDecoder];
		[decoderClassByType insertKey: theType value: aClass];

       /*
        * decremement reference count of old decoder-- if it is zero, free
        * it.
        */
		oldRefCount = (int) [_decoderReferences valueForKey: oldDecoder];
		--oldRefCount; 
		if(oldRefCount == 0) {
			[_decoderReferences removeKey: oldDecoder];
			[oldDecoder free];
		} else if (oldRefCount != -1) {
			[_decoderReferences insertKey: oldDecoder
								value:(void *)oldRefCount];
		}

       /*
        * increment reference count for new decoder.
        */
		refCount++;
	}

	// set reference count
	[_decoderReferences insertKey:newDecoder value:(void *)refCount];

	// add to decoderList
	[decoderList addObject:newDecoder];
	
	/*
	 * propagate configuration (imageDepth)
	 */
	[newDecoder setImageDepth:imageDepth];
	[newDecoder setVerboseMode:_SIDFlags.verboseMode];
	[newDecoder setErrorDelegate:self];

	NX_FREE(_unfilteredFileTypes);
	_unfilteredFileTypes = NULL;

	[self imageUnfilteredFileTypes];
	
	return self;	
}

- decodeFromFile:(const char *) filePath
/*"
 * Attempts to decode the data in filePath as an image.  Uses the
 * filePath's extension to determine the image type.  If it fails to find a
 * decoder given the filePath's extension, the method maps the filePath and
 * calls {-decodeFromStream:} in hopes that one of the image decoders will
 * decide it can decode the stream contents.
 * 
 * Returns the decoded image upon success and nil upon failure.  This
 * methdo is a cover for #{-decodeFromFile:intoImage:withDecoder:}
"*/
{
	return [self decodeFromFile: filePath
				 intoImage: nil
				 withDecoder: nil];
}

- decodeFromFile:(const char *) filePath intoImage: anImage
/*"
 * Attempts to decode the contents of filePath in the same fashion as
 * #{-decodeFromFile:}.  Instead of allocating an instance of NXImage, this
 * method will append the newly decoded image in an NXBitmapImageRep using
 * the #{-useRepresentation:} method.
 * 
 * Returns the decoded image upon success or nil upon failure.  This
 * methdo is a cover for #{-decodeFromFile:intoImage:withDecoder:}
"*/
{
	return [self decodeFromFile: filePath
				 intoImage: anImage
				 withDecoder: nil];
}

- decodeFromFile:(const char *)	filePath
	   intoImage: anImage
	 withDecoder: aDecoder
/*"
 * Attempts to decode the contents of filePath using aDecoder. If anImage
 * is defined, the decoded representation will be appended to anImage using
 * the #{-useRepresentation:} method.  If aDecoder is not defined, the
 * method will try to identify the decoder to be used from filePath's
 * extension.  If that fails, this method will map the file and pass
 * control #{-decodeFromStream:...}.
 * 
 * Returns the decoded image upon success or nil upon failure.
"*/
{
	NXStream *stream;
	if (!aDecoder) {
		// grab the file's suffix

		// look up decoder
	}

	stream = NXMapFile(filePath, NX_READONLY);
	if(!stream)
		return nil;

	VERBOSE {
		METHODnl;
		fprintf(stderr, "%s\n", filePath);
	}

	if(aDecoder) {
		_lastDecoder = aDecoder;
		anImage = [aDecoder decodeFromStream: stream
						  intoImage: anImage];
	} else
		anImage = [self decodeFromStream: stream
					  intoImage: anImage
					  withDecoder: nil];

	NXCloseMemory(stream, NX_FREEBUFFER);

	return anImage;
}


- decodeFromStream:(NXStream *) aStream
/*"
 * This method is a cover for #{-decodeFromStream:intoImage:withDecoder:}.
"*/
{
	return [self decodeFromStream: aStream intoImage: nil withDecoder: nil];
}

- decodeFromStream:(NXStream *) aStream intoImage:anImage
/*"
 * This method is a cover for #{-decodeFromStream:intoImage:withDecoder:}.
"*/
{
	return [self decodeFromStream: aStream intoImage: nil withDecoder: nil];
}

- decodeFromStream:(NXStream *) aStream
		 intoImage:anImage
	   withDecoder:aDecoder
/*"
 * Decodes the contents of aStream into the image anImage using the decoder
 * aDecoder.  If anImage is defined, the decoded image will be appended
 * using the #{-useRepresentation:} method.  If aDecoder is not defined,
 * this method determines the decoder using the #{-decoderForStream:}
 * method.
 * 
 * Returns the decoded image upon success, or nil upon failure.
"*/
{
	if(!aStream)
		return nil;

	aDecoder = aDecoder ? aDecoder : [self decoderForStream: aStream];
	_lastDecoder = aDecoder;
	if(!aDecoder)
		return nil;

	return [aDecoder decodeFromStream: aStream intoImage: anImage];
}

- (void) setImageDepth:(NXWindowDepth) aDepth
/*"
 * Sets the target image depth to aDepth.  Some of the image decoders can
 * optimize the decoding process to a specific depth.  This method will
 * propagate aDepth to all decoders using the #{setImageDepth} method;
 * whether or not a decoder can actually decode to that depth is entirely
 * up to the decoder.
 * 
 * Generally, image decoders default to producing 24-bit deep images.
"*/
{
	id *ida;
	id *max;
	
	imageDepth = aDepth;

	ida = NX_ADDRESS(decoderList);
	max = ida + [decoderList count];
	for(;ida<max;ida++)
		[*ida setImageDepth: imageDepth];
	
}
- (NXWindowDepth) imageDepth
/*"
 * Returns %{imageDepth}.
"*/
{
	return imageDepth;
}

- (BOOL) lastImageCorrupt
/*"
 * Returns YES if last the image that was decoded contained some kind of
 * error.
"*/
{
	return _lastDecoder ? [_lastDecoder lastImageCorrupt] : YES;
}

- decoderList
/*"
 * Returns the decoderList List.
"*/
{
	return decoderList;
}

- decoderByType
/*"
 * Returns the decoderByType HashTable.
"*/
{
	return decoderByType;
}

- decoderClassByType
/*"
 * Returns the decoderClassByType HashTable.
"*/
{
	return decoderClassByType;
}


- (BOOL) verboseMode
/*"
 * Returns YES if verbose mode is enabled.
"*/
{
	return _SIDFlags.verboseMode;
}

- (void) setVerboseMode:(BOOL) aFlag
/*"
 * Enabled/disables verbose mode.  Verbose mode spews a HUGE quantity of
 * state information to stderr.
"*/
{
	id *ida;
	id *max;
	
	_SIDFlags.verboseMode = aFlag;

	ida = NX_ADDRESS(decoderList);
	max = ida + [decoderList count];
	for(;ida<max;ida++)
		[*ida setVerboseMode: _SIDFlags.verboseMode];
}

- (void) setSpewToStderr:(BOOL) aFlag
/*"
 * If aFlag is YES, error messages geneerated by decoders will be printed
 * via NXLogError().
"*/
{
	_SIDFlags.spewStderr = aFlag;
}

- (BOOL) spewToStderr
/*"
 * Returns YES if error messages will be printed to the console/stderr.
"*/
{
	return _SIDFlags.spewStderr;
}

- (void) setErrorDelegate:(id <SurfErrorDelegate>) aDelegate
/*"
 * Sets the error delegate to aDelegate.  aDelegate's implementation of
 * #{-decoder:spewMessage:withSeverity:} will be invoked whenever a decoder
 * generates an error message.
"*/
{
	errorDelegate = aDelegate;
}

- errorDelegate
/*"
 * Returns the image decoder's error delegate.
"*/
{
	return errorDelegate;
}

- (void) decoder: aDecoder
	 spewMessage:(const char *) errMsg
	withSeverity:(SurfErrorSeverity) aSeverity
/*"
 * Invoked whenever a decoder generates an error.  If #errorDelegate has
 * been set, it will be notified of the error message via the
 * #{-decoder:spewMessage:withSeverity:} method.  If #spewToStderr has been
 * enabled, the message will be printed via the #{NXLogError()} function.
"*/
{
	if(errorDelegate)
		[errorDelegate decoder:aDecoder
					   spewMessage:errMsg
					   withSeverity:aSeverity];

	if(_SIDFlags.spewStderr)
		NXLogError("*** Image Decoder Msg *** {%s}", errMsg);
}

- (void)printForDebugger:(NXStream *)aStream
/*"
 * Prints a summary of the decoders state to aStream.
"*/
{
}

- (void) dumpTypes
/*"
 *  Pretty-prints file extensions to stderr.
"*/
{
	char ** typeArray = (char **) [self imageUnfilteredFileTypes];
	int count;

	count = 0;
	while(*typeArray) {
		typeArray++;
		count++;
	}

	NXLogError("Can decode images with %d different extensions:\n", count);
	typeArray = (char **) [self imageUnfilteredFileTypes];
	while (*typeArray) {
		NXLogError("\t'%s'", *typeArray);
		typeArray++;
	}
	
}
@end
