/** <title>NSImage</title>

   <abstract>Load, manipulate and display images</abstract>

   Copyright (C) 1996 Free Software Foundation, Inc.
   
   Author: Adam Fedor <fedor@colorado.edu>
   Date: Feb 1996
   
   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
   
   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.
   
   You should have received a copy of the GNU Library General Public
   License along with this library; see the file COPYING.LIB.
   If not, write to the Free Software Foundation,
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
   */ 
#include "config.h"
#include <string.h>

#include <Foundation/NSString.h>
#include <Foundation/NSException.h>
#include <Foundation/NSFileManager.h>
#include <Foundation/NSArray.h>
#include <Foundation/NSValue.h>
#include <Foundation/NSDictionary.h>
#include <Foundation/NSBundle.h>
#include <Foundation/NSString.h>
#include <Foundation/NSDebug.h>

#include "AppKit/NSImage.h"
#include "AppKit/AppKitExceptions.h"
#include "AppKit/NSBitmapImageRep.h"
#include "AppKit/NSCachedImageRep.h"
#include "AppKit/NSView.h"
#include "AppKit/NSWindow.h"
#include "AppKit/NSScreen.h"
#include "AppKit/NSColor.h"
#include "AppKit/NSPasteboard.h"
#include "AppKit/NSPrintOperation.h"
#include "AppKit/PSOperators.h"
#include "GNUstepGUI/GSDisplayServer.h"

BOOL	NSImageForceCaching = NO;	/* use on missmatch	*/

@implementation NSBundle (NSImageAdditions)

- (NSString*) pathForImageResource: (NSString*)name
{
  NSString	*ext = [name pathExtension];
  NSString	*path = nil;

  if ((ext == nil) || [ext isEqualToString:@""])
    {
      NSArray	*types = [NSImage imageUnfilteredFileTypes];
      unsigned	c = [types count];
      unsigned	i;

      for (i = 0; path == nil && i < c; i++)
	{
	  ext = [types objectAtIndex: i];
	  path = [self pathForResource: name ofType: ext];
	}
    }
  else
    {
      name = [name stringByDeletingPathExtension];
      path = [self pathForResource: name ofType: ext];
    }
  return path;
}

@end

@interface	GSRepData : NSObject
{
@public
  NSImageRep	*rep;
  NSImageRep	*original;
  NSColor	*bg;
}
@end

@implementation	GSRepData
- (id) copyWithZone: (NSZone*)z
{
  GSRepData	*c = (GSRepData*)NSCopyObject(self, 0, z);

  if (c->rep)
    c->rep = [c->rep copyWithZone: z];
  if (c->bg)
    c->bg = [c->bg copyWithZone: z];
  return c;
}

- (void) dealloc
{
  TEST_RELEASE(rep);
  TEST_RELEASE(bg);
  NSDeallocateObject(self);
}
@end

/* Class variables and functions for class methods */
static NSMutableDictionary	*nameDict = nil;
static NSDictionary		*nsmapping = nil;
static NSColor			*clearColor = nil;
static Class			cachedClass = 0;
static Class			bitmapClass = 0;

static NSArray *iterate_reps_for_types(NSArray *imageReps, SEL method);

/* Find the GSRepData object holding a representation */
static GSRepData*
repd_for_rep(NSArray *_reps, NSImageRep *rep)
{
  NSEnumerator	*enumerator = [_reps objectEnumerator];
  IMP		nextImp = [enumerator methodForSelector: @selector(nextObject)];
  GSRepData	*repd;

  while ((repd = (*nextImp)(enumerator, @selector(nextObject))) != nil)
    {
      if (repd->rep == rep)
	{
	  return repd;
	}
    }
  [NSException raise: NSInternalInconsistencyException
	      format: @"Cannot find stored representation"];
  /* NOT REACHED */
  return nil;
}

@interface NSImage (Private)
- (BOOL) _useFromFile: (NSString *)fileName;
- (BOOL) _loadFromData: (NSData *)data;
- (BOOL) _loadFromFile: (NSString *)fileName;
- (NSImageRep*) _cacheForRep: (NSImageRep*)rep;
- (NSImageRep*) _doImageCache;
@end

@implementation NSImage

+ (void) initialize
{
  if (self == [NSImage class])
    {
      NSString *path = [NSBundle pathForLibraryResource: @"nsmapping"
				                 ofType: @"strings"
				             inDirectory: @"Images"];

      // Initial version
      [self setVersion: 1];

      // initialize the class variables
      nameDict = [[NSMutableDictionary alloc] initWithCapacity: 10];
      if (path)
	nsmapping = RETAIN([[NSString stringWithContentsOfFile: path]
				propertyListFromStringsFileFormat]);
      clearColor = RETAIN([NSColor clearColor]);
      cachedClass = [NSCachedImageRep class];
      bitmapClass = [NSBitmapImageRep class];
    }
}

