#import <appkit/tiff.h>
#import <appkit/color.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/graphics.h>
#import <streams/streams.h>
#import <stdio.h>
#import <stdlib.h>
#import <math.h>
#import "ImageControl.h"

@implementation ImageControl

Pixel 		*GetMeshedXY(int x, int y);
Pixel 		*GetPlanarXY(int x, int y);
Pixel 		*GetMeshedNext(void);
Pixel 		*GetPlanarNext(void);

	id				image;
	int				SPP;
	int				width, height;
	int				sampleWidth;
	int				pixelWidth;
	int				bitNumber;
	int				byteNumber;
	int				bytesPerRow;
	int				bitOffset;
	int				sampleMask;
	int				pixelMask;
	int				colorSpace;
	BOOL			hasAlpha;
	Pixel			workPixel;
	unsigned char		*data[5];
	int				curPixel;
	int				padBits;
	int				pixelFill;

+ new: (id)imageIn;
{
	self				= [super new];
	image			= imageIn;
	SPP				= [image samplesPerPixel];
	width			= [image pixelsWide];
	height			= [image pixelsHigh];
	hasAlpha		= [image hasAlpha];
	bitNumber 		= 0;
	sampleWidth 		= [image bitsPerSample];
	pixelWidth 		= sampleWidth * SPP;
	bytesPerRow 		= [image bytesPerRow];
	padBits			= bytesPerRow * 8 - ([image isPlanar] ? sampleWidth : pixelWidth) * width;
	byteNumber 		= 0;
	bitOffset 		= 0;
	sampleMask 		= (1 << sampleWidth) - 1;
	pixelMask 		= (1 << pixelWidth) - 1;
	colorSpace		= [image colorSpace];
	workPixel.count 	= SPP;
	pixelFill 			= 8 - pixelWidth % 8;
	if (pixelFill > 7) pixelFill = 0;
	[image getDataPlanes: data];
	
	fprintf(stderr, "Bytes Per Row = %d and Padding Bits = %d\n", bytesPerRow, padBits);
	
	return self;
}

- (GetPixelXYFunc)getXYFunction
{
	if ([image isPlanar]) 	return GetPlanarXY;
	else					return GetMeshedXY;
}

- resetNext
{
	byteNumber 	= 0;
	bitOffset 	= 0;
	curPixel 		= 0;

	return self;
}

- (GetPixelNextFunc)getNextFunction
{
	if ([image isPlanar]) 	return GetPlanarNext;
	else					return GetMeshedNext;
}

Pixel *GetMeshedXY(int x, int y)
{
	static unsigned long	value;			// tempoary value it will find
	static int				contained, index;	// working variables
	static int				i;				// index value;
	
	bitNumber 	= (y * width + x) * pixelWidth;   // Figures starting bit in data
	byteNumber 	= bitNumber / 8;			// Finds byte bits start in
	bitOffset	 	= bitNumber % 8;			// Discovers bit offset in byte

	value = *(data[0] + byteNumber) & 0x000000FF;

	for (contained=8-bitOffset, index=1; contained < pixelWidth; index++, contained+=8) {
		value = value << 8;
		value |= (*(data[0] + byteNumber + index) & 0x000000FF);
	}

	// Shift bits and mask off extra stuff.
	for (i = SPP - 1; i >= 0; i--) {
		workPixel.values[i] = value & sampleMask;
		value >>= sampleWidth;
	}
/*
	fprintf(stderr, "bitw=%4d bitnum=%4d bytenum=%4d bitoff=%4d mask=%0X  values = ", 
						sampleWidth, bitNumber, byteNumber, bitOffset, sampleMask);
	for (i = 0; i < SPP; i++) {
		fprintf(stderr, "%02X ", workPixel.values[i]);
	}
	fprintf(stderr, "\n");
*/
	return &workPixel;
}

