/*
    NXImage - class to load an manipulate images

    Copyright (C) 1993, Adam Fedor. 

    NXImage.m,v 1.20 1995/06/26 23:02:42 fedor Exp
    
    FIXME:  
        [1] findImageNamed: might do weird things if the name has '.''s in
    	it (excluding the extension).
	[2] Should there be a place to look for system bitmaps? 
	(findImageNamed:).
	[3] bestRepresentation is not complete
*/

#include <string.h>
#include "NXImage.h"
#include "NXBitmapImageRep.h"
#include "NXCachedImageRep.h"
#include "View.h"
#include "Window.h"
#include "dpsclient/psops.h"
#include <sys/param.h>
#include <objc/HashTable.h>
#include <objc/List.h>
#include <objc/Storage.h>
#include "NXBundle.h"
#include "stdmacros.h"
#include <objc/hashtable.h>

typedef struct _rep_data_t {
    char	*fileName;
    id		rep;
    id		cache;
    id		original;
    BOOL	validCache;
} rep_data_t;

/* Use this typedef for newer versions of the libobjects library */
#if 0
#define NXHashState GNUHashState
#endif

#ifndef index
#define index strchr
#define rindex strrchr
#endif

/* Class variables and functions for class methods */
static HashTable *nameHash;
static List	 *imageReps = NULL;
static HashTable *fileHash = NULL;
static char	 **fileTypes;
static BOOL	 syncFile;
static HashTable *pboardHash = NULL;
static NXAtom	 *pboardTypes;
static BOOL	 syncPboard;

void **
iterate_reps_for_types(List *imageReps, SEL method, HashTable *table);

/* Strip the extension from a name */
static char *
baseName(const char *name, char *buf)
{
    char *s;
    
    buf = NXCopyStringBuffer(name);
    s = rindex(name, '.');
    if (s > rindex(name, '/'))
    	*s = '\0';
    return buf;
} 

/* Get the extension from a name */
static const char *
extension(const char *name)
{
    char *s;
    
    s = rindex(name, '.');
    if (s > rindex(name, '/'))
    	return s+1;
    else
    	return NULL;
} 

/* Find the rep_data_t holding a representation */
rep_data_t *repd_for_rep(Storage *_reps, NXImageRep *rep)
{
    int i, count;
    rep_data_t *repd;

    count = [_reps count];
    for (i = 0; i < count; i++) {
    	repd = (rep_data_t *)[_reps elementAt:i];
	if (repd->rep == rep)
	    return repd;
    }
    return NULL;
}

extern const char *NXImageInstanceName(void);

@interface NXImage(ToolKit)
- _displayComposite:(int)op fromRect:(const NXRect *)rect 
	toPoint:(const NXPoint *)point;
@end

@implementation NXImage

+ findImageNamed:(const char *)aName
{
    // If there is no image with that name, search in the main bundle
    if (!nameHash || ![nameHash valueForKey:aName]) {
	const char *ext;
        char 	path[MAXPATHLEN+1];
	id   	main;
	main = [NXBundle mainBundle];
	ext  = extension(aName);
	path[0] = '\0';
	if (ext)
	    [main getPath:path forResource:aName ofType:ext];
	else {
	    // Look for name with any registered extension.
	    NXHashState state;
    	    char *key, *value;
	    
    	    [self imageFileTypes];
    	    state = [fileHash initState];
    	    while ([fileHash nextState: &state key:(const void **)&key 
				 value: (void **)&value]) {
	    	[main getPath:path forResource:aName ofType:key];
		if (path[0] != '\0')
		    break;
	    }
	}
	if (path[0] != '\0') {
	     char buf[MAXPATHLEN+1];
	     id image = [[NXImage alloc] initFromFile:path];
	     baseName(aName, buf);
	     if (image)
	         [image setName:buf];
	     return image;
	}
    }
    
    return [nameHash valueForKey:aName];
}

- init
{
    [self initSize:NULL];
    return self;
}

// Designated initializer for nearly everything.
- initSize:(const NXSize *)aSize
{
    const char *instance_name;
    [super init];
    _reps = [[Storage alloc] initCount:0 
		      elementSize:sizeof(rep_data_t)
		      description:@encode(rep_data_t)];
    if (aSize) {
        _size = *aSize;
    	_flags.sizeWasExplicitlySet = YES;
    }
    _flags.colorMatchPreferred = YES;
    _flags.multipleResolutionMatching = YES;

    // Force linker to link in the category
    instance_name = NXImageInstanceName();
    return self;
}