+ (id) imageNamed: (NSString *)aName
{
  NSString	*realName = [nsmapping objectForKey: aName];
  NSImage	*image;

  if (realName)
    aName = realName;

  image = (NSImage*)[nameDict objectForKey: aName];
 
  if (image == nil)
    {
      NSString	*ext;
      NSString	*path = nil;
      NSBundle	*main_bundle;
      NSArray	*array;
      NSString	*the_name = aName;

      // FIXME: This should use [NSBundle pathForImageResource], but this will 
      // only allow imageUnfilteredFileTypes.
      /* If there is no image with that name, search in the main bundle */
      main_bundle = [NSBundle mainBundle];
      ext = [aName pathExtension];
      if (ext != nil && [ext length] == 0)
	{
	  ext = nil;
	}

      /* Check if extension is one of the image types */
      array = [self imageFileTypes];
      if ([array indexOfObject: ext] != NSNotFound)
	{
	  /* Extension is one of the image types
	     So remove from the name */
	  the_name = [aName stringByDeletingPathExtension];
	}
      else
	{
	  /* Otherwise extension is not an image type
	     So leave it alone */
	  the_name = aName;
	  ext = nil;
	}

      /* First search locally */
      if (ext)
	path = [main_bundle pathForResource: the_name ofType: ext];
      else 
	{
	  id o, e;

	  e = [array objectEnumerator];
	  while ((o = [e nextObject]))
	    {
	      path = [main_bundle pathForResource: the_name 
		        ofType: o];
	      if (path != nil && [path length] != 0)
		break;
	    }
	}

      /* If not found then search in system */
      if (!path)
	{
	  if (ext)
	    {
	      path = [NSBundle pathForLibraryResource: the_name
				               ofType: ext
				          inDirectory: @"Images"];
	    }
	  else 
	    {
	      id o, e;

	      e = [array objectEnumerator];
	      while ((o = [e nextObject]))
		{
		  path = [NSBundle pathForLibraryResource: the_name
			       	                   ofType: o
				              inDirectory: @"Images"];
		  if (path != nil && [path length] != 0)
		    break;
		}
	    }
	}

      if ([path length] != 0) 
	{
	  image = [[self allocWithZone: NSDefaultMallocZone()]
		initByReferencingFile: path];
	  if (image != nil)
	    {
	      [image setName: aName];
	      RELEASE(image);		// Retained in dictionary.
	      image->_flags.archiveByName = YES;
	    }
	  return image;
	}
    }
  
  return image;
}

- (id) init
{
  return [self initWithSize: NSMakeSize(0, 0)];
}

// Designated initializer for nearly everything.
- (id) initWithSize: (NSSize)aSize
{
  [super init];

  //_flags.archiveByName = NO;
  //_flags.scalable = NO;
  //_flags.dataRetained = NO;
  //_flags.flipDraw = NO;
  if (aSize.width && aSize.height) 
    {
      _size = aSize;
      _flags.sizeWasExplicitlySet = YES;
    }
  //_flags.usesEPSOnResolutionMismatch = NO;
  _flags.colorMatchPreferred = YES;
  _flags.multipleResolutionMatching = YES;
  //_flags.cacheSeparately = NO;
  //_flags.unboundedCacheDepth = NO;
  //_flags.syncLoad = NO;
  _reps = [[NSMutableArray alloc] initWithCapacity: 2];
  ASSIGN(_color, clearColor);
  _cacheMode = NSImageCacheDefault;
  
  return self;
}

- (id) initByReferencingFile: (NSString *)fileName
{
  self = [self init];

  if (![self _useFromFile: fileName])
    {
      RELEASE(self);
      return nil;
    }
  _flags.archiveByName = YES;

  return self;
}

- (id) initWithContentsOfFile: (NSString *)fileName
{
  self = [self init];

  _flags.dataRetained = YES;
  if (![self _loadFromFile: fileName])
    {
      RELEASE(self);
      return nil;
    }

  return self;
}

- (id) initWithData: (NSData *)data
{
  self = [self init];

  _flags.dataRetained = YES;
  if (![self _loadFromData: data])
    {
      RELEASE(self);
      return nil;
    }

  return self;
}

- (id)initWithBitmapHandle:(void *)bitmap
{
  NSImageRep *rep = [[NSBitmapImageRep alloc] initWithBitmapHandle: bitmap];
  
  if (rep == nil)
    {
      RELEASE(self);
      return nil;
    }
  self = [self init];
  [self addRepresentation: rep];
  RELEASE(rep);
  return self;
}

- (id)initWithIconHandle:(void *)icon
{
  // Only needed on MS Windows
  NSImageRep *rep = [[NSBitmapImageRep alloc] initWithIconHandle: icon];
  
  if (rep == nil)
    {
      RELEASE(self);
      return nil;
    }
  self = [self init];
  [self addRepresentation: rep];
  RELEASE(rep);
  return self;
}

- (id)initWithContentsOfURL:(NSURL *)anURL
{
  NSArray *array = [NSImageRep imageRepsWithContentsOfURL: anURL];

  if (!array)
    {
      RELEASE(self);
      return nil;
    }
  self = [self init];
  _flags.dataRetained = YES;
  [self addRepresentations: array];
  return self;
}

- (id) initWithPasteboard: (NSPasteboard *)pasteboard
{
  NSArray *reps = [NSImageRep imageRepsWithPasteboard: pasteboard];
  self = [self init];

  if (reps != nil)
    [self addRepresentations: reps]; 
  else
    {
      NSArray *array = [pasteboard propertyListForType: NSFilenamesPboardType];
      NSString* file; 
      
      if ((array == nil) || ([array count] == 0) ||
	  (file = [array objectAtIndex: 0]) == nil || 
	  ![self _loadFromFile: file])
        {
	  RELEASE(self);
	  return nil;
	} 
    }
  _flags.dataRetained = YES;
  
  return self;
}