Pixel *GetPlanarXY(int x, int y)
{
	static unsigned long	value[5];			// tempoary value it will find
	static int				contained, index;	// working variables
	static int				i;				// Index variable
	
	bitNumber 	= (y * width + x) * sampleWidth;   // Figures starting bit in data
	byteNumber 	= bitNumber / 8;			// Finds byte bits start in
	bitOffset	 	= bitNumber % 8;			// Discovers bit offset in byte

	for (i = 0; i < SPP; i++) {
		// start getting the pixel. It can span multiple bytes
		value[i] = *(data[i] + byteNumber) & 0x000000FF;
	}

	for (contained=8-bitOffset, index=1; contained < sampleWidth; index++, contained+=8) {
		for (i = 0; i < SPP; i++) {
			value[i] = value[i] << 8;
			value[i] |= (*(data[i] + byteNumber + index) & 0x000000FF);
		}
	}

	// Shift bits and mask off extra stuff.
	for (i = 0; i < SPP; i++) {
		workPixel.values[i] = value[i]>>(8*index-bitOffset-sampleWidth)&sampleMask;
	}
/*
	fprintf(stderr, "bitw=%4d bitnum=%4d bytenum=%4d bitoff=%4d mask=%0X  values = ", 
						sampleWidth, bitNumber, byteNumber, bitOffset, sampleMask);
	for (i = 0; i < SPP; i++) {
		fprintf(stderr, "%02X ", workPixel.values[i]);
	}
	fprintf(stderr, "\n");
*/
	return &workPixel;
}

Pixel 	*GetMeshedNext(void)
{
	static unsigned long	value;			// tempoary value it will find
	static int				contained, index;	// working variables
	static int				i;				// index value;
	
	value = (*(data[0] + byteNumber) & 0x000000FF) >> (pixelFill - bitOffset);

	for (contained=8-bitOffset, index=1; contained < pixelWidth; index++, contained+=8) {
		value = value << 8;
		value |= (*(data[0] + byteNumber + index) & 0x000000FF);
	}

//	fprintf(stderr, "%08X ", value);

	// Shift bits and mask off extra stuff.
	for (i = SPP - 1; i >= 0; i--) {
		workPixel.values[i] = value & sampleMask;
		value >>= sampleWidth;
	}

//	fprintf(stderr, "bitw=%4d bitnum=%4d bytenum=%4d bitoff=%4d mask=%0X  values = ", 
//						sampleWidth, bitNumber, byteNumber, bitOffset, sampleMask);
//	for (i = 0; i < SPP; i++) {
//		fprintf(stderr, "%02X ", workPixel.values[i]);
//	}
//	fprintf(stderr, "\n");

//	fprintf(stderr, "%02X ", workPixel.values[0]);
	curPixel++;
	if (curPixel == width) {
		bitOffset += padBits;
		curPixel = 0;
//		fprintf(stderr, "\n");
	}
	bitOffset += pixelWidth;
	if (bitOffset  >= 8) {
		byteNumber += bitOffset / 8;
		bitOffset %= 8; 
	}

	return &workPixel;
}

Pixel 	*GetPlanarNext(void)
{
	static unsigned long	value[5];			// tempoary value it will find
	static int				contained, index;	// working variables
	static int				i;				// index value;
	
	for (i = 0; i < SPP; i++) {
		value[i] = (*(data[i] + byteNumber) & 0x000000FF)>>(8-sampleWidth-bitOffset);
	}
	
	// Shift bits and mask off extra stuff.
	for (i = SPP - 1; i >= 0; i--) {
		workPixel.values[i] = value[i] & sampleMask;
	}
/*
	fprintf(stderr, "bitw=%4d bitnum=%4d bytenum=%4d bitoff=%4d mask=%0X  values = ", 
						sampleWidth, bitNumber, byteNumber, bitOffset, sampleMask);
	for (i = 0; i < SPP; i++) {
		fprintf(stderr, "(%02X, %02X); ", workPixel.values[i], value[i]);
	}
	fprintf(stderr, "\n");
*/
//	fprintf(stderr, "%01X ", workPixel.values[0]);
	curPixel++;
	if (curPixel == width) {
		bitOffset += padBits;
		curPixel = 0;
//		fprintf(stderr, "\n");
	}
	bitOffset += sampleWidth;
	if (bitOffset  >= 8) {
		byteNumber += bitOffset / 8;
		bitOffset %= 8; 
	}

	return &workPixel;
}