- initFromFile:(const char *)fileName
{
    [self init];
    return ([self useFromFile:fileName]) ? self : nil;
}

- initFromStream:(NXStream *)stream
{
    [self init];
    return ([self loadFromStream:stream]) ? self : nil;
}

- initFromImage:(NXImage *)image rect:(const NXRect *)rect
{
    return nil;
}

- getImage:(NXImage **)image rect:(NXRect *)rect
{
    return nil;
}

- setSize:(const NXSize *)aSize
{
    _size = *aSize;
    _flags.sizeWasExplicitlySet = YES;
    return self;
}

- getSize:(NXSize *)aSize
{
    if (_size.width == 0) {
    	NXImageRep *rep = [self bestRepresentation];
    	[rep getSize:&_size];
    }
    
    *aSize = _size;
    return self;
}

- _setDontFreeName:(BOOL)flag
{
    _flags.dontFreeName = flag;
    return self;
}

- free
{
    [self representationList];
    [_repList freeObjects];
    [_repList free];
    [_reps free];
    if (name && !_flags.dontFreeName) {
        [nameHash removeKey:name];
        NX_FREE(name);
    }
    return [super free];
}

- copy
{
    int i, count;
    id  copy = [super copy];
    
    [self representationList];
    count = [_repList count];
    for (i = 0; i < count; i++) {
	id rep;
	rep = [[_repList objectAt:i] copy];
	[copy useRepresentation:rep];
    }
    [copy _setDontFreeName:YES];
    
    return copy;
}

- (BOOL)setName:(const char *)string
{
    if (!nameHash)
        nameHash = [[HashTable alloc] initKeyDesc:"*" valueDesc:"@"];

    if (!string || [nameHash isKey:string])
        return NO;
    name = NXCopyStringBuffer(string);
    [nameHash insertKey:name value:self];
    return YES;
}

- (const char *)name
{
    return name;
}

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

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

- setScalable:(BOOL)flag
{
    _flags.scalable = flag;
    return self;
}

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

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

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

- setUnique:(BOOL)flag
{
    _flags.uniqueWindow = flag;
    _flags.uniqueWasExplicitlySet = YES;
    return self;
}

- (BOOL)isUnique
{
    return _flags.uniqueWindow;
}

- setCacheDepthBounded:(BOOL)flag
{
    _flags.unboundedCacheDepth = ~flag;
    return self;
}

- (BOOL)isCacheDepthBounded
{
    return ~_flags.unboundedCacheDepth;
}

- setBackgroundColor:(NXColor)aColor
{
    _color = aColor;
    return self;
}

- (NXColor)backgroundColor
{
    return _color;
}

- setEPSUsedOnResolutionMismatch:(BOOL)flag
{
    _flags.useEPSOnResolutionMismatch = flag;
    return self;
}

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

- setColorMatchPreferred:(BOOL)flag
{
    _flags.colorMatchPreferred = flag;
    return self;
}

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

- setMatchedOnMultipleResolution:(BOOL)flag
{
    _flags.multipleResolutionMatching = flag;
    return self;
}

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

/* Make sure any images that were added with useFromFile: are loaded
   in and added to the representation list
*/
- _loadImageFilenames
{
    unsigned i, count;
    rep_data_t	*repd;

    _syncLoad = NO;
    count = [_reps count];
    for (i = 0; i < count; i++) {
    	repd = (rep_data_t *)[_reps elementAt:i];
    	if (repd->fileName)
    	    [self loadFromFile:repd->fileName];
    }
    // Now get rid of them since they are already loaded
    count = [_reps count];
    while (count--) {
    	repd = (rep_data_t *)[_reps elementAt:count];
    	if (repd->fileName) {
	    NX_FREE(repd->fileName);
	    [_reps removeElementAt:count];
	}
    }
    return self;
}
    