- (void) dealloc
{
  RELEASE(_reps);
  /* Make sure we don't remove name from the nameDict if we are just a copy
     of the named image, not the original image */
  if (_name && self == [nameDict objectForKey: _name]) 
    [nameDict removeObjectForKey: _name];
  RELEASE(_name);
  TEST_RELEASE(_fileName);
  RELEASE(_color);

  [super dealloc];
}

- (id) copyWithZone: (NSZone *)zone
{
  NSImage	*copy;
  NSArray *reps = [self representations];
  NSEnumerator *enumerator = [reps objectEnumerator];
  NSImageRep *rep;

  copy = (NSImage*)NSCopyObject (self, 0, zone);

  RETAIN(_name);
  RETAIN(_fileName);
  RETAIN(_color);
  copy->_lockedView = nil;
  // FIXME: maybe we should retain if _flags.dataRetained = NO
  copy->_reps = [[NSMutableArray alloc] initWithCapacity: [_reps count]];

  //  Only copy non-cached reps.
  while ((rep = [enumerator nextObject]) != nil)
    {
      if (![rep isKindOfClass: cachedClass])
        {
	  [copy addRepresentation: rep];
	}
    }
  
  return copy;
}

- (BOOL) setName: (NSString *)aName
{
  BOOL retained = NO;
  
  if (!aName || [nameDict objectForKey: aName])
    return NO;

  if (_name && self == [nameDict objectForKey: _name])
    {
      /* We retain self in case removing from the dictionary releases
         us */
      RETAIN (self);
      retained = YES;
      [nameDict removeObjectForKey: _name];
    }
  
  ASSIGN(_name, aName);
  
  [nameDict setObject: self forKey: _name];
  if (retained)
    {
      RELEASE (self);
    }
  
  return YES;
}

- (NSString *) name
{
  return _name;
}

- (void) setSize: (NSSize)aSize
{
  // Optimized as this is called very often from NSImageCell
  if (NSEqualSizes(_size, aSize))
    return;

  _size = aSize;
  _flags.sizeWasExplicitlySet = YES;

  [self recache];
}

- (NSSize) size
{
  if (_size.width == 0) 
    {
      NSImageRep *rep = [self bestRepresentationForDevice: nil];

      if (rep)
	_size = [rep size];
      else
	_size = NSZeroSize;
    }
  return _size;
}

- (BOOL) isFlipped
{
  return _flags.flipDraw;
}

- (void) setFlipped: (BOOL)flag
{
  _flags.flipDraw = flag;
}

// Choosing Which Image Representation to Use 
- (void) setUsesEPSOnResolutionMismatch: (BOOL)flag
{
  _flags.useEPSOnResolutionMismatch = flag;
}

- (BOOL) usesEPSOnResolutionMismatch
{
  return _flags.useEPSOnResolutionMismatch;
}

- (void) setPrefersColorMatch: (BOOL)flag
{
  _flags.colorMatchPreferred = flag;
}

- (BOOL) prefersColorMatch
{
  return _flags.colorMatchPreferred;
}

- (void) setMatchesOnMultipleResolution: (BOOL)flag
{
  _flags.multipleResolutionMatching = flag;
}

- (BOOL) matchesOnMultipleResolution
{
  return _flags.multipleResolutionMatching;
}

// Determining How the Image is Stored 
- (void) setCachedSeparately: (BOOL)flag
{
  _flags.cacheSeparately = flag;
}

- (BOOL) isCachedSeparately
{
  return _flags.cacheSeparately;
}

- (void) setDataRetained: (BOOL)flag
{
  _flags.dataRetained = flag;
}

- (BOOL) isDataRetained
{
  return _flags.dataRetained;
}

- (void) setCacheDepthMatchesImageDepth: (BOOL)flag
{
  _flags.unboundedCacheDepth = flag;
}

- (BOOL) cacheDepthMatchesImageDepth
{
  return _flags.unboundedCacheDepth;
}

- (void) setCacheMode: (NSImageCacheMode)mode
{
  _cacheMode = mode;
}

- (NSImageCacheMode) cacheMode
{
  return _cacheMode;
}


// Determining How the Image is Drawn 
- (BOOL) isValid
{
  BOOL		valid = NO;
  unsigned	i, count;

  /* Go through all our representations and determine if at least one
     is a valid cache */
  // FIXME: Not sure if this is correct
  count = [_reps count];
  for (i = 0; i < count; i++) 
    {
      GSRepData	 *repd = (GSRepData*)[_reps objectAtIndex: i];

      if (repd->bg != nil || [repd->rep isKindOfClass: cachedClass] == NO)
	{
	  valid = YES;
	  break;
	}
    }
  return valid;
}

- (void) recache
{
  unsigned i;

  i = [_reps count];
  while(i--) 
    {
      GSRepData	*repd;

      repd = (GSRepData*)[_reps objectAtIndex: i];
      if (repd->original != nil)
	{
	  [_reps removeObjectAtIndex: i];
	}
    }
}

- (void) setScalesWhenResized: (BOOL)flag
{
  _flags.scalable = flag;
}

- (BOOL) scalesWhenResized
{
  return _flags.scalable;
}

- (void) setBackgroundColor: (NSColor *)aColor
{
  if (aColor == nil)
    {
      aColor = clearColor;
    }
  ASSIGN(_color, aColor);
}

- (NSColor *) backgroundColor
{
  return _color;
}