Pixel *OIWtoGray(Pixel *p)
{
	p->values[0] = p->values[0];

	return p;
}

Pixel *OIBtoGray(Pixel *p)
{
	p->values[0] = sampleMask - p->values[0];

	return p;
}

Pixel *RGBtoGray(Pixel *p)
{
  	// .33R+ .5G+ .17B
	p->values[0] = (((p->values[0])*11 + (p->values[1])*16 + (p->values[2])*5) >> 5);
	if (hasAlpha) {
		p->values[1] = p->values[3];
		p->count = 2;
	} else p->count = 1;
	return p;
}

Pixel *CMYKtoGray(Pixel *p)
{
	// I need to find out the values for doing the CYMK conversion.
	return p;
}

- (ToGrayFunc) getToGrayFunction
{
	NXColorSpace	cs = [image colorSpace];

	if (cs == NX_OneIsWhiteColorSpace) return OIWtoGray;
	else if (cs == NX_OneIsBlackColorSpace) return OIBtoGray;
	else if (cs == NX_RGBColorSpace) return RGBtoGray;
	
	return CMYKtoGray;
}

Pixel *OIWtoRGB(Pixel *p)
{
	if (hasAlpha) {
		p->values[3] = p->values[1];
		p->count = 4;
	} else p->count = 3;
	p->values[1] = p->values[2] = p->values[0];
	return p;
}

Pixel *OIBtoRGB(Pixel *p)
{
	if (hasAlpha) {
		p->values[3] = p->values[1];
		p->count = 4;
	} else p->count = 3;
	p->values[1] = p->values[2] = sampleMask - p->values[0];
	return p;
}

Pixel *RGBtoRGB(Pixel *p)
{
	return p;
}

Pixel *CMYKtoRGB(Pixel *p)
{
	// I need to find out the values for doing the CYMK conversion.
	return p;
}

- (ToRGBFunc) getToRGBFunction
{
	NXColorSpace	cs = [image colorSpace];

	if (cs == NX_OneIsWhiteColorSpace) return OIWtoRGB;
	else if (cs == NX_OneIsBlackColorSpace) return OIBtoRGB;
	else if (cs == NX_RGBColorSpace) return RGBtoRGB;
	
	return CMYKtoRGB;
}

