/*
    NXBitmapImageRep - image representation for bitmapped images

    Copyright (C) 1993, Adam Fedor.
*/

#include <stdlib.h>
#include <math.h>
#include "stdmacros.h"
#include "NXBitmapImageRep.h"
#include "appkit/tiff.h"
#include <objc/hashtable.h>

#define DATA_OWNER      1
#define DATA_NOTOWNER   2

/* Maximum number of planes */
#define PLANES		5

extern const char *NXBitmapImageRepInstanceName(void);

@interface NXBitmapImageRep(ToolKit)
- (BOOL)_displayDrawIn:(const NXRect *)rect;
- (BOOL)_displayDraw;
@end

@implementation NXBitmapImageRep 

/* Used by newListFromFile: and newListFromStream: to tell the imageRep
   which image it is supposed to be using.
*/
- _setImageNumber:(int)number
{
    _imageNumber = number;
    return self;
}

/* Used by newListFromFile: to tell the imageRep
   which image file it is supposed to be using.
*/
- _setFileName:(const char *)fileName
{
    _fileName = NXCopyStringBuffer(fileName);
    return self;
}

- _dataFromImage:(TIFF *)image
{
    unsigned char *newdata;

    [self data];
    newdata = NXReadTiff(_imageNumber, image, NULL, *_data);
    if (!newdata)
	return nil;
    *_data = newdata;
    if (_moreRepFlags.isPlanar) {
    	int i;
    	for (i=1; i < _repFlags.numColors; i++) 
	    _data[i] = _data[0] + i*_bytesPerRow * _pixelsHigh;
    }

    return self;
}

/* Given a TIFF image (from the libtiff library), load the image information
   into our data structure.  Reads the default or current image from the 
   stream.  This is done by passing a -1 as the imageNumber to NXGetTiffInfo.  
*/
- _initFromImage:(TIFF *)image
{
    id		   ok;
#ifdef HAVE_LIBTIFF
    NXColorSpace   colorSpace;
#endif
    NXTiffInfo	  *info;

    info = NXGetTiffInfo(-1, image);
    if (!info) {
	[self free];
	return nil;
    }

    /* 8-bit RGB will be converted to 24-bit by the tiff routines, so account
       for this.
    */
#ifdef HAVE_LIBTIFF
    switch(info->photoInterp) {
    case PHOTOMETRIC_MINISBLACK: colorSpace = NX_OneIsWhiteColorSpace; break;
    case PHOTOMETRIC_MINISWHITE: colorSpace = NX_OneIsBlackColorSpace; break;
    case PHOTOMETRIC_RGB: colorSpace = NX_RGBColorSpace; break;
    case PHOTOMETRIC_PALETTE: 
	colorSpace = NX_RGBColorSpace; 
	info->samplesPerPixel = 3;
	break;
    default:
	break;
    }

    ok = [self initData:NULL pixelsWide:info->width 
		pixelsHigh:info->height 
		bitsPerSample:info->bitsPerSample 
		samplesPerPixel:info->samplesPerPixel 
		hasAlpha:(info->samplesPerPixel > 3) 
		isPlanar:(info->planarConfig == PLANARCONFIG_SEPARATE) 
		colorSpace:colorSpace 
		bytesPerRow:0 
		bitsPerPixel:0];
    compression = info->compression;
#endif
    
    return ok;
}

- initFromFile:(const char *)fileName
{
    id		ok;
    TIFF	*image;

    _fileName = NXCopyStringBuffer(fileName);

    /* Open the file just to read image info */
    image = NXOpenTiffFile(fileName, NX_READONLY);
    if (!image) {
	[self free];
	return nil;
    }
    ok = [self _initFromImage:image];

    NXCloseTiff(image);
    return ok;
}

/* Loads only the default (first) image from the TIFF image contained in
   the stream. 
*/
- initFromStream:(NXStream *)stream
{
    id		ok;
    TIFF 	*image;

    image = NXOpenTiffStream(stream, NX_READONLY);
    if (!image)
	return nil;
    ok = [self _initFromImage:image];
    if (!ok) {
	[self free];
	return nil;
    }
    _imageNumber = 0;
    [self _dataFromImage:image];
    NXCloseTiff(image);
    return ok;
}