// Using the Image 
- (void) compositeToPoint: (NSPoint)aPoint 
		operation: (NSCompositingOperation)op
{
  NSRect rect;
  // Might not be computed up to now
  NSSize size = [self size];

  rect = NSMakeRect(0, 0, size.width, size.height);
  [self compositeToPoint: aPoint fromRect: rect operation: op];
}

- (void) compositeToPoint: (NSPoint)aPoint
		 fromRect: (NSRect)aRect
		operation: (NSCompositingOperation)op
{
  NSImageRep *rep = nil;

  NS_DURING
    { 
      if ([GSCurrentContext() isDrawingToScreen] == YES)
	  rep = [self _doImageCache];
      if (rep
	  &&_cacheMode != NSImageCacheNever 
	  && [rep isKindOfClass: cachedClass])
        {
	  NSRect rect;
	  float y = aPoint.y;

	  rect = [(NSCachedImageRep *)rep rect];
	  NSDebugLLog(@"NSImage", @"composite rect %@ in %@", 
		      NSStringFromRect(rect), NSStringFromRect(aRect));
	  // Move the drawing rectangle to the origin of the image rep
	  // and intersect the two rects.
	  aRect.origin.x += rect.origin.x;
	  aRect.origin.y += rect.origin.y;
	  rect = NSIntersectionRect(aRect, rect);

	  PScomposite(NSMinX(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect),
	    [[(NSCachedImageRep *)rep window] gState], aPoint.x, y, op);
	}
      else	
        {
	  NSRect rect;
          rep = [self bestRepresentationForDevice: nil];
	  rect = NSMakeRect(aPoint.x, aPoint.y, _size.width, _size.height);
	  [self drawRepresentation: rep inRect: rect];
	}
    }
  NS_HANDLER
    {
      NSLog(@"NSImage: compositeToPoint:fromRect:operation: failed due to %@: %@", 
	    [localException name], [localException reason]);
      if ([_delegate respondsToSelector: @selector(imageDidNotDraw:inRect:)])
        {
	  NSImage *image = [_delegate imageDidNotDraw: self inRect: aRect];

	  if (image != nil)
	    [image compositeToPoint: aPoint
		   fromRect: aRect 
		   operation: op];
	}
    }
  NS_ENDHANDLER
}

- (void) compositeToPoint: (NSPoint)aPoint
		operation: (NSCompositingOperation)op
		 fraction: (float)delta
{
  NSRect rect;
  NSSize size = [self size];

  rect = NSMakeRect(0, 0, size.width, size.height);
  [self compositeToPoint: aPoint fromRect: rect 
	operation: op fraction: delta];
}

- (void) compositeToPoint: (NSPoint)aPoint
		 fromRect: (NSRect)srcRect
		operation: (NSCompositingOperation)op
		 fraction: (float)delta
{
// FIXME We need another PS command for this
}

- (void) dissolveToPoint: (NSPoint)aPoint fraction: (float)aFloat
{
  NSRect rect;
  NSSize size = [self size];

  rect = NSMakeRect(0, 0, size.width, size.height);
  [self dissolveToPoint: aPoint fromRect: rect fraction: aFloat];
}

- (void) dissolveToPoint: (NSPoint)aPoint
		fromRect: (NSRect)aRect 
		fraction: (float)aFloat
{
  NSImageRep *rep = nil;

  NS_DURING
    {
      if ([GSCurrentContext() isDrawingToScreen] == YES)
	  rep = [self _doImageCache];
      if (rep
	  &&_cacheMode != NSImageCacheNever 
	  && [rep isKindOfClass: cachedClass])
        {
	  NSRect rect;
	  float y = aPoint.y;

	  rect = [(NSCachedImageRep *)rep rect];
	  // Move the drawing rectangle to the origin of the image rep
	  // and intersect the two rects.
	  aRect.origin.x += rect.origin.x;
	  aRect.origin.y += rect.origin.y;
	  rect = NSIntersectionRect(aRect, rect);
	  PSdissolve(NSMinX(rect), NSMinY(rect), NSWidth(rect), NSHeight(rect),
	    [[(NSCachedImageRep *)rep window] gState], aPoint.x, y, aFloat);
	}
      else
        {
	  NSRect rect;

	  /* FIXME: Here we are supposed to composite directly from the source
	     but how do you do that? */
          rep = [self bestRepresentationForDevice: nil];
	  rect = NSMakeRect(aPoint.x, aPoint.y, _size.width, _size.height);
	  [self drawRepresentation: rep inRect: rect];
	}
    }
  NS_HANDLER
    {
      NSLog(@"NSImage: dissolve failed due to %@: %@", 
	    [localException name], [localException reason]);
      if ([_delegate respondsToSelector: @selector(imageDidNotDraw:inRect:)])
        {
	  NSImage *image = [_delegate imageDidNotDraw: self inRect: aRect];

	  if (image != nil)
	    [image dissolveToPoint: aPoint
		   fromRect: aRect 
		   fraction: aFloat];
	}
    }
  NS_ENDHANDLER
}

- (BOOL) drawRepresentation: (NSImageRep *)imageRep inRect: (NSRect)aRect
{
  if (_color != nil)
    {
      NSRect fillrect = aRect;
      [_color set];
      if ([[NSView focusView] isFlipped])
	fillrect.origin.y -= _size.height;
      NSRectFill(fillrect);
      if ([GSCurrentContext() isDrawingToScreen] == NO)
	{
	  /* Reset alpha for image drawing. */
	  [[NSColor whiteColor] set];
	}
    }

  if (!_flags.scalable) 
    return [imageRep drawAtPoint: aRect.origin];
  return [imageRep drawInRect: aRect];
}