// 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).
- _doImageCache
{
    NXImageRep *rep;
    rep_data_t *repd;
    repd = repd_for_rep(_reps, [self bestRepresentation]);
    rep = repd->rep;
    if (repd->cache)
	rep = repd->cache;
    if (![rep isKindOf:[NXCachedImageRep class]]) {
	if ([self lockFocus]) {
	    rep_data_t *cached;
	    NXRect bounds;
	    PSsetgray(NX_WHITE);
	    [_lockedView getBounds:&bounds];
	    NXRectFill(&bounds);
	    [self drawRepresentation:rep inRect:NULL];
	    [self unlockFocus];
	    cached = [_reps elementAt:[_reps count] - 1];
	    cached->original = rep;
	    cached->validCache = YES;
	}
    } else if (!repd->validCache) {
	if ([self lockFocusOn:rep]) {
	    NXRect bounds;
	    PSsetgray(NX_WHITE);
	    [_lockedView getBounds:&bounds];
	    NXRectFill(&bounds);
	    repd = repd_for_rep(_reps, rep);
	    [self drawRepresentation:repd->original inRect:NULL];
	    [self unlockFocus];
	    repd->validCache = YES;
	}
    }

    return self;
}

- dissolve:(float)delta toPoint:(const NXPoint *)point
{
    return nil;
}

- dissolve:(float)delta	fromRect:(const NXRect *)rect toPoint:(const NXPoint *)point
{
    return nil;
}

- composite:(int)op toPoint:(const NXPoint *)point
{
    NXRect rect;
    NXSetRect(&rect, 0, 0, _size.width, _size.height);
    return [self composite:op fromRect:&rect toPoint:point];
}

- composite:(int)op fromRect:(const NXRect *)rect toPoint:(const NXPoint *)point
{
    [self _doImageCache];
    return [self _displayComposite:op fromRect:rect toPoint:point];
}

- (BOOL)drawRepresentation:(NXImageRep *)imageRep inRect:(const NXRect *)rect
{
    NXPoint origin;
    if (rect)
	origin = rect->origin;
    else
	origin.x = origin.y = 0;
    if (!_flags.scalable) 
	return [imageRep drawAt:&origin];
    return [imageRep drawIn:rect];
}

- recache
{
    int i, count;

    count = [_reps count];
    for (i = 0; i < count; i++) {
	rep_data_t *repd;
    	repd = (rep_data_t *)[_reps elementAt:i];
	repd->validCache = NO;
    }
    return self;
}

- writeTIFF:(NXStream *)stream
{
    return [self writeTIFF:stream allRepresentations:NO];
}

- writeTIFF:(NXStream *)stream allRepresentations:(BOOL)flag
{
    return [self writeTIFF:stream allRepresentations:flag
    	  usingCompression:0
		 andFactor:0];
}

- writeTIFF:(NXStream *)stream allRepresentations:(BOOL)flag usingCompression:(int)compression andFactor:(float)aFloat
{
    return nil;
}

- write:(NXTypedStream *)stream
{
    return self;
}

- read:(NXTypedStream *)stream
{
    return self;
}

- finishUnarchiving
{
    if (name && [nameHash valueForKey:name]) {
    	[self free];
	return [nameHash valueForKey:name];
    }
    
    return nil;
}

- _appendImageRepList:imageList
{
    int i, count;
    count = [imageList count];
    for (i=0; i<count; i++)
	[self useRepresentation:[imageList objectAt:i]];
    return self;
}

- (BOOL)loadFromStream:(NXStream *)stream
{
    int 	count;
    BOOL	ok;
    Class	rep;
    List 	*imageList = nil;
    
    count = [imageReps count];
    while (count--) {
        rep = (Class)[imageReps objectAt:count];
	if ([rep canLoadFromStream:stream]) {
	    if ([rep respondsTo:@selector(newListFromStream:)])
	        imageList = [rep perform:@selector(newListFromStream:)
				    with:(void *)stream];
	    else {
	        id image;
		image = [[rep alloc] initFromStream:stream];
		if (image) {
	            imageList = [[List alloc] init];
		    [imageList addObject:image];
		}
	    }
	    if (!imageList)
	        return NO;
	    [self _appendImageRepList:imageList];
	    break;
	}
    }
    
    ok = (imageList) ? YES : NO;
    [imageList free];
    return ok;
}