- initData: (void *)data pixelsWide:(int)width
		pixelsHigh:(int)height
		bitsPerSample:(int)bps
		samplesPerPixel:(int)spp
		hasAlpha:(BOOL)alpha
		isPlanar:(BOOL)isPlanar
		colorSpace:(int)colorSpace
		bytesPerRow:(int)rBytes
		bitsPerPixel:(int)pBits
{
    unsigned char **planes = NULL;
    if (data) {
        if (isPlanar) {
	    NX_MALLOC(planes, unsigned char *, PLANES);
	    planes[0] = data;
	    planes[1] = NULL;
	} else {
	    NX_MALLOC(planes, unsigned char *, 1);
	    planes[0] = data;
	}
    }
    return [self initDataPlanes:planes pixelsWide:width 
		pixelsHigh:height 
		bitsPerSample:bps 
		samplesPerPixel:spp 
		hasAlpha:alpha 
		isPlanar:isPlanar 
		colorSpace:colorSpace 
		bytesPerRow:rBytes 
		bitsPerPixel:pBits];
}

- initDataPlanes:(unsigned char **)planes pixelsWide:(int)width 
		pixelsHigh:(int)height 
		bitsPerSample:(int)bps 
		samplesPerPixel:(int)spp 
		hasAlpha:(BOOL)alpha 
		isPlanar:(BOOL)isPlanar 
		colorSpace:(int)colorSpace 
		bytesPerRow:(int)rBytes 
		bitsPerPixel:(int)pBits
{
    const char *instance_name;
    instance_name = NXBitmapImageRepInstanceName();
    if (!bps || !spp || !width || !height) {
	[self free];
	return nil;
    }

    _pixelsWide = width;
    _pixelsHigh = height;
    size.width  = width;
    size.height = height;
    _repFlags.bitsPerSample = bps;
    _repFlags.numColors     = spp;
    _moreRepFlags.isPlanar  = isPlanar;
    _colorSpace             = colorSpace;
    if (!pBits)
	pBits = bps * ((isPlanar) ? 1 : spp);
    bitsPerPixel            = pBits;
    if (!rBytes) 
	rBytes = ceil((float)width * bitsPerPixel / 8);
    _bytesPerRow            = rBytes;

    if (!planes || planes[0] == NULL) {
	_repFlags.dataSource    = DATA_OWNER;
        _repFlags.dataLoaded    = 0;
	_data = NULL;
    } else {
	_repFlags.dataSource    = DATA_NOTOWNER;
        _repFlags.dataLoaded    = 1;
	_data = planes;
	if (spp > 1 && _data[1] == NULL) {
	    // data came from initData:... and we need to set the other planes
	    int i;
	    for (i=1; i < spp; i++)
		_data[i] = (_data[0] + i*rBytes*height);
	    for (i=spp; i < PLANES; i++)
		_data[i] = NULL;
	}
    }
    return self;
}

+ (List *)newListFromFile:(const char *)fileName
{
    NXTiffInfo	*info;
    int		images;
    TIFF	*image;
    List	*aList;
    id		imageRep;

    if (!fileName)
        return nil;

    // We need to open the file and find out how many images there are
    image = NXOpenTiffFile(fileName, NX_READONLY);
    if (!image)
	return nil;

    images = 0;
    aList = [[List alloc] init];
    while ((info = NXGetTiffInfo(images, image))) {
	NX_FREE(info);
    	imageRep = [[[self class] alloc] _initFromImage:image];
	if (imageRep) {
	    [imageRep _setImageNumber:images];
	    [imageRep _setFileName:fileName];
            [aList addObject:imageRep];
	}
	images++;
    }
    NXCloseTiff(image);
    if (images == 0) {
	[aList free];
	return nil;
    }
    return aList;
}

