/*	MiscImageDecoderController.m

	Copyright 1996 Netsurfer Inc.

	This notice may not be removed from this source code.
	The use and distribution of this software is governed by the
	terms of the MiscKit license agreement.  Refer to the license
	document included with the MiscKit distribution for the terms.

	Author: <bbum@friday.com>
	Converted to OpenStep, September 1996, Uwe Hoffmann.
*/

#import <AppKit/AppKit.h>

#import "MiscImageDecoderController.h"
#import "MiscImageDecoder.h"
#import "MiscJPEGDecoder.h"
#import "MiscGIFDecoder.h"
#import "MiscTIFFDecoder.h"
#import "MiscPNGDecoder.h"

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

#define VERBOSE 		if(_SIDFlags.verboseMode)
#define METHOD  		NSLog(@"[%@ %s%@] ", \
                                [[self class] description], \
                 		        (self == (id)[self class]) ? "+" : "-", \
				                NSStringFromSelector(_cmd))
#define METHODnl  		NSLog(@"[%@ %s%@]\n", \
                                [[self class] description], \
                 		        (self == (id)[self class]) ? "+" : "-", \
				                NSStringFromSelector(_cmd))

@implementation MiscImageDecoderController
/*"An instance of this class is a controller for image decoding which   
 can be used to decode any of a number of different image
 formats depending on what decoder classes have been registered with the
 controller instance.
 
  Given an arbitrary path or data object,  an instance of #MiscImageDecoder
  will determine the type of the image, attempt to decode it, and, upon
  success, will return an instance of #NSBitmapImageRep 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
  #{MiscImageDecoderController}'s #{-addDecoderClass:} method.  When instantiated,
  an instance of MiscImageDecoderController automatically registers the
  #{MiscGIFDecoder}, #{MiscPNGDecoder} and #{MiscJPEGDecoder} classes.
  
  #{How Image Formats Are Determined}
  
  The #{-decodeFromData:...} methods determine the contents of
  the data object by calling {+canLoadFromData:} within each potentially
  eligible decoder's class.  The first decoder to return YES decodes the
  data object.
  
  #{-decodeFromFile:...} and friends try to determine the image type by
  looking up the file's extension in the #{decoderByType} dictionary.  If
  not found, the data is mapped into memory and passed to
  #{-decodeFromData:...}.
    
"*/

