#import "../ImageOpr.h"
#import <appkit/Application.h>
#import <appkit/publicWraps.h>
#import <appkit/NXBitmapImageRep.h>
#import <appkit/Control.h>
#import <appkit/Panel.h>
#import <appkit/TextField.h>
#import <stdio.h>
#import <stdlib.h>
#import <string.h>
#import <libc.h>
#import <streams/streams.h>
#import "../TVController.h"
#import "../ToyWin.h"
#import "../ToyWinEPS.h"
#import "../ToyView.h"
#import "../common.h"
#import "../getpixel.h"
#import "../strfunc.h"
#import "../ColorMap.h"
#import "imageOperation.h"
#ifdef _REDUCE_BY_MDA
# import "MDAmethod.h"
# define DitherCLASS	MDAmethod
#else
# import "Dither.h"
# define DitherCLASS	Dither
#endif

@implementation ImageOpr (ReduceColor)

static short tagColors[] = { /* Recude into N colors */
	256, 256, 64, 16, 8
};

/* Local Method */
- (int)reduceColor:(ColorMap *)colormap info:(commonInfo *)cinf
	map:(unsigned char **)map newmap:(unsigned char **)newmap
	alpha:(BOOL)alpha tag:(int)tag
{
	id	msgtext;
	paltype *pal = NULL;
	int i, x, y, idx;
	int trial, cnum;
	id <Dithering> dither[3];
	char msgbuf[80];
	unsigned char *rr, *gg, *bb;
	int elm[MAXPLANE];
	unsigned char apool[MAXWidth];
	unsigned char *alp;
	static short tagOpr[] = {
	/* initial value of 'trial'... index for dthbits[][] */
		2, /* MCA (default reduction) */
		5, /* Dither + MCA 256 colors */
		7, /* Dither + MCA  64 colors */
		5, /* Dither + MCA  16 colors */
		5  /* Dither + MCA   8 colors */
	};
	static short dthbits[][3] = {
		{7, 7, 7}, {7, 7, 6}, {6, 6, 6}, {6, 6, 5},
		{5, 5, 5}, {5, 5, 4}, {4, 4, 4}, {4, 4, 3},
		{3, 3, 3}, {3, 3, 2}, {2, 2, 2 /* never */ }};

	msgtext = [theController messageDisplay:"Starting Reduction..."];

	cnum = 0;
	for (i = 0; i < 3; i++)
		dither[i] = nil;
	for (i = 0; i < 3; i++) {
		dither[i] = [DitherCLASS alloc];
		if (dither[i] == nil
			|| [dither[i] init:128 width:cinf->width] == nil)
			goto ErrEXIT;
	}

	resetPixel(map, 0);
	idx = 0;
	for (y = 0; y < cinf->height; y++) {
		alp = alpha ? &newmap[ALPHA][idx] : apool;
		for (x = 0; x < cinf->width; x++, idx++) {
			getPixel(&elm[0], &elm[1], &elm[2], &elm[3]);
			if ((alp[x] = elm[ALPHA]) == AlphaTransp) {
				for (i = 0; i < 3; i++)
					newmap[i][idx] = 255;
			}else {
				for (i = 0; i < 3; i++)
					newmap[i][idx] = elm[i];
			}
		}
	}

	for (trial = tagOpr[tag]; ; trial++) {
	    sprintf(msgbuf, "Trying Reduction: RGB=%d:%d:%d",
		dthbits[trial][0], dthbits[trial][1], dthbits[trial][2]);
	    [msgtext setStringValue: msgbuf];
	    NXPing();

	    for (i = 0; i < 3; i++)
		[dither[i] reset:(1 << dthbits[trial][i])];
	    [colormap init_regColor];
	    for (y = 0; y < cinf->height; y++) {
		for (i = 0; i < 3; i++) {
			rr = [dither[i] buffer];
			gg = &newmap[i][y * cinf->width];
			for (x = 0; x < cinf->width; x++)
				*rr++ = *gg++;
		}
		rr = [dither[RED] getNewLine];
		gg = [dither[GREEN] getNewLine];
		bb = [dither[BLUE] getNewLine];
		for (x = 0; x < cinf->width; x++) {
			if ([colormap regColorToMap:*rr++:*gg++:*bb++] < 0)
				goto ReTRY;	/* Too Many Color */
		}
	    }
	    break;	/* OK */
ReTRY:	    ;
	}

	[msgtext setStringValue: "Making Palette..."];
	NXPing();
	cnum = tagColors[tag];
	pal = [colormap getReducedMap: &cnum alpha:alpha];
	[msgtext setStringValue: "Writing Image..."];
	NXPing();

	for (i = 0; i < 3; i++)
		[dither[i] reset:(1 << dthbits[trial][i])];
	idx = 0;
	for (y = 0; y < cinf->height; y++) {
		for (i = 0; i < 3; i++) {
			rr = [dither[i] buffer];
			gg = &newmap[i][idx];
			for (x = 0; x < cinf->width; x++)
				*rr++ = *gg++;
		}
		rr = [dither[RED] getNewLine];
		gg = [dither[GREEN] getNewLine];
		bb = [dither[BLUE] getNewLine];
		for (x = 0; x < cinf->width; x++, idx++) {
			unsigned char *p = pal[mapping(*rr, *gg, *bb)];
			for (i = 0; i < 3; i++)
				newmap[i][idx] = p[i];
			rr++, gg++, bb++;
		}
	}

ErrEXIT:
	for (i = 0; i < 3; i++)
		if (dither[i]) [dither[i] free];
	[theController messageDisplay:NULL];
	return cnum;
}