- (BOOL)loadFromFile:(const char *)fileName
{
    int 	count;
    BOOL	ok;
    Class	rep;
    List 	*imageList = nil;

    [[self class] imageFileTypes];
    if (!extension(fileName) || ![fileHash valueForKey:extension(fileName)])
        return NO;
    
    /* Is this kosher?  We just call newListFromFile for each imageRep
       without checking canLoadFromStream or anything, and just assume
       we'll get nil back if the imageRep can't handle it.  FIXME.
    */
    count = [imageReps count];
    while (count-- && !imageList) {
        rep = (Class)[imageReps objectAt:count];
	if ([rep respondsTo:@selector(newListFromFile:)])
	    imageList = [rep perform:@selector(newListFromFile:)
				    with:(void *)fileName];
	else {
	    id image;
	    image = [[rep alloc] initFromFile:fileName];
	    if (image) {
	        imageList = [[List alloc] init];
	        [imageList addObject:image];
	    }
	}
    }

    if (!imageList)
	return NO;
    
    [self _appendImageRepList:imageList];
    ok = (imageList) ? YES : NO;
    [imageList free];
    return ok;
}

- (BOOL)useFromFile:(const char *)fileName
{
    rep_data_t	repd;
			     
    [[self class] imageFileTypes];
    if (![fileHash valueForKey:extension(fileName)])
        return NO;
    repd.fileName = NXCopyStringBuffer(fileName);
    [_reps addElement:&repd];
    _syncLoad = YES;
    return YES;
}

- (BOOL)useDrawMethod:(SEL)drawMethod inObject:anObject
{
    return NO;
}

- (BOOL)useRepresentation:(NXImageRep *)imageRepresentation
{
    rep_data_t repd;
    if (!imageRepresentation)
    	return NO;

    if (_syncLoad)
	[self _loadImageFilenames];
    repd.fileName = NULL;
    repd.rep = imageRepresentation;
    repd.cache = NULL;
    repd.original = NULL;
    repd.validCache = NO;
    [_reps addElement:&repd];
    return YES;
}

- (BOOL)useCacheWithDepth:(int)depth
{
    NXRect	rect;
    NXSize	size;
    NXCachedImageRep *rep;

    [self getSize:&size];
    if (!size.width || !size.height)
	return NO;

    NXSetRect(&rect, 0, 0, size.width, size.height);
    // FIXME: Need to create a window with the proper depth
    rep = [[NXCachedImageRep alloc] initFromWindow:nil rect:&rect];
    [self useRepresentation:rep];
    return (rep) ? YES : NO;
}

- removeRepresentation:(NXImageRep *)imageRepresentation
{
    int i, count;
    rep_data_t *repd;
    count = [_reps count];
    for (i = 0; i < count; i++) {
    	repd = (rep_data_t *)[_reps elementAt:i];
	if (repd->rep == imageRepresentation)
		[_reps removeElementAt:i];
    }
    return self;
}

- (BOOL)lockFocus
{
    NXImageRep *rep;

    if (!(rep = [self bestRepresentation])) {
	[self useCacheWithDepth:NX_DefaultDepth];
	rep = [self lastRepresentation];
	if (!rep)
	    return NO;
    }
    return [self lockFocusOn:rep];
}

- (BOOL)lockFocusOn:(NXImageRep *)imageRepresentation
{
    Window *window;
    if (![imageRepresentation isKindOf:[NXCachedImageRep class]]) {
	rep_data_t *repd, *cached;
	int depth;
	if (_flags.unboundedCacheDepth)
	    depth = [imageRepresentation bitsPerSample]
		* [imageRepresentation numColors];
	else
	    depth = NX_DefaultDepth;
	if (![self useCacheWithDepth:depth]) return NO;
	repd = repd_for_rep(_reps, imageRepresentation);
	cached = repd_for_rep(_reps, [self lastRepresentation]);
	repd->cache = cached->rep;
	cached->original = repd->rep;
	imageRepresentation = cached->rep;
    }
    [(NXCachedImageRep *)imageRepresentation getWindow:&window andRect:NULL];
    _lockedView = [window contentView];
    [_lockedView lockFocus];
    return YES;
}

- unlockFocus
{
    if (_lockedView)
	[_lockedView unlockFocus];
    _lockedView = nil;
    return self;
}

- (NXImageRep *)lastRepresentation
{
    // Reconstruct the repList if it has changed
    [self representationList];
    return [_repList lastObject];
}

