/*
        mindlib/palette.c

        This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation; either version 2, or (at your option)
        any later version.

        This program 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 General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program; if not, write to the Free Software
        Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xatom.h>
#include <stdio.h>
#include "../include/palette.h"
#include "../include/hash.h"
#include "../include/misc.h"
#include "../include/Image.h"
#include "../include/xpaint.h"

typedef struct list_s {
	Display		*dpy;
	Colormap	cmap;
	Palette		*map;
	struct list_s	*next;
} PaletteList;

typedef struct Col_s {
	XColor		color;
	Boolean		used;
	Boolean		invalid;
} Col;

#define HASH_SIZE 128
#define HASH(c)   	  ((int)((c).red + (c).green + (c).blue) % HASH_SIZE)
#define HASH_PIXEL(c)     (c % HASH_SIZE)

static PaletteList	*cmapList = NULL;

static int      readCMP(Col *cca, Col *ccb)
{
	XColor	*ca = &cca->color, *cb = &ccb->color;

	if (ca->red != cb->red)
		return ca->red < cb->red ? -1 : 1;
	if (ca->green != cb->green)
		return ca->green < cb->green ? -1 : 1;
	if (ca->blue != cb->blue)
		return ca->blue < cb->blue ? -1 : 1;
        return 0;
}

static int      cmpPixel(Col *cca, Col *ccb)
{
	if (cca->color.pixel == ccb->color.pixel)
		return 0;
	return (cca->color.pixel < ccb->color.pixel) ? -1 : 1;
}

static void	freeFunc(void *junk)
{
	/*
	**  Nop free func
	*/
}

static void	entryUnlink(Palette *map, Col *node)
{
	node->used    = True;
	node->invalid = False;
	map->nfree--;
}


#define STEP	256