+ (List *)newListFromStream:(NXStream *)stream
{
    NXTiffInfo	*info;
    int		images;
    TIFF	*image;
    List	*aList;
    id		imageRep;

    image = NXOpenTiffStream(stream, NX_READONLY);
    if (!image)
	return nil;

    images = 0;
    aList = [[List alloc] init];
    while ((info = NXGetTiffInfo(images, image))) {
	NX_FREE(info);
    	imageRep = [[[self class] alloc] _initFromImage:image];
	if (imageRep) {
	    [imageRep _setImageNumber:images];
            [aList addObject:imageRep];
	}
    	[imageRep _dataFromImage:image];
	images++;
    }
    NXCloseTiff(image);
    if (images == 0) {
	[aList free];
	return nil;
    }
    return aList;
}

+ (const char *const *)imageUnfilteredFileTypes
{
    static const char *const types[] = {"tiff", "tif", NULL};
    return types;
}

+ (const NXAtom *)imageUnfilteredPasteboardTypes
{
    static NXAtom tiffTypes[1] = {NULL};
    return (const NXAtom *)tiffTypes;
}

+ (BOOL) canLoadFromStream:(NXStream *)stream
{
    TIFF *image = NULL;
#ifdef HAVE_LIBTIFF
    int   pos = NXTell(stream);
    
    image = NXOpenTiffStream(stream, NX_READONLY);
    NXCloseTiff(image);
    NXSeek(stream, pos, NX_FROMSTART);
#endif

    return (image) ? YES : NO;
}

- (unsigned char *)data
{
    unsigned char *planes[5];
    if (!_data)
        [self getDataPlanes:planes];
    return *_data;
}

- getDataPlanes:(unsigned char **)data
{
    int i;
    /* Make sure data is loaded */
    if (!_repFlags.dataLoaded) {
	if (_moreRepFlags.isPlanar) {
	    NX_MALLOC(_data, unsigned char *, PLANES);
	    NX_MALLOC(_data[0], unsigned char, 
		_bytesPerRow*_pixelsHigh * _repFlags.numColors);
	    for (i=1; i < _repFlags.numColors; i++) 
		_data[i] = _data[0] + i*_bytesPerRow * _pixelsHigh;
	    for (i= _repFlags.numColors; i < PLANES; i++) 
		_data[i] = NULL;
	} else {
	    NX_MALLOC(_data, unsigned char *, 1);
	    NX_MALLOC(_data[0], unsigned char, _bytesPerRow*_pixelsHigh);
	}
	if (_fileName) {
	    TIFF *image;
	    image = NXOpenTiffFile(_fileName, NX_READONLY);
	    [self _dataFromImage:image];
	    NXCloseTiff(image);
	}
	_repFlags.dataLoaded = 1;
    }
    if (!data)
	return nil;
    for (i=0; i < _repFlags.numColors; i++)
    	data[i] = _data[i];
    return self;
}

- (BOOL)isPlanar
{
    return _moreRepFlags.isPlanar;
}

- (int)samplesPerPixel
{
    return _repFlags.numColors;
}

- (int)bitsPerPixel
{
    return bitsPerPixel;
}

- (int)bytesPerRow
{
    return _bytesPerRow;
}

- (int)bytesPerPlane
{
    return _bytesPerRow*_pixelsHigh;
}

- (int)numPlanes
{
    return (_moreRepFlags.isPlanar) ? _repFlags.numColors : 1;
}

- (int)colorSpace
{
    return _colorSpace;
}

- (BOOL)drawIn:(const NXRect *)rect
{
    if (!_repFlags.dataLoaded)
	[self data];

    return [self _displayDrawIn:rect];
}

- (BOOL)draw
{
    if (!_repFlags.dataLoaded)
	[self data];

    return [self _displayDraw];
}

- free
{
    if (_repFlags.dataSource == DATA_OWNER && _repFlags.dataLoaded) {
	if (_moreRepFlags.isPlanar) {
	    int i;
	    for (i=0; i < _repFlags.numColors; i++) 
		NX_FREE(_data[i]);
	} else
	    NX_FREE(_data[0]);
	NX_FREE(_data);
    }
    return [super free];
}

@end