+ sharedInstance
/*"Returns an shared instance of MiscImageDecoderController."*/
{
	static id sharedInstance = nil;

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

- init
/*"Designated intializer. Adds decoder classes for the JPEG, GIF and PNG formats."*/
{
	_SIDFlags.verboseMode = NO;
	_SIDFlags.spewStderr = NO;
	lastImageCorrupt = NO;
	errorDelegate = nil;

	decoderByType = [[NSMutableDictionary allocWithZone:[self zone]] init];
	decoderClassByType = [[NSMutableDictionary allocWithZone:[self zone]] init];
	decoderList = [[NSMutableArray allocWithZone:[self zone]] init];

	[self addDecoderClass:[MiscGIFDecoder class]];
	[self addDecoderClass:[MiscJPEGDecoder class]];
	[self addDecoderClass:[MiscPNGDecoder class]];
	[self addDecoderClass:[MiscTIFFDecoder class]];	
	return self;
}

- (void)dealloc
{
	[decoderByType release];
	[decoderClassByType release];
	[decoderList release];
	return [super dealloc];
}

- (NSArray *)imageUnfilteredFileTypes
/*"Returns an array of strings containing all of the image 
types an instance of MiscImageDecoderController can decode."*/
{
	return [decoderByType allKeys];
}

- (BOOL)canInitWithData:(NSData *)data
/*"Returns YES if the controller thinks it can decode the data.
The receiver forwards data to each specific format decoding class, if any
return YES, this method immediately returns YES."*/
{
	NSParameterAssert(data);
	return [self decoderForData:data] ? YES : NO;
}

- (MiscImageDecoder *)decoderForType:(NSString *)imageType
/*"Returns the decoder for images of type imageType.  This can be used to
customize the particular image decoder."*/
{
	NSParameterAssert(imageType);
	VERBOSE {
		METHODnl;
		NSLog(@"%@", imageType);
	}
	return (MiscImageDecoder *)[decoderByType objectForKey:imageType];
}

- (Class)decoderClassForType:(NSString *)imageType
/*"Returns the decoder class for images of type imageType.  This can be
used to customize the particular image decoder."*/
{
	NSParameterAssert(imageType);
	VERBOSE {
		METHODnl;
		NSLog(@"%@", imageType);
	}
	return (Class)[decoderClassByType objectForKey:imageType];
}

- (MiscImageDecoder *)decoderForData:(NSData *)data
/*"Returns the decoder object that should be used to decode data.  If an
appropriate decoder object is not available, returns nil."*/
{
	unsigned int i,n;
	MiscImageDecoder *decoder;
	
	NSParameterAssert(data);
	n = [decoderList count];
	for(i = 0; i < n; i++){
		decoder = (MiscImageDecoder *)[decoderList objectAtIndex:i];
		if([[decoder class] canInitWithData:data])
			return decoder;
	}
	return nil;
}

- (Class)decoderClassForData:(NSData *)data
/*"Returns the decoder Class used to decode data.  
If no class is available, returns nil."*/
{
	return [[self decoderForData:data] class];
}

- (void)addDecoderClass:aClass
/*"Adds aClass as a possible decoder."*/
{
	MiscImageDecoder *newDecoder;
	unsigned int i,n;
	NSArray *decoderTypes;
	NSString *theType;

	VERBOSE {
		METHOD;
	}
	
	if(!aClass || [aClass isKindOfClass:[MiscImageDecoder class]])
		return;


	newDecoder = (MiscImageDecoder *)[[aClass allocWithZone:[self zone]] init];

	decoderTypes = [aClass imageUnfilteredFileTypes];
	n = [decoderTypes count];

	for(i = 0; i < n; i++){

		theType = [decoderTypes objectAtIndex:i];

		VERBOSE {
			METHODnl;
			NSLog(@"Adding type: %@", theType);
		}
		
		[decoderByType setObject:newDecoder forKey:theType];
		[decoderClassByType setObject:aClass forKey:theType];
	}

	// add to decoderList
	[decoderList addObject:newDecoder];
	
	[newDecoder setImageDepth:imageDepth];
	[newDecoder setVerboseMode:_SIDFlags.verboseMode];
	[newDecoder setErrorDelegate:self];

	[newDecoder release];
}

- (NSBitmapImageRep *)decodeFromFile:(NSString *)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 {-decodeFromData:} in hopes that one of the image decoders will
  decide it can decode the data contents. Returns the decoded image upon 
  success and nil upon failure. This
  method is a cover for #{-decodeFromFile:withDecoder:}.
"*/
{
	return [self decodeFromFile:filePath withDecoder:nil];
}

- (NSBitmapImageRep *)decodeFromFile:(NSString *)filePath withDecoder:(MiscImageDecoder *)aDecoder
/*"
  Attempts to decode the contents of filePath using aDecoder.
  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 #{-decodeFromData:...}. Returns the decoded image upon success 
  or nil upon failure."*/
{
	MiscImageDecoder *decoder;
	NSBitmapImageRep *result;

	NSParameterAssert(filePath);
	
	VERBOSE {
		METHODnl;
		NSLog(filePath);
	}
	if(aDecoder) {
		result = [aDecoder decodeFromFile:filePath];
		lastImageCorrupt = [aDecoder lastImageCorrupt];
		return result;
	}
	decoder = (MiscImageDecoder *)[decoderByType objectForKey:[filePath pathExtension]];
	if(decoder) {
		result = [decoder decodeFromFile:filePath];
		lastImageCorrupt = [decoder lastImageCorrupt];
		return result;
	}	
	return [self decodeFromData:[NSData dataWithContentsOfMappedFile:filePath] withDecoder:nil];

}


- (NSBitmapImageRep *)decodeFromData:(NSData *)data
/*"This method is a cover for #{-decodeFromData:withDecoder:}."*/
{
	return [self decodeFromData:data withDecoder:nil];
}


- (NSBitmapImageRep *)decodeFromData:(NSData *)data withDecoder:(MiscImageDecoder *)aDecoder
/*"
  Decodes the contents of data  using the decoder
  aDecoder. If aDecoder is not defined,
  this method determines the decoder using the #{-decoderForData:}
  method. Returns the decoded image upon success, or nil upon failure.
"*/
{
	MiscImageDecoder *decoder;
	NSBitmapImageRep *result;

	NSParameterAssert(data);

	decoder = aDecoder ? aDecoder : [self decoderForData:data];
	if(!decoder){
		lastImageCorrupt = YES;	
		return nil;
	}
	result = [decoder decodeFromData:data];
	lastImageCorrupt = [decoder lastImageCorrupt];
	return result;
}

- (void)setImageDepth:(NSWindowDepth)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.
"*/
{
	unsigned int i,n;

	n = [decoderList count];
	
	imageDepth = aDepth;

	for(i = 0; i < n ;i++)
		[[decoderList objectAtIndex:i] setImageDepth:imageDepth];
	
}

- (NSWindowDepth)imageDepth
/*"Returns the image depth of the receiver."*/
{
	return imageDepth;
}

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

- (NSArray *)decoderArray
/*"Returns the array of decoders controlled by the receiver."*/
{
	return decoderList;
}

- (NSDictionary *)decoderByType
/*"Returns the decoder by type dictionary of the receiver."*/
{
	return decoderByType;
}

- (NSDictionary *)decoderClassByType
/*"Returns the decoder class by type dictionary of the receiver."*/
{
	return decoderClassByType;
}

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

- (void)setVerboseMode:(BOOL)aFlag
/*"Enabled/disables verbose mode.  Verbose mode spews a HUGE quantity 
of state information to stderr."*/
{
	unsigned int i,n;

	n = [decoderList count];
	
	_SIDFlags.verboseMode = aFlag;

	for(i = 0; i < n ;i++)
		[[decoderList objectAtIndex:i] setVerboseMode:aFlag];
	
}

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

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

- (void)setErrorDelegate:(id <MiscImageDecoderErrorDelegate>)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;
}

- (id <MiscImageDecoderErrorDelegate>)errorDelegate
/*"Returns the image decoder's error delegate."*/
{
	return errorDelegate;
}

- (void)decoder:(MiscImageDecoder *)aDecoder
	spewMessage:(NSString *)errMsg
	withSeverity:(MiscImageDecoderErrorSeverity)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 #{NSLog()} function.
"*/
{
	if(errorDelegate)
		[errorDelegate decoder:aDecoder
					   spewMessage:errMsg
					   withSeverity:aSeverity];

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


@end