/* Local Method */
- (commonInfo *)reducedBitmap:(unsigned char **)newmap
	from:(ToyWin *)tw with:(commonInfo *)cinf tag:(int)tag
{
	commonInfo *newinfo = NULL;
	ColorMap *colormap = nil;
	unsigned char *map[MAXPLANE];
	int	cnum = 0, pl, err = 0;
	BOOL	hasalpha = NO;
	const char *fnam;

	newmap[0] = NULL;
	fnam = [tw filename];
	if ((err = [tw getBitmap:map info: &cinf]) == 0)
		err = initGetPixel(cinf);
	if (err) {
		errAlert(fnam, err);
		return NULL;
	}

	colormap = [[ColorMap alloc] init];
	if ([colormap allocFullColor] == nil) {
		err = Err_MEMORY;
		goto ErrEXIT;
	}else {
		cnum = [colormap getAllColor:map limit:0 alpha:&hasalpha];
		if (hasalpha) ++cnum;
		if (cnum <= tagColors[tag]) {
			(void)[self needReduce:fnam colors:cnum ask:NO];
			goto ErrEXIT;
		}
	}
	pl = hasalpha ? 4 : 3;
	err = allocImage(newmap, cinf->width, cinf->height, 8, pl);
	newinfo = (commonInfo *)malloc(sizeof(commonInfo));
	if (!newinfo)
		err = Err_MEMORY;
	if (err) goto ErrEXIT;

	cnum = [self reduceColor:colormap info:cinf
			map:map newmap:newmap alpha:hasalpha tag:tag];
	if (cnum <= 0) {
		err = Err_MEMORY;
		goto ErrEXIT;
	}
	*newinfo = *cinf;
	newinfo->xbytes = newinfo->width;
	newinfo->palsteps = cnum;
	newinfo->bits = 8;
	newinfo->numcolors = 3;
	newinfo->isplanar = YES;
	newinfo->alpha = hasalpha;
	newinfo->palette = [colormap getPalette];
	sprintf(newinfo->memo, "%d x %d  %dcolors%s",
			newinfo->width, newinfo->height, cnum,
			(newinfo->alpha ? "  Alpha" : ""));
	comment_copy(newinfo->memo, cinf->memo);

	if (colormap) [colormap free];
	[tw freeTempBitmap];
	return newinfo;

ErrEXIT:
	if (err) errAlert(fnam, err);
	[tw freeTempBitmap];
	if (newmap[0]) free((void *)newmap[0]);
	if (newinfo) free((void *)newinfo);
	if (colormap) [colormap free];
	return NULL;
}


- reduceUsing: (int)tag
{
	ToyWin		*tw, *newtw;
	commonInfo	*cinf;
	unsigned char	*map[MAXPLANE];
	const char *fnam;
	char	fn[256];

	if ((tw = [self keyParentWindow: Reduction]) == nil) {
		NXBeep();
		return self;
	}
	fnam = [tw filename];
	cinf = [[tw toyView] commonInfo];
	if (![self checkCommonInfo:cinf filename:fnam])
		return self;

	if (cinf->numcolors == 1) {
		warnAlert(fnam, Err_OPR_IMPL);
		return NULL;
	}
	if (cinf->palette && cinf->palsteps <= tagColors[tag]) {
		(void)[self needReduce:fnam colors:cinf->palsteps ask:NO];
		return NULL;
	}

	if ((cinf = [self reducedBitmap:map from:tw with:cinf tag:tag]) == NULL)
		return self;
	newtw = [[ToyWin alloc] init:tw by:Reduction];
	if (tag == 0)
		sprintf(fn, "%s(Reduce)", fnam);
	else
		sprintf(fn, "%s(Reduce%d)", fnam, tagColors[tag]);
	[newtw initLocateWindow:fn width:cinf->width height:cinf->height];
	if ([newtw drawView:map info: cinf] == nil) {
		errAlert(fnam, Err_MEMORY);
		[newtw free];
		free((void *)map[0]);
		free((void *)cinf);
	}else
		[theController newWindow: newtw];
	return self;
}


- (BOOL)needReduce:(const char *)fn colors:(int)cnum ask:(BOOL)ask
{
	const char *qust, *title, *cancel, *reduce;

	qust = NXLocalizedString("No Need to Reduce", NULL, NO_Need_Reduction);
	title = NXLocalizedString("WARNING", NULL, WARNING);
	if (ask) {
	    cancel = NXLocalizedString("Cancel", NULL, Stop_SAVE);
	    reduce = NXLocalizedString("Reduce", NULL, BMP_Reduce);
	    if (NXRunAlertPanel(title, qust, cancel, reduce, NULL, fn, cnum))
		return NO;
	}else
	    NXRunAlertPanel(title, qust, NULL, NULL, NULL, fn, cnum);
	return YES;
}

@end