- ditherImage
{
	short			*dp;
	short			*dithpic;
	unsigned char		pix8, bit;
	int				i, j, err;
	unsigned char		*pp, *pq;
	id				imageOut;
	long				size = [image pixelsWide] * [image pixelsHigh];
	GetPixelNextFunc	myFuncNext;
	int				maxColor = 1 << [image bitsPerSample];
	float				multiplier;
	ToGrayFunc		GetColor;
	
	if (([image bitsPerSample] == 1) && ([image samplesPerPixel] == 1)) {
		imageOut = [NXBitmapImageRep alloc];
		[imageOut 	initData: 		NULL
					pixelsWide: 		[image pixelsWide]
					pixelsHigh: 		[image pixelsHigh]
					bitsPerSample:	1
					samplesPerPixel:	1
					hasAlpha:		NO
					isPlanar:			NO
					colorSpace:		NX_OneIsWhiteColorSpace
					bytesPerRow:		0
					bitsPerPixel:		0];
		if ([image colorSpace] == NX_OneIsBlackColorSpace) {
#ifdef DEBUG
			fprintf(stderr, "Inverting image\n");
#endif
			j = 0xFF;
		}
		else j = 0x00;
		size = (([image pixelsWide] + 7) / 8) * [image pixelsHigh];
		pp = [image data];
		pq = [imageOut data];
		for (i = 0; i < size; i++) {
			*pq++ = *pp++ ^ j;
		}
		return imageOut;
	}
	
	imageOut = [NXBitmapImageRep alloc];
	[imageOut 	initData: 		NULL
				pixelsWide: 		[image pixelsWide]
				pixelsHigh: 		[image pixelsHigh]
				bitsPerSample:	1
				samplesPerPixel:	1
				hasAlpha:		NO
				isPlanar:			NO
				colorSpace:		NX_OneIsWhiteColorSpace
				bytesPerRow:		0
				bitsPerPixel:		0];

	dithpic = (short *)malloc(width * height * sizeof(short));
	if (!dithpic) {
		[imageOut free];
#ifdef DEBUG
		fprintf(stderr, "Unable to malloc memory\n");
#endif
		return nil;
	}

#ifdef DEBUG
	fprintf(stderr, "Converting to BW...");
#endif
	myFuncNext = [self getNextFunction];
	[self resetNext];
	
	GetColor = [self getToGrayFunction];
	multiplier = 255.0 / (float)(maxColor - 1);
	for (i = 0, dp = dithpic; i < size; i++, dp++) {
		*dp = GetColor(myFuncNext())->values[0] * multiplier;
	}

#ifdef DEBUG
	fprintf(stderr,"Ditherizing1...");
#endif
	dp = dithpic /* [imageWork data] */;
	pp = [imageOut data];

	bit = pix8 = 0;
	for (i = 0; i < height; i++) {
		for (j = 0; j < width; j++, dp++) {
			if (*dp < 128) { 
				err = *dp;     
				pix8 |= 0; 
			} else { 
				err = *dp - 255; 
				pix8 |= 1; 
			}

			if (bit == 7) {
				*pp++ = pix8;
				bit = pix8 = 0;
			} else { 
				pix8 <<= 1; 
				bit++; 
			}
	
			if (j < width - 1) dp[1] += ((err * 7) / 16);
	
			if (i < height - 1) {
				dp[width] += ((err * 5) / 16);
				if (j > 0)			dp[width - 1] += ((err * 3) / 16);
				if (j < width - 1)	dp[width + 1] += (err / 16);
			}
		}
//		fprintf(stderr, "Bit = %d (%p)", bit, pp);
		if (bit) {
			*pp++ = pix8;
			pix8 = bit = 0;
		}
//		fprintf(stderr, " -> %d (%p)\n", bit, pp);
	}

#ifdef DEBUG
	fprintf(stderr,"done\n");
#endif

	free(dithpic);

	return imageOut;
}