- (void) drawAtPoint: (NSPoint)point
	    fromRect: (NSRect)srcRect
	   operation: (NSCompositingOperation)op
	    fraction: (float)delta
{
//FIXME We need another PS command for this
}

- (void) drawInRect: (NSRect)dstRect
	   fromRect: (NSRect)srcRect
	  operation: (NSCompositingOperation)op
	   fraction: (float)delta
{
//FIXME We need another PS command for this
}

- (void) addRepresentation: (NSImageRep *)imageRep
{
  GSRepData	*repd;

  repd = [GSRepData new];
  repd->rep = RETAIN(imageRep);
  [_reps addObject: repd]; 
  RELEASE(repd);
}

- (void) addRepresentations: (NSArray *)imageRepArray
{
  unsigned	i, count;
  GSRepData	*repd;

  count = [imageRepArray count];
  for (i = 0; i < count; i++)
    {
      repd = [GSRepData new];
      repd->rep = RETAIN([imageRepArray objectAtIndex: i]);
      [_reps addObject: repd]; 
      RELEASE(repd);
    }
}

- (void) removeRepresentation: (NSImageRep *)imageRep
{
  unsigned	i;
  GSRepData	*repd;

  i = [_reps count];
  while (i-- > 0)
    {
      repd = (GSRepData*)[_reps objectAtIndex: i];
      if (repd->rep == imageRep)
	{
	  [_reps removeObjectAtIndex: i];
	}
      else if (repd->original == imageRep)
	{
	  repd->original = nil;
	}
    }
}

- (void) lockFocus
{
  [self lockFocusOnRepresentation: nil];
}

- (void) lockFocusOnRepresentation: (NSImageRep *)imageRep
{
  if (_cacheMode != NSImageCacheNever)
    {
      NSWindow	*window;
      GSRepData *repd;

      if (imageRep == nil)
	imageRep = [self bestRepresentationForDevice: nil];

      imageRep = [self _cacheForRep: imageRep];
      repd = repd_for_rep(_reps, imageRep);

      window = [(NSCachedImageRep *)imageRep window];
      _lockedView = [window contentView];
      if (_lockedView == nil)
	[NSException raise: NSImageCacheException
		     format: @"Cannot lock focus on nil rep"];
      [_lockedView lockFocus];

      /* Validate cached image */
      if (repd->bg == nil)
	{
	  repd->bg = [_color copy];
	  [_color set];
	  
	  if ([_color alphaComponent] < 1)
	    {
	      /* With a Quartz-like alpha model, alpha can't be cleared
	         with a rectfill, so we need to clear the alpha channel
		 explictly. (A compositerect with NSCompositeCopy would
		 be more efficient, but it doesn't seem like it's 
		 implemented correctly in all backends yet (as of 
		 2002-08-23). Also, this will work with both the Quartz-
		 and DPS-model.) */
	      PScompositerect(0, 0, _size.width, _size.height,
			      NSCompositeClear);
	    }
	  NSRectFill(NSMakeRect(0, 0, _size.width, _size.height));
	}
    }
}

- (void) unlockFocus
{
  if (_lockedView != nil)
    {
      [_lockedView unlockFocus];
      _lockedView = nil;
    }
}

/* Determine if the device is color or gray scale and find the reps of
   the same type
*/
- (NSMutableArray *) _bestRep: (NSArray *)reps 
	       withColorMatch: (NSDictionary*)deviceDescription
{
  int colors = 3;
  NSImageRep* rep;
  NSMutableArray *breps;
  NSEnumerator *enumerator = [reps objectEnumerator];
  NSString *colorSpace = [deviceDescription objectForKey: NSDeviceColorSpaceName];
  
  if (colorSpace != nil)
    colors = NSNumberOfColorComponents(colorSpace);
  
  breps = [NSMutableArray array];
  while ((rep = [enumerator nextObject]) != nil)
    {
      if ([rep colorSpaceName] || abs(NSNumberOfColorComponents([rep colorSpaceName]) - colors) <= 1)
        [breps addObject: rep];
    }
  
  /* If there are no matches, pass all the reps */
  if ([breps count] == 0)
    return (NSMutableArray *)reps;
  return breps;
}

/* Find reps that match the resolution of the device or return the rep
   that has the highest resolution */
- (NSMutableArray *) _bestRep: (NSArray *)reps 
	  withResolutionMatch: (NSDictionary*)deviceDescription
{
  NSImageRep* rep;
  NSMutableArray *breps;
  NSSize dres;
  NSEnumerator *enumerator = [reps objectEnumerator];
  NSValue *resolution = [deviceDescription objectForKey: NSDeviceResolution];

  if (resolution)
    dres = [resolution sizeValue];
  else
    dres = NSMakeSize(0, 0);

  breps = [NSMutableArray array];
  while ((rep = [enumerator nextObject]) != nil)
    {
      /* FIXME: Not sure about checking resolution */
      [breps addObject: rep];
    }
  
  /* If there are no matches, pass all the reps */
  if ([breps count] == 0)
    return (NSMutableArray *)reps;
  return breps;
}

/* Find reps that match the bps of the device or return the rep that
   has the highest bps */