static Palette *paletteNew(Widget w, Boolean useDefault)
{
	int			i, v;
	Display			*dpy = XtDisplay(w);
	Screen			*screen = XtScreen(w);
	Colormap		rcmap = DefaultColormapOfScreen(screen);
	Palette			*map = XtNew(Palette);
	Col			*ctable;
	Visual			*Visual = NULL;
	PaletteList		*New;
	int			end, depth = -1;
	Boolean			flg = False;

	XtVaGetValues(w, XtNvisual, &Visual, XtNdepth, &depth, NULL);
	if (Visual == NULL || useDefault)
		Visual = DefaultVisualOfScreen(screen);
	if (depth <= 0 || useDefault)
		depth = DefaultDepthOfScreen(screen);

	map->htable  = NULL;
	map->ltable  = NULL;
	map->display = dpy;
	map->htable  = HashCreate((int (*)(void *, void *))readCMP,  freeFunc, HASH_SIZE);
	map->ltable  = HashCreate((int (*)(void *, void *))cmpPixel, freeFunc, HASH_SIZE);
	map->list    = NULL;
	map->last    = NULL;
#if defined(__cplusplus) || defined(c_plusplus)
	map->isMapped= Visual->c_class != TrueColor;
	map->isGrey  = (Visual->c_class == StaticGray || Visual->c_class == GrayScale);
#else
	map->isMapped= Visual->class != TrueColor;
	map->isGrey  = (Visual->class == StaticGray || Visual->class == GrayScale);
#endif
	map->ncolors = Visual->map_entries;
	map->nfree   = 0;
	map->ctable  = NULL;
	map->visual  = Visual;
	map->depth   = depth;
	map->userList = NULL;
	map->isDefault = False;
	map->mine    = None;

#if defined(__cplusplus) || defined(c_plusplus)
	switch (Visual->c_class) {
#else
	switch (Visual->class) {
#endif
	case TrueColor:
		map->rShift = 0;
		map->gShift = 0;
		map->bShift = 0;
		map->rRange = 1;
		map->gRange = 1;
		map->bRange = 1;
		for (v = Visual->red_mask; (v & 1) == 0; v >>= 1)
			map->rShift++;
		for (; (v & 1) == 1; v >>= 1)
			map->rRange <<= 1;
		for (v = Visual->green_mask; (v & 1) == 0; v >>= 1)
			map->gShift++;
		for (; (v & 1) == 1; v >>= 1)
			map->gRange <<= 1;
		for (v = Visual->blue_mask; (v & 1) == 0; v >>= 1)
			map->bShift++;
		for (; (v & 1) == 1; v >>= 1)
			map->bRange <<= 1;
        break;
	/* case StaticGray: */
	case StaticColor:
		map->readonly  = True;
		map->cmap = XCreateColormap(dpy, RootWindowOfScreen(screen), 
						Visual, AllocNone);
		map->isDefault = False;
		goto addlist;
	default:
		map->readonly = False;
		if (useDefault) {
			map->cmap = rcmap;
			map->isDefault = True;
		} else {
			map->cmap = XCreateColormap(dpy, RootWindowOfScreen(screen), 
						Visual, AllocAll);
		}
		break;
	}

	ctable = (Col *)XtCalloc(sizeof(Col), Visual->map_entries);
	map->ctable = ctable;
	end = CellsOfScreen(screen);
	for (i = 0; i < Visual->map_entries; i += STEP) {
		XColor	xcol[STEP];
		int	cnt = Visual->map_entries - i;
		int	j, d;

		if (cnt > STEP)
			cnt = STEP;

		for (j = 0; j < cnt; j++) {
			Col	*c = &ctable[i + j];

			c->color.pixel = i + j;
			c->color.flags = DoRed|DoGreen|DoBlue;
			xcol[j].pixel = i + j;
			xcol[j].flags = DoRed|DoGreen|DoBlue;

			c->used    = False;
			c->invalid = False;
		}

		if (i >= end)
			d = 0;
		else	
			d = MIN(end - i, cnt);

		if (i < end)
			XQueryColors(dpy, rcmap, xcol, d);
		if (!flg) {
			for (j = d; j < cnt; j++) {
				xcol[j].flags = DoRed|DoGreen|DoBlue;
				xcol[j].red = xcol[j].green = xcol[j].blue = 0xffff;
			}
			if (d == 0)
				flg = True;
		}

		if (!map->isDefault)
			XStoreColors(dpy, map->cmap, xcol, cnt);

		for (j = 0; j < cnt; j++) {
			Col		*c = &ctable[i + j];

			c->color.red   = xcol[j].red & 0xff00;
			c->color.green = xcol[j].green & 0xff00;
			c->color.blue  = xcol[j].blue & 0xff00;
			HashAdd(map->htable, HASH(c->color), c);
			HashAdd(map->ltable, HASH_PIXEL(c->color.pixel), c);
		}
	}

	map->nfree = Visual->map_entries;

	if (!map->isDefault) {
		Boolean		got = False;

		got = !map->isMapped;
		for (i = 0; i < Visual->map_entries; i++) {
			if (ctable[i].color.pixel == BlackPixelOfScreen(screen)) {
				entryUnlink(map, &ctable[i]);
			} else if (ctable[i].color.pixel == WhitePixelOfScreen(screen)) {
				entryUnlink(map, &ctable[i]);
			} else if (!got) {
				map->mine = i;
				entryUnlink(map, &ctable[i]);
				got = True;
			}
		}
	}

addlist:
	New = (PaletteList*)XtMalloc(sizeof(PaletteList));
	New->dpy     = XtDisplay(w);
	New->cmap    = map->cmap;
	New->next    = cmapList;
	New->map     = map;
	cmapList     = New;

	return map;
}

Palette *PaletteCreate(Widget w)
{
	return paletteNew(GetShell(w), False);
}

static void addColor(Palette *map, XColor *color)
{
	Col	*node, *n;
	int	i;

	if (map->readonly) {
		/*
		**  The temporary color is needed since alloc
		**  will change the pixel values.
		*/
		XColor	Newc;
		Newc = *color;
		node = XtNew(Col);

		Newc.flags = DoRed|DoGreen|DoBlue;
		XAllocColor(map->display, map->cmap, &Newc);
		node->color.pixel = color->pixel = Newc.pixel;
	} else {
		Col		*cptr = (Col*)map->ctable;
		int		d, curDif = -1;
		int		rd, gd, bd;

		/*
		**  Find the closest match in the existing color map.
		*/
		node = cptr;

		for (i = 0; i < map->ncolors; i++, cptr++) {
			if (map->nfree != 0 && cptr->used)
				continue;

			rd = (cptr->color.red >> 8)   - (color->red >> 8);
			gd = (cptr->color.green >> 8) - (color->green >> 8);
			bd = (cptr->color.blue >> 8)  - (color->blue >> 8);
			d  = rd * rd + gd * gd + bd * bd;

			if (d < curDif || curDif == -1) {
				node   = cptr;
				curDif = d;
			}
		}

		/*
		**  All free colors used up.
		*/
		if (map->nfree == 0) {
			color->pixel = node->color.pixel;
			return;
		}

		/*
		**  Get next free color.
		*/
		entryUnlink(map, node);

		n = (Col *)HashFind(map->ltable, HASH_PIXEL(node->color.pixel), node);
		HAshRemove(map->ltable, HASH_PIXEL(color->pixel), n);
		HAshRemove(map->htable, HASH(*color), node);

		color->pixel = node->color.pixel;
	}

	node->used        = True;
	node->invalid     = False;
	node->color.red   = color->red   & 0xff00;
	node->color.green = color->green & 0xff00;
	node->color.blue  = color->blue  & 0xff00;
	node->color.flags = DoRed|DoGreen|DoBlue;
	if (!map->readonly)
		XStoreColor(map->display, map->cmap, &node->color);
	HashAdd(map->htable, HASH(*color), node);
	HashAdd(map->ltable, HASH_PIXEL(color->pixel), node);
}

static Col *allocN(Palette *map, int ncolor, Boolean *flags, XColor *colors, Pixel *list)
{
	Col	c, *node = NULL;
	int	i;

	for (i = 0; i < ncolor; i++) {
		if (flags != NULL && flags[i])
			continue;

		c.color.red   = colors[i].red   & 0xff00;
		c.color.green = colors[i].green & 0xff00;
		c.color.blue  = colors[i].blue  & 0xff00;

		if ((node = (Col *)HashFind(map->htable, HASH(c.color), &c)) == NULL) {
			addColor(map, &colors[i]);
			list[i] = colors[i].pixel;
		} else {
			/*
			**  It must have been allocated in the previous
			**   pass, or by the above.
			*/
			if (!node->used) 
				entryUnlink(map, node);
			list[i] = node->color.pixel;
		}
	}

	return node;
}

int PaletteAllocN(Palette *map, XColor *color, int ncolor, Pixel *list)
{
	Boolean	*flg = XtCalloc(sizeof(Boolean), ncolor);
	Col	c, *node;
	int	i;
	Boolean	NewMine = False;

	if (!map->isMapped) {
		for (i = 0; i < ncolor; i++) {
			unsigned int	r, g, b;

			r = (color[i].red   * map->rRange) >> 16;
			g = (color[i].green * map->gRange) >> 16;
			b = (color[i].blue  * map->bRange) >> 16;
			list[i] = (r << map->rShift)|(g << map->gShift)|(b << map->bShift);
		}
		return 0;
	}

	for (i = 0; i < ncolor; i++) {
		Col	c;

		c.color.red   = color[i].red   & 0xff00;
		c.color.green = color[i].green & 0xff00;
		c.color.blue  = color[i].blue  & 0xff00;

		if ((node = (Col *)HashFind(map->htable, HASH(c.color), &c)) != NULL) {
			flg[i] = True;
			/*
			**  Match found, if the entry hasn't been "alloced"
			**   yet, mark it so, and remove it from the "free" list.
			*/
			if (!node->used) 
				entryUnlink(map, node);
			list[i] = node->color.pixel;
			if (list[i] == map->mine)
				NewMine = True;
		} else {
			flg[i] = False;
		}
	}

	for (i = 0; i < ncolor; i++) {
		if (flg[i])
			continue;

		c.color.red   = color[i].red   & 0xff00;
		c.color.green = color[i].green & 0xff00;
		c.color.blue  = color[i].blue  & 0xff00;

		if ((node = (Col *)HashFind(map->htable, HASH(c.color), &c)) == NULL) {
			addColor(map, &color[i]);
			list[i] = color[i].pixel;
		} else {
			/*
			**  It must have been allocated in the previous
			**   pass, or by the above.
			*/
			list[i] = node->color.pixel;
		}
		if (list[i] == map->mine)
			NewMine = True;
	}

	XtFree((char *)flg);

	if (NewMine && map->ctable != NULL) {
		Col             *cptr = (Col*)map->ctable;

		for (i = 0; i < map->ncolors; i++, cptr++) {
			if (cptr->used)
				continue;
			map->mine = cptr->color.pixel;
			break;
		}
	}

	return 0;
}

Pixel PaletteAlloc(Palette *map, XColor *color)
{
	if (!map->isMapped) {
		unsigned int	r, g, b;

		r = (color->red   * map->rRange) >> 16;
		g = (color->green * map->gRange) >> 16;
		b = (color->blue  * map->bRange) >> 16;
		return (r << map->rShift)|(g << map->gShift)|(b << map->bShift);
	}

	if (map->last != NULL) {
		XColor	*lc = (XColor*)map->last;

		if (lc->red   == color->red && 
		    lc->green == color->green &&
		    lc->blue  == color->blue) 
			return color->pixel = lc->pixel;
	}

	map->last = allocN(map, 1, NULL, color, &color->pixel);

	return color->pixel;
}

/*
**  Given a Pixel value on the specified map return the
**   RGV value.
**
**   Special case "TrueColor" since it is just computed.
**
*/
XColor *PaletteLookup(Palette *map, Pixel pix)
{
	if (map->isMapped) {
		Col	col;
		Col	*c;

		col.color.pixel = pix;

		if ((c = (Col*)HashFind(map->ltable, HASH_PIXEL(pix), &col)) == NULL) {
			printf("Shouldn't happen\n");
			return NULL;
		}

		if (c->invalid) {
			HAshRemove(map->htable, HASH(c->color), c);
			XQueryColor(map->display, map->cmap, &c->color);
			c->color.red   &= 0xff00;
			c->color.green &= 0xff00;
			c->color.blue  &= 0xff00;
			HashAdd(map->htable, HASH(c->color), c);
			c->invalid = False;
		}

		return &c->color;
	} else {
		static XColor	xc;

		xc.red   = (pix >> map->rShift) & (map->rRange-1);
		xc.green = (pix >> map->gShift) & (map->gRange-1);
		xc.blue  = (pix >> map->bShift) & (map->bRange-1);

		xc.red   *= 65536 / map->rRange;
		xc.green *= 65536 / map->gRange;
		xc.blue  *= 65536 / map->bRange;

		return &xc;
	}
}


Palette *PaletteFindDpy(Display *dpy, Colormap cmap)
{
	PaletteList	*cur;

	for (cur = cmapList; cur != NULL; cur = cur->next)
		if (cur->cmap == cmap && cur->dpy == dpy)
			return cur->map;

	return NULL;
}

Palette *PaletteFind(Widget w, Colormap cmap)
{
	return PaletteFindDpy(XtDisplay(w), cmap);
}

