/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <X11/Intrinsic.h>
#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/CoreP.h>
#include <X11/Shell.h>
#include <X11/Xatom.h>
#include "Palette.h"

#define ABS(x) ((x)>0?(x):-(x))
#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)<(b)?(a):(b))

#define N(c) ((c)/65535.0)

struct _Palette{

	struct _Palette *next;
	Display         *display;

	Colormap        map;
	int             map_entries;
	unsigned long   tolerance;

	Boolean         is_direct;
	unsigned long   r_shift;
	unsigned long   g_shift;
	unsigned long   b_shift;
	unsigned long   r_range;
	unsigned long   g_range;
	unsigned long   b_range;

	unsigned  short mask;
};

typedef struct Color {

	struct Color *left;
	struct Color *right;
	XColor   color;
	int      count;
	Boolean  system;

} Color;

static Palette  _list          = NULL;

static Color* head = 0;

static Color* new_color(Palette palette,XColor* col)
{
	Color * c = (Color*)XtCalloc(1,sizeof(Color));
	c->color = *col;

	if(XAllocColor(palette->display,palette->map,&c->color))
		c->system = False;
	else
		printf("Cannot allocate %d %d %d\n",col->red,col->green,col->blue);

	return c;
}

#define X(a) ((a)&palette->mask)

static int coldiff(Palette palette,XColor* a,XColor* b)
{
	if( X(a->red) != X(b->red))     return X(a->red) - X(b->red);
	if( X(a->green) != X(b->green)) return X(a->green) - X(b->green);
	return  X(a->blue) - X(b->blue);
}

static Color* find_color(Palette palette,Color* from,XColor* col,Color* parent,int right)
{
	if(from) 
	{
		long diff = coldiff(palette,col,&from->color);
		if(diff == 0)
			return from;
		else if(diff < 0)
			return find_color(palette,from->left,col,from,0);
		else
			return find_color(palette,from->right,col,from,1);
	}

	from = new_color(palette,col);

	if(parent == 0) 
		head = from;
	else
		if(right)
			parent->right = from;
		else
			parent->left = from;
	return from;
}

static Color* look_up(Palette palette,XColor* col)
{
	return find_color(palette,head,col,0,0);
}

static Pixel find(Palette palette,unsigned short r,unsigned short g,unsigned short b)
{
	XColor x; Color* c;

	x.red   = r;
	x.green = g;
	x.blue  = b;

	c = look_up(palette,&x);
	
	return c->color.pixel;

}

#if 0
static void free_color(Palette palette,int entry)
{
	int n = palette->colors[entry];

	if(n < 0)
		return;

	_colors[n].count--;

	if(_colors[n].count == 0)
	{
		_next_free = 0;
		if(!_colors[n].system)
			XFreeColors(palette->display,palette->map,
			    &_colors[n].xcolor.pixel,1,0);
	}

	palette->colors[entry] = -1;

}

static void attach_color(Palette palette,int i)
{
	_colors[i].xcolor.flags = DoRed|DoGreen|DoBlue;

	if(XAllocColor(palette->display,palette->map,&_colors[i].xcolor))
		_colors[i].system = False;
	else
		printf("Cannot attach a system color !!!\n");
}

static void no_sys_colors(Palette palette)
{
	int i;
	for(i = 0; i < _color_cnt ; i++)
		if(_colors[i].system) _colors[i].system = _colors[i].count =  _next_free = 0;
}

#if 0
#define DEBUG(a,i) printf("Palette: %s %04x-%04x-%04x tol=%d, max=%d %04x-%04x-%04x (%d,%d)\n",(a),  \
	col->red,col->green,col->blue,tolerance,max,_colors[i].xcolor.red,_colors[i].xcolor.green,_colors[i].xcolor.blue,\
		_colors[i].count,_colors[i].system);
#else
#define DEBUG(a,b)
#endif