- (NSMutableArray *) _bestRep: (NSArray *)reps 
		 withBpsMatch: (NSDictionary*)deviceDescription
{
  NSImageRep* rep, *max_rep;
  NSMutableArray *breps;
  NSEnumerator *enumerator = [reps objectEnumerator];
  int bps = [[deviceDescription objectForKey: NSDeviceBitsPerSample] intValue];
  int max_bps;

  breps = [NSMutableArray array];
  max_bps = 0;
  max_rep = nil;
  while ((rep = [enumerator nextObject]) != nil)
    {
      int rep_bps = 0;
      if ([rep respondsToSelector: @selector(bitsPerPixel)])
        rep_bps = [(NSBitmapImageRep *)rep bitsPerPixel];
      if (rep_bps > max_bps)
        {
          max_bps = rep_bps;
          max_rep = rep;
        }
      if (rep_bps == bps)
        [breps addObject: rep];
    }
  

  if ([breps count] == 0 && max_rep != nil)
    [breps addObject: max_rep];

  /* If there are no matches, pass all the reps */
  if ([breps count] == 0)
    return (NSMutableArray *)reps;
  return breps;
}

- (NSMutableArray *) _representationsWithCachedImages: (BOOL)flag
{
  unsigned	count;

  if (_flags.syncLoad)
    {
      /* Make sure any images that were added with _useFromFile: are loaded
	 in and added to the representation list. */
      [self _loadFromFile: _fileName];
      _flags.syncLoad = NO;
    }

  count = [_reps count];
  if (count == 0)
    {
      return [NSArray array];
    }
  else
    {
      id	repList[count];
      unsigned	i, j;

      [_reps getObjects: repList];
      j = 0;
      for (i = 0; i < count; i++) 
	{
          if (flag || ((GSRepData*)repList[i])->original == nil)
            {
              repList[j] = ((GSRepData*)repList[i])->rep;
              j++;
            }
	}
      return [NSArray arrayWithObjects: repList count: j];
    }
}

- (NSImageRep*) bestRepresentationForDevice: (NSDictionary*)deviceDescription
{
  NSMutableArray *reps = [self _representationsWithCachedImages: NO];
  
  if (deviceDescription == nil)
    {
      if ([GSCurrentContext() isDrawingToScreen] == YES)
        {
          int screen = [[[GSCurrentServer() attributes] 
			  objectForKey: GSScreenNumber] intValue];
          deviceDescription = [[[NSScreen screens] objectAtIndex: screen] 
				deviceDescription];
        }
      else if ([NSPrintOperation currentOperation])
        {
          /* FIXME: We could try to use the current printer, 
	     but there are many cases where might
             not be printing (EPS, PDF, etc) to a specific device */
        }
    }

  if (_flags.colorMatchPreferred == YES)
    {
      reps = [self _bestRep: reps withColorMatch: deviceDescription];
      reps = [self _bestRep: reps withResolutionMatch: deviceDescription];
    }
  else
    {
      reps = [self _bestRep: reps withResolutionMatch: deviceDescription];
      reps = [self _bestRep: reps withColorMatch: deviceDescription];
    }
  reps = [self _bestRep: reps withBpsMatch: deviceDescription];
  /* Pick an arbitrary representation if there is more than one */
  return [reps lastObject];
}

- (NSArray *) representations
{
  return [self _representationsWithCachedImages: YES];
}

- (void) setDelegate: anObject
{
  _delegate = anObject;
}

- (id) delegate
{
  return _delegate;
}

// Producing TIFF Data for the Image 
- (NSData *) TIFFRepresentation
{
  return [bitmapClass TIFFRepresentationOfImageRepsInArray: [self representations]];
}

- (NSData *) TIFFRepresentationUsingCompression: (NSTIFFCompression)comp
	factor: (float)aFloat
{
  return [bitmapClass TIFFRepresentationOfImageRepsInArray: [self representations]
		      usingCompression: comp
		      factor: aFloat];
}

// NSCoding
- (void) encodeWithCoder: (NSCoder*)coder
{
  BOOL	flag;

  flag = _flags.archiveByName;
  [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];
  if (flag == YES)
    {
      /*
       * System image - just encode the name.
       */
      [coder encodeValueOfObjCType: @encode(id) at: &_name];
    }
  else
    {
      NSMutableArray	*a;
      NSEnumerator	*e;
      NSImageRep	*r;

      /*
       * Normal image - encode the ivars
       */
      [coder encodeValueOfObjCType: @encode(NSSize) at: &_size];
      [coder encodeValueOfObjCType: @encode(id) at: &_color];
      flag = _flags.scalable;
      [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];
      flag = _flags.dataRetained;
      [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];
      flag = _flags.flipDraw;
      [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];
      flag = _flags.sizeWasExplicitlySet;
      [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];
      flag = _flags.useEPSOnResolutionMismatch;
      [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];
      flag = _flags.colorMatchPreferred;
      [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];
      flag = _flags.multipleResolutionMatching;
      [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];
      flag = _flags.cacheSeparately;
      [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];
      flag = _flags.unboundedCacheDepth;
      [coder encodeValueOfObjCType: @encode(BOOL) at: &flag];

      // FIXME: The documentation says to archive only the file name,
      // if not data retained!
      /*
       * Now encode an array of all the image reps (excluding cache)
       */
      a = [NSMutableArray arrayWithCapacity: 2];
      e = [[self representations] objectEnumerator];
      while ((r = [e nextObject]) != nil)
	{
	  if ([r isKindOfClass: cachedClass] == NO)
	    {
	      [a addObject: r];
	    }
	}
      [coder encodeValueOfObjCType: @encode(id) at: &a];
    }
}