- makePalette	: 	(int)cMapSize
			:	(unsigned char *)data
			:	(unsigned char *)r
			:	(unsigned char *)g
			:	(unsigned char *)b
{
	int					bits;
	int					rBits, gBits, bBits;
	int					rMask, gMask, bMask;
	int					i;
	double				mod;
	GetPixelNextFunc		getNext;
	ToRGBFunc			toRGB;
	Pixel				*pixel;
	
	bits = log(cMapSize) / log(2);
	rBits = bits / 3;
	gBits = bits / 3;
	bBits = bits / 3;
	if (rBits + bBits + gBits < bits) rBits++;
	if (rBits + bBits + gBits < bits) gBits++;
	rMask = ((0x01 << rBits) - 1) << (bits - rBits);
	gMask = ((0x01 << gBits) - 1) << (bits - (gBits + rBits));
	bMask = ((0x01 << bBits) - 1) << (bits - (bBits + rBits + gBits));
#ifdef DEBUG
	fprintf(stderr, "Creating Palette: #%d R,#%d G, #%d B...", rBits, gBits, bBits); 
#endif
	mod = 255 / (cMapSize - 1);
	for (i = 0; i < cMapSize; i++) {
		r[i] = (((i & rMask) * (cMapSize - 1)) / rMask) * mod;
		g[i] = (((i & gMask) * (cMapSize - 1)) / gMask) * mod;
		b[i] = (((i & bMask) * (cMapSize - 1)) / bMask) * mod;
	}
#ifdef DEBUG
	fprintf(stderr, "done.\n");
#endif
	getNext = [self getNextFunction];
	toRGB = [self getToRGBFunction];
	[self resetNext];
	mod = (double)cMapSize / (double)(sampleMask + 1);
#ifdef DEBUG
	fprintf(stderr, "Mapping Pixels...%f...%d...%d...", mod, sampleMask, cMapSize);
#endif
	for (i = 0; i < width * height; i++) {
		pixel = toRGB(getNext());
		pixel->values[0] *= mod;
		pixel->values[1] *= mod;
		pixel->values[2] *= mod;
		*data++ = 	(pixel->values[0] & rMask) |
					((pixel->values[1] >> rBits) & gMask) |
					(pixel->values[2] >> (rBits + gBits));
	}
#ifdef DEBUG
	fprintf(stderr, "done.\n");
#endif
}

- makePaletteBW	: 	(int)cMapSize
				:	(unsigned char *)data
				:	(unsigned char *)r
				:	(unsigned char *)g
				:	(unsigned char *)b
{
	int					i, j;
	double				mod;
	GetPixelNextFunc		getNext;
	Pixel				*pixel;
	
	mod = 255 / (cMapSize - 1);
#ifdef DEBUG
	fprintf(stderr, "Creating Palette: %d...", j); 
#endif
	for (i = 0; i < cMapSize; i++) {
		r[i] = g[i] = b[i] = mod * i;
#ifdef DEBUG
		fprintf(stderr, "%d %d %d, ", r[i], g[i], b[i]);
#endif
	}
#ifdef DEBUG
	fprintf(stderr, "done.\n");
#endif
	getNext = [self getNextFunction];
	[self resetNext];
	mod = (double)(cMapSize - 1) / (double)sampleMask;
#ifdef DEBUG
	fprintf(stderr, "Mapping Pixels...%f...%d...%d...", mod, sampleMask, cMapSize);
#endif
	for (i = 0; i < width * height; i++) {
		pixel = getNext();
		*data++ = (unsigned char)(pixel->values[0] * mod);
	}
#ifdef DEBUG
	fprintf(stderr, "done.\n");
#endif
}

- convertToPalette: (int)size
		andReturn: (unsigned char **)outPic 
		andPalettes: (unsigned char *)r : (unsigned char *)g : (unsigned char *)b
{
	*outPic = (unsigned char *)malloc(width * height * sizeof(unsigned char));

#ifdef DEBUG	
	fprintf(stderr, "color space = %d\n", [image colorSpace]);
	fprintf(stderr, "%d %d %d %d\n",	NX_OneIsWhiteColorSpace,
								NX_OneIsBlackColorSpace,
								NX_RGBColorSpace,
								NX_CMYKColorSpace);
#endif
	if (	([image colorSpace] == NX_OneIsWhiteColorSpace) ||
		([image colorSpace] == NX_OneIsBlackColorSpace)) {
#ifdef DEBUG
		fprintf(stderr, "This is a B&W pic, so handle as such\n");
#endif
		[self makePaletteBW: size : *outPic : r : g : b];
	} else if (([image colorSpace] == NX_RGBColorSpace) ||
			([image colorSpace] == NX_CMYKColorSpace)) {
		[self makePalette: size : *outPic : r : g : b];
	} else {
#ifdef DEBUG
		fprintf(stderr, "Opps, this is an impossible color space... leaving.\n");
#endif
	}
}

@end