- (NXImageRep *)bestRepresentation
{
    NXImageRep *rep;
    rep_data_t *repd;

    // Make sure we have the images loaded in
    if (_syncLoad)
	[self _loadImageFilenames];

    if ([_reps count] == 0)
	return nil;
    
    // What's the best representation? FIXME
    repd = (rep_data_t *)[_reps elementAt:[_reps count]-1];
    if (repd->cache)
	rep = repd->cache;
    else
	rep = repd->rep;

    return rep;
}

- (List *)representationList
{
    int i, count;
    if (!_repList)
    	_repList = [[List alloc] init];
    if (_syncLoad)
	[self _loadImageFilenames];
    count = [_reps count];
    [_repList empty];
    for (i = 0; i < count; i++) {
	rep_data_t *repd;
    	repd = (rep_data_t *)[_reps elementAt:i];
	[_repList addObject:repd->rep];
    }
    return _repList;
}

- setDelegate:(id)anObject
{
    delegate = anObject;
    return self;
}

- delegate
{
    return delegate;
}

+ initialize
{
    imageReps = [[List alloc] init];
    [imageReps addObject:[NXBitmapImageRep class]];
    syncFile = YES;
    syncPboard = YES;
    return self;
}

+ (void)registerImageRep:imageRepClass
{
    [imageReps addObject:imageRepClass];
    syncFile = YES;
    syncPboard = YES;
}

+ (void)unregisterImageRep:imageRepClass
{
    [imageReps removeObject:imageRepClass];
    syncFile = YES;
    syncPboard = YES;
}

+ (Class)imageRepForFileType:(const char *)type
{
    [self imageFileTypes];
    return (Class)[fileHash valueForKey:type];
}

+ (Class)imageRepForPasteboardType:(NXAtom)type
{
    [self imagePasteboardTypes];
    return (Class)[pboardHash valueForKey:type];
}

+ (Class)imageRepForStream:(NXStream *)stream
{
    int count;
    Class rep;
    
    count = [imageReps count];
    while (count--) {
        rep = [imageReps objectAt:count];
	if ([rep canLoadFromStream:stream])
	    return rep;
    }
    return Nil;
}

+ (const char *const *)imageUnfilteredFileTypes
{
    if (!fileHash)
    	fileHash = [[HashTable alloc] initKeyDesc:"%" valueDesc:"@"];
	
    if (syncFile) {
    	NX_FREE(fileTypes);
    	fileTypes = (char **)iterate_reps_for_types(imageReps, 
    		@selector(imageUnfilteredFileTypes), fileHash);
	syncFile = NO;
    }
    
    return (const char *const *)fileTypes;
}

+ (const char *const *)imageFileTypes
{
    return [self imageUnfilteredFileTypes];
}

+ (const NXAtom *)imageUnfilteredPasteboardTypes
{
    if (!pboardHash)
    	pboardHash = [[HashTable alloc] initKeyDesc:"%" valueDesc:"@"];
	
    if (syncPboard) {
    	NX_FREE(pboardTypes);
    	pboardTypes = (NXAtom *)iterate_reps_for_types(imageReps, 
    		@selector(imageUnfilteredPasteboardTypes), pboardHash);
	syncPboard = NO;
    }
    
    return (const NXAtom *)pboardTypes;
}

+ (const NXAtom *)imagePasteboardTypes
{
    return [self imageUnfilteredPasteboardTypes];
}

@end


/* For every image rep, call the specified method to obtain a list of
   (void) pointers.  Add these to a hash table and return the list of
   all the pointers, with duplicates weeded out.  Used by 
   imageUnfilteredPasteboardTypes and imageUnfilteredFileTypes.
*/
void **
iterate_reps_for_types(List *imageReps, SEL method, HashTable *table)
{
    int count;
    NXHashState state;
    void **types;
    void *key, *value;
    
    count = [imageReps count];
    while (count--) {
    	id rep;
    	const void *const *pb_list;
	rep = [imageReps objectAt:count];
	pb_list = (const void *const *)[rep perform: method];
	while (*pb_list) {
	    if (![table isKey:*pb_list])
	    	[table insertKey:*pb_list value:rep];
	    pb_list++;
	}
    }
    
    count = [table count];
    NX_MALLOC(types, void*, count+1);
    state = [table initState];
    count = 0;
    while ([table nextState: &state key: (const void **)&key 
		      value: (void **)&value])
    	types[count++] = key;
    types[count] = NULL;
    
    return types;
}