- (id) initWithCoder: (NSCoder*)coder
{
  BOOL	flag;

  _reps = [[NSMutableArray alloc] initWithCapacity: 2];
  [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
  if (flag == YES)
    {
      NSString	*theName = [coder decodeObject];

      RELEASE(self);
      self = RETAIN([NSImage imageNamed: theName]);
    }
  else
    {
      NSArray	*a;

      [coder decodeValueOfObjCType: @encode(NSSize) at: &_size];
      [coder decodeValueOfObjCType: @encode(id) at: &_color];
      [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
      _flags.scalable = flag;
      [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
      _flags.dataRetained = flag;
      [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
      _flags.flipDraw = flag;
      [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
      _flags.sizeWasExplicitlySet = flag;
      [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
      _flags.useEPSOnResolutionMismatch = flag;
      [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
      _flags.colorMatchPreferred = flag;
      [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
      _flags.multipleResolutionMatching = flag;
      [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
      _flags.cacheSeparately = flag;
      [coder decodeValueOfObjCType: @encode(BOOL) at: &flag];
      _flags.unboundedCacheDepth = flag;

      /*
       * get the image reps and add them.
       */
      a = [coder decodeObject];
      [self addRepresentations: a];
    }
  return self;
}

- (id) awakeAfterUsingCoder: (NSCoder*)aDecoder
{
  if (_name && [nameDict objectForKey: _name]) 
    {
      return [nameDict objectForKey: _name];
    }
    
  return self;
}

+ (BOOL) canInitWithPasteboard: (NSPasteboard *)pasteboard
{
  int i, count;
  NSArray* array = [NSImageRep registeredImageRepClasses];

  count = [array count];
  for (i = 0; i < count; i++)
    if ([[array objectAtIndex: i] canInitWithPasteboard: pasteboard])
      return YES;
  
  return NO;
}

+ (NSArray *) imageUnfilteredFileTypes
{
  return iterate_reps_for_types([NSImageRep registeredImageRepClasses],
				@selector(imageUnfilteredFileTypes));
}

+ (NSArray *) imageFileTypes
{
  return iterate_reps_for_types([NSImageRep registeredImageRepClasses],
				@selector(imageFileTypes));
}

+ (NSArray *) imageUnfilteredPasteboardTypes
{
  return iterate_reps_for_types([NSImageRep registeredImageRepClasses],
				@selector(imageUnfilteredPasteboardTypes));
}

+ (NSArray *) imagePasteboardTypes
{
  return iterate_reps_for_types([NSImageRep registeredImageRepClasses],
				@selector(imagePasteboardTypes));
}

@end

/* For every image rep, call the specified method to obtain an
   array of objects.  Add these together, with duplicates
   weeded out.  Used by imageUnfilteredPasteboardTypes,
   imageUnfilteredFileTypes, etc. */
static NSArray *
iterate_reps_for_types(NSArray* imageReps, SEL method)
{
  NSImageRep	*rep;
  NSEnumerator	*e;
  NSMutableArray	*types;

  types = [NSMutableArray arrayWithCapacity: 2];

  // Iterate through all the image reps
  e = [imageReps objectEnumerator];
  rep = [e nextObject];
  while (rep)
    {
      id e1;
      id obj;
      NSArray* pb_list;

      // Have the image rep perform the operation
      pb_list = [rep performSelector: method];

      // Iterate through the returned array
      // and add elements to types list, duplicates weeded.
      e1 = [pb_list objectEnumerator];
      obj = [e1 nextObject];
      while (obj)
	{
	  if ([types indexOfObject: obj] == NSNotFound)
	    [types addObject: obj];
	  obj = [e1 nextObject];
	}

      rep = [e nextObject];
    }
    
  return (NSArray *)types;
}

@implementation NSImage (Private)
    
- (BOOL)_loadFromData: (NSData *)data
{
  BOOL ok;
  Class rep;

  ok = NO;
  rep = [NSImageRep imageRepClassForData: data];
  if (rep && [rep respondsToSelector: @selector(imageRepsWithData:)])
    {
      NSArray* array;
      array = [rep imageRepsWithData: data];
      if (array)
	ok = YES;
      [self addRepresentations: array];
    }
  else if (rep)
    {
      NSImageRep* image;
      image = [rep imageRepWithData: data];
      if (image)
	ok = YES;
      [self addRepresentation: image];
    }
  return ok;
}

- (BOOL) _loadFromFile: (NSString *)fileName
{
  NSArray* array;

  array = [NSImageRep imageRepsWithContentsOfFile: fileName];
  if (array)
    [self addRepresentations: array];

  return (array) ? YES : NO;
}

- (BOOL) _useFromFile: (NSString *)fileName
{
  NSArray	*array;
  NSString	*ext;
  NSFileManager *manager = [NSFileManager defaultManager];

  if ([manager fileExistsAtPath: fileName] == NO)
    {
      return NO;
    }

  ext = [fileName pathExtension];
  if (!ext)
    return NO;
  array = [isa imageFileTypes];
  if ([array indexOfObject: ext] == NSNotFound)
    return NO;

  ASSIGN(_fileName, fileName);
  _flags.syncLoad = YES;
  return YES;
}

// Cache the bestRepresentation.  If the bestRepresentation is not itself
// a cache and no cache exists, create one and draw the representation in it
// If a cache exists, but is not valid, redraw the cache from the original
// image (if there is one).
- (NSImageRep *)_doImageCache
{
  NSImageRep *rep = [self bestRepresentationForDevice: nil];

  if (_cacheMode != NSImageCacheNever)
    {
      GSRepData *repd;

      rep =  [self _cacheForRep: rep];
      repd = repd_for_rep(_reps, rep);

      NSDebugLLog(@"NSImage", @"Cached image rep is %d", (int)rep);
      /*
       * if the cache is not valid, it's background color will not exist
       * and we must draw the background then render from the original
       * image rep into the cache.
       */
      if (repd->bg == nil) 
	{
	  [self lockFocusOnRepresentation: rep];
	  [self drawRepresentation: repd->original 
		inRect: NSMakeRect(0, 0, _size.width, _size.height)];
	  [self unlockFocus];

	  if (_color != nil && [_color alphaComponent] != 0.0)
	    {
	      repd->bg = [_color copy];
	    }
	  else
	    {
	      repd->bg = [clearColor copy];
	    }

	  if ([repd->bg alphaComponent] == 1.0)
	    {
	      [rep setOpaque: YES];
	    }
	  else
	    {
	      [rep setOpaque: [repd->original isOpaque]];
	    }
	  NSDebugLLog(@"NSImage", @"Rendered rep %d on background %@",
	    (int)rep, repd->bg);
	}
    }
  
  return rep;
}

- (NSImageRep*) _cacheForRep: (NSImageRep*)rep
{
  /*
   * If this is not a cached image rep - create a cache to be used to
   * render the image rep into, and switch to the cached rep.
   */
  if ([rep isKindOfClass: cachedClass] == NO)
    {
      NSImageRep	*cacheRep = nil;
      unsigned		count = [_reps count];

      if (count > 0)
	{
	  GSRepData	*invalidCache = nil;
	  GSRepData	*partialCache = nil;
	  GSRepData	*validCache = nil;
	  GSRepData	*reps[count];
          unsigned	partialCount = 0;
	  unsigned	i;
	  BOOL		opaque = [rep isOpaque];

	  [_reps getObjects: reps];

	  /*
	   * Search the cached image reps for any whose original is our
	   * 'best' image rep.  See if we can notice any invalidated
	   * cache as we go - if we don't find a valid cache, we want to
	   * re-use an invalidated one rather than creating a new one.
	   * NB. If the image rep is opaque, then any cached rep is valid
	   * irrespective of the background color it was drawn with.
	   */
	  for (i = 0; i < count; i++)
	    {
	      GSRepData	*repd = reps[i];

	      if (repd->original == rep && repd->rep != rep)
		{
		  if (repd->bg == nil)
		    {
NSDebugLLog(@"NSImage", @"Invalid %@ ... %@ %d", repd->bg, _color, repd->rep);
		      invalidCache = repd;
		    }
		  else if (opaque == YES || [repd->bg isEqual: _color] == YES)
		    {
NSDebugLLog(@"NSImage", @"Exact %@ ... %@ %d", repd->bg, _color, repd->rep);
		      validCache = repd;
		      break;
		    }
		  else
		    {
NSDebugLLog(@"NSImage", @"Partial %@ ... %@ %d", repd->bg, _color, repd->rep);
		      partialCache = repd;
		      partialCount++;
		    }
		}
	    }

	  if (validCache != nil)
	    {
	      if (NSImageForceCaching == NO && [rep isOpaque] == NO)
		{
		  /*
		   * If the image rep is not opaque and we are drawing
		   * without an opaque background then the cache can't
		   * really be valid 'cos we might be drawing transparency
		   * on top of anything.  So we invalidate the cache by
		   * removing the background color information.
		   */
		  if ([validCache->bg alphaComponent] != 1.0)
		    {
		      DESTROY(validCache->bg);
		    }
		}
	      cacheRep = validCache->rep;
	    }
	  else if (partialCache != nil && partialCount > 2)
	    {
	      /*
	       * Only re-use partially correct caches if there are already
	       * a few partial matches - otherwise we fall default to
	       * creating a new cache.
	       */
	      if (NSImageForceCaching == NO && [rep isOpaque] == NO)
		{
		  if (invalidCache != nil)
		    {
		      /*
		       * If there is an unused cache - use it rather than
		       * re-using this one, since we might get a request
		       * to draw with this color again.
		       */
		      partialCache = invalidCache;
		    }
		  else
		    {
		      DESTROY(partialCache->bg);
		    }
		}
	      cacheRep = partialCache->rep;
	    }
	  else if (invalidCache != nil)
	    {
	      cacheRep = invalidCache->rep;
	    }
	}
      if (cacheRep == nil)
	{
	  NSScreen	*cur = [NSScreen mainScreen];
	  NSSize	imageSize;
	  GSRepData	*repd;

	  imageSize = [self size];
	  if (imageSize.width == 0 || imageSize.height == 0)
	    return nil;

	  cacheRep = [[cachedClass alloc] initWithSize: _size
						depth: [cur depth]
					     separate: NO
						alpha: NO];
	  [self addRepresentation: cacheRep];
	  RELEASE(cacheRep);		/* Retained in _reps array.	*/
          repd = repd_for_rep(_reps, cacheRep);
          repd->original = rep;
	}
      return cacheRep;
    }
  else
    {
      return rep;
    }
}

@end