static void new_color(Palette palette,int entry, XColor *col)
{
	unsigned long max_tolerance = 0x7fffffff;
	unsigned long max = max_tolerance;
	unsigned long tolerance = palette->tolerance;
	int best = 0;
	int a,b;
	int i,j;
	int try_x_colors = 1;

	free_color(palette,entry);

	/* 1 - Look if it is already there... */

	for(i = 0; i < _color_cnt ; i++)
		if(_colors[i].count)
		{
			unsigned long d = RGBDistance(col,&_colors[i].xcolor);
			if(d == 0) {
				if(_colors[i].system) attach_color(palette,i);
				_colors[i].count++;
				palette->colors[entry] = i;
				no_sys_colors(palette);
				DEBUG("Color was in palette",i);
				return;
			}
		}

	/* try allocate */

	col->flags = DoRed|DoGreen|DoBlue;
	col->pixel = 0;

	if(XAllocColor(palette->display,palette->map,col))
	{
		int n = next_free();
		_colors[n].count       = 1;
		_colors[n].system      = False;
		_colors[n].xcolor      = *col;
		palette->colors[entry] = n;
		DEBUG("Color allocated",n);
		no_sys_colors(palette);
		return;
	}

	while(tolerance<=max_tolerance)
	{

		/* try a close match */

		for(i = 0; i < _color_cnt ; i++)
			if(_colors[i].count)
			{
				unsigned long d = RGBDistance(col,&_colors[i].xcolor);
				if(d < max) {
					max = d;
					best = i;
				}
			}

		if(max <= tolerance)
		{
			if(_colors[best].system) attach_color(palette,best);
			_colors[best].count++;
			palette->colors[entry] = best;
			DEBUG("Color matched ",best);
			no_sys_colors(palette);
			return;
		}

		/* Remap colors ... */

		a=-1;
		b=-1;
		max = max_tolerance;

		for(i = 0; i < _color_cnt-1 ; i++)
			if(_colors[i].count)
				for(j = i+1; j < _color_cnt ; j++)
					if(_colors[j].count)
						if( !_colors[a].system ||
						    !_colors[b].system ||
						    (_colors[a].system != _colors[b].system))
						{
							unsigned long d = RGBDistance(&_colors[i].xcolor,&_colors[j].xcolor);
							if(d < max) {
								max = d;
								a = i;
								b = j;
							}
						}

		/* We can remap one ....*/

		if(max <= tolerance)
		{
			Palette p = _list;

			if(_colors[a].system)
			{
				int c = a;
				a     = b;
				b     = a;
			}

			DEBUG("Remap 1",a);
			DEBUG("Remap 2",b);

			if(_colors[b].system)
				attach_color(palette,b);

			XFreeColors(palette->display,
			    palette->map,&_colors[a].xcolor.pixel,1,0);

			_colors[a].count = 0;
			_next_free = 0;

			while(p)
			{
				for(j=0;j<p->count;j++)
					if(p->colors[j] == a)
					{
						p->colors[j] = b;
						_colors[b].count++;
					}
				p = p->next;
			}

			if(XAllocColor(palette->display,palette->map,col))
			{
				_colors[a].system  = False;
				_colors[a].count   = 1;
				_colors[a].xcolor  = *col;
				palette->colors[entry] = a;
				DEBUG("Color allocated after remap",a);
				no_sys_colors(palette);
				return;
			}
		}

		if(try_x_colors)
		{
			/* Everything failed, look what was allocated by other processes */

			int cnt      = 0;
			time_t now   = time(0);
			try_x_colors = 0;

			/* Get the system colors */

			if(now - sys_color_time > 10) /* 10 sec. */
			{
				if(system_colors) XtFree((XtPointer)system_colors);
				system_colors = NULL;
			}

			if(!system_colors)
			{
				DEBUG("Get sys colors",0);
				system_colors = (XColor*)XtMalloc(palette->map_entries*
				    sizeof(XColor));

				for(i=0;i<palette->map_entries;i++)
					system_colors[i].pixel = i;

				XQueryColors(palette->display,palette->map,system_colors,
				    palette->map_entries);

				sys_color_time = now;
			}

			for(i = 0;i< palette->map_entries;i++)
			{
				Boolean ours = False;
				for(j = 0; j< _color_cnt; j++)
				{
					long d = RGBDistance(&system_colors[i],&_colors[j].xcolor);
					if(d == 0)
					{
						ours = True;
						break;
					}
				}

				/* Its not our color, add it */

				if(!ours)
				{
					/* Add system palette */
					int n = next_free();
					_colors[n].xcolor = system_colors[i];
					_colors[n].count  = 1;
					_colors[n].system = True;
					cnt++;
				}
			}

			/* Try if some system color matches... */
			if(cnt)
				for(i = 0; i < _color_cnt ; i++)
					if(_colors[i].count && _colors[i].system)
					{
						unsigned long d = RGBDistance(col,&_colors[i].xcolor);
						if(d == 0) {
							attach_color(palette,i);
							_colors[i].count++;
							palette->colors[entry] = i;
							no_sys_colors(palette);
							DEBUG("Found match in sys colors",i);
							return;
						}
					}
		}
		else tolerance *= 0x10; /* Augment the tolerence */

	}

	if(_colors[best].system) attach_color(palette,best);

	DEBUG("Bad luck",best);

	/* No luck ... */
	_colors[best].count++;
	palette->colors[entry] = best;
	no_sys_colors(palette);
}

#endif

void PaletteRGBToHSV(Palette palette,const RGBColor* rgb,HSVColor* hsv)
{
	double max = MAX(rgb->red,MAX(rgb->green,rgb->blue));
	double min = MIN(rgb->red,MIN(rgb->green,rgb->blue));
	double delta = max - min;

	hsv->value = max;
	if (max != 0.0)
		hsv->saturation = delta / max;
	else
		hsv->saturation = 0.0;

	if (hsv->saturation == 0) {
		hsv->hue = 0;
	}
	else {

		if (rgb->red == max)   hsv->hue = (rgb->green - rgb->blue) / delta;
		if (rgb->green == max) hsv->hue = 2 + (rgb->blue - rgb->red) / delta;
		if (rgb->blue == max)  hsv->hue = 4 + (rgb->red - rgb->green) / delta;

		hsv->hue *= 60; 
		while(hsv->hue < 0)   hsv->hue += 360;
		while(hsv->hue > 360) hsv->hue -= 360;
	}
}

void PaletteHSVToRGB(Palette palette,const HSVColor* hsv,RGBColor* rgb)
{
	int i;
	double   f,p,q,t;

	if (hsv->saturation == 0 && hsv->hue == 0) {
		rgb->red = rgb->green = rgb->blue = hsv->value;
	} else {
		double hue = hsv->hue;

		while(hue < 0)   hue += 360;
		while(hue > 360) hue -= 360;

		hue /= 60.0;

		i = hue;
		f = hue - i;
		p = hsv->value * (1 - hsv->saturation);
		q = hsv->value * (1 - (hsv->saturation * f));
		t = hsv->value * (1 - (hsv->saturation * (1 - f)));
		switch (i) {
		case 0:
			rgb->red = hsv->value;
			rgb->green = t;
			rgb->blue = p;
			break;
		case 1:
			rgb->red = q;
			rgb->green = hsv->value;
			rgb->blue = p;
			break;
		case 2:
			rgb->red = p;
			rgb->green = hsv->value;
			rgb->blue = t;
			break;
		case 3:
			rgb->red = p;
			rgb->green = q;
			rgb->blue = hsv->value;
			break;
		case 4:
			rgb->red = t;
			rgb->green = p;
			rgb->blue = hsv->value;
			break;
		case 5:
			rgb->red = hsv->value;
			rgb->green = p;
			rgb->blue = q;
			break;
		}
	}
}

static double value( double n1, double n2, double hue )
{
	while ( hue > 360.0 ) hue -= 360.0;
	while ( hue < 0.0 )   hue += 360.0;

	if ( hue < 60.0 )
		return  ( n1 + ((n2 - n1)*hue/60.0));
	else if ( hue < 180.0 )
		return ( n2 );
	else if ( hue < 240.0 )
		return ( n1 + ((n2 - n1)*(240.0 - hue)/60.0));
	else
		return( n1 );
}	

void PaletteHSLToRGB(Palette palette,const HSLColor* hsl,RGBColor* rgb)
{
	double m2 = ( hsl->lightness < 0.5 ) ? 
				(hsl->lightness * ( 1.0 + hsl->saturation)) : 
				(hsl->lightness + hsl->saturation - 
					(hsl->lightness * hsl->saturation));

	double m1 = (2.0 * hsl->lightness) - m2;

	if(hsl->saturation == 0)
	{
		if ( (hsl->hue + 1.0) < 1.0 )
			rgb->red = rgb->green = rgb->blue = hsl->lightness;
		else
			rgb->red = rgb->green = rgb->blue = 0;
	}
	else
	{
		rgb->red   = value( m1, m2, hsl->hue + 120.0 );
		rgb->green = value( m1, m2, hsl->hue );
		rgb->blue  = value( m1, m2, hsl->hue - 120.0 );
	}
}	

void PaletteRGBToHSL(Palette palette,const RGBColor* rgb,HSLColor* hsl)
{
	double max = MAX(rgb->red,MAX(rgb->green,rgb->blue));
	double min = MIN(rgb->red,MIN(rgb->green,rgb->blue));
	double delta = max - min;

	hsl->lightness = (max+min)/2;
	if(delta == 0)
		hsl->saturation = hsl->hue = 0;
	else {
		if(hsl->lightness < 0.5)
			hsl->saturation = delta/(max+min);
		else
			hsl->saturation = delta/(2-max-min);	

		if(rgb->red == max)   hsl->hue = 0 + (rgb->green - rgb->blue)  /delta;
		if(rgb->green == max) hsl->hue = 2 + (rgb->blue  - rgb->red)   /delta;
		if(rgb->blue == max)  hsl->hue = 4 + (rgb->red   - rgb->green) /delta;

		hsl->hue *= 60;
		while(hsl->hue < 0)   hsl->hue += 360;
		while(hsl->hue > 360) hsl->hue -= 360;
	}
}

void DisposePalette(Palette palette)
{
	Palette p = _list;
	Palette q = NULL;
	int i;

	while(p)
	{
		if(p == palette)
		{
			if(q) q->next = p->next;
			else _list = p->next;
			return;
		}
		q = p;
		p = p->next;
	}
}

Palette NewPalette(Widget w)
{
	Palette  palette = XtNew(struct _Palette);
	Display  *dpy    = XtDisplay(w);
	Screen   *screen = XtScreen(w);
	Visual   *visual = NULL;
	Colormap   rcmap = DefaultColormapOfScreen(screen);
	XColor  col;
	int cells,i,j;
	int good=0,bad=0;
	Arg   al[1];

	palette->next = _list;
	_list = palette;

	while(!XtIsShell(w)) w = XtParent(w);


	XtSetArg(al[0],XtNvisual,&visual);

	XtGetValues(w, al, 1);

	if (visual == NULL) visual = DefaultVisualOfScreen(screen);


	palette->display     = dpy;
	palette->map         = rcmap;
	palette->mask        = 0xc000;

	palette->tolerance   = 0x300;
	palette->map_entries = visual->map_entries;
	palette->is_direct   = visual->class == TrueColor;

	if(palette->is_direct)
	{
		int v;

		palette->r_shift = 0;
		palette->g_shift = 0;
		palette->b_shift = 0;
		palette->r_range = 1;
		palette->g_range = 1;
		palette->b_range = 1;

		for (v = visual->red_mask; (v & 1) == 0; v >>= 1)
			palette->r_shift++;

		for (; (v & 1) == 1; v >>= 1)
			palette->r_range <<= 1;

		for (v = visual->green_mask; (v & 1) == 0; v >>= 1)
			palette->g_shift++;

		for (; (v & 1) == 1; v >>= 1)
			palette->g_range <<= 1;

		for (v = visual->blue_mask; (v & 1) == 0; v >>= 1)
			palette->b_shift++;

		for (; (v & 1) == 1; v >>= 1)
			palette->b_range <<= 1;

		palette->r_range--;
		palette->g_range--;
		palette->b_range--;

	}

	return palette;
}

long RGBDistance(const XColor *c1,const XColor *c2)
{
	long d;
	long dr = (long)c1->red    - (long)c2->red  ;
	long dg = (long)c1->green  - (long)c2->green;
	long db = (long)c1->blue   - (long)c2->blue ;

	dr = ABS(dr);
	dg = ABS(dg);
	db = ABS(db);

	d = MAX(dr,dg);
	return MAX(d,db);
}

/*=======================================================================*/

Pixel PaletteNamedColor(Palette palette,const char *name)
{
	RGBColor r;
	PaletteNameToRGB(palette,name,&r);
	return PaletteRGBColor(palette,&r);
}

void PaletteNameToRGB(Palette palette,const char *name,RGBColor* c)
{
	XColor col;
	col.red = col.green = col.blue = 0;
	if(!XParseColor(palette->display,palette->map,name,&col))
		printf("PaletteSetNamedColor: bad name... %s",name);

	c->red   = col.red   / 65535.0;
	c->green = col.green / 65535.0;
	c->blue  = col.blue  / 65535.0;
}

Pixel PaletteHSVColor(Palette palette,const HSVColor* c)
{
	RGBColor r;
	PaletteHSVToRGB(palette,c,&r);
	return PaletteRGBColor(palette,&r);
}

Pixel PaletteHSLColor(Palette palette,const HSLColor* c)
{
	RGBColor r;
	PaletteHSLToRGB(palette,c,&r);
	return PaletteRGBColor(palette,&r);
}

Pixel PaletteRGBColor(Palette palette,const RGBColor* c)
{
	if(palette->is_direct)
	{
		unsigned int red   = c->red   * palette->r_range;
		unsigned int green = c->green * palette->g_range;
		unsigned int blue  = c->blue  * palette->b_range;

		return (red   << palette->r_shift)| 
			   (green << palette->g_shift)| 
			   (blue  << palette->b_shift);
	 }
	 else 
	 {

		unsigned short  red   = c->red   * 65535.0;
		unsigned short  green = c->green * 65535.0;
		unsigned short  blue = c->blue  * 65535.0;

		return find(palette,red,green,blue);
	}
}

