#include "evas_common.h"
#include "evas_engine.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>

typedef struct _Convert_Pal_Priv Convert_Pal_Priv;

struct _Convert_Pal_Priv
{
   Display *disp;
   Colormap cmap;
   Visual  *vis;
};

typedef DATA8 * (*X_Func_Alloc_Colors) (Display *d, Colormap cmap, Visual *v);

static X_Func_Alloc_Colors x_software_x11_color_alloc[PAL_MODE_LAST + 1];
static int                 x_software_x11_color_count[PAL_MODE_LAST + 1];
static Evas_List          *palettes = NULL;

static DATA8 * x_software_x11_color_alloc_rgb(int nr, int ng, int nb, Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_gray(int ng, Display *d, Colormap cmap, Visual *v);

static DATA8 * x_software_x11_color_alloc_rgb_332(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_rgb_666(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_rgb_232(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_rgb_222(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_rgb_221(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_rgb_121(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_rgb_111(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_gray_256(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_gray_64(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_gray_16(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_gray_4(Display *d, Colormap cmap, Visual *v);
static DATA8 * x_software_x11_color_alloc_mono(Display *d, Colormap cmap, Visual *v);

static DATA8 *
x_software_x11_color_alloc_rgb(int nr, int ng, int nb, Display *d, Colormap cmap, Visual *v)
{
   int r, g, b, i;
   DATA8 *color_lut;
   int sig_mask = 0;

   for (i = 0; i < v->bits_per_rgb; i++) sig_mask |= (0x1 << i);
   sig_mask <<= (16 - v->bits_per_rgb);
   i = 0;
   color_lut = malloc((nr) * (ng) * (nb));
   if (!color_lut) return NULL;
   for (r = 0; r < (nr); r++)
     {
	for (g = 0; g < (ng); g++)
	  {
	     for (b = 0; b < (nb); b++)
	       {
		  XColor xcl;
		  XColor xcl_in;
		  int val;
		  Status ret;

                  val = (int)((((double)r) / ((nr) - 1)) * 65535);
		  xcl.red = (unsigned short)(val);
		  val = (int)((((double)g) / ((ng) - 1)) * 65535);
		  xcl.green = (unsigned short)(val);
		  val = (int)((((double)b) / ((nb) - 1)) * 65535);
		  xcl.blue = (unsigned short)(val);
		  xcl_in = xcl;
		  ret = XAllocColor(d, cmap, &xcl);
		  if ((ret == Success) ||
		      ((xcl_in.red & sig_mask) != (xcl.red & sig_mask)) ||
		      ((xcl_in.green & sig_mask) != (xcl.green & sig_mask)) ||
		      ((xcl_in.blue & sig_mask) != (xcl.blue & sig_mask)))
		    {
		       unsigned long pixels[256];
		       int j;

		       if (i > 0)
			 {
			    for(j = 0; j < i; j++)
			      pixels[j] = (unsigned long) color_lut[j];
			    XFreeColors(d, cmap, pixels, i, 0);
			 }
		       free(color_lut);
		       return NULL;
		    }
		  color_lut[i] = xcl.pixel;
		  i++;
	       }
	  }
     }
   return color_lut;
}

static DATA8 *
x_software_x11_color_alloc_gray(int ng, Display *d, Colormap cmap, Visual *v)
{
   int g, i;
   DATA8 *color_lut;
   int sig_mask = 0;

   for (i = 0; i < v->bits_per_rgb; i++) sig_mask |= (0x1 << i);
   sig_mask <<= (16 - v->bits_per_rgb);
   i = 0;
   color_lut = malloc(ng);
   if (!color_lut) return NULL;
   for (g = 0; g < (ng); g++)
     {
	XColor xcl;
	XColor xcl_in;
	int val;
	Status ret;
	
	val = (int)((((double)g) / ((ng) - 1)) * 65535);
	xcl.red = (unsigned short)(val);
	xcl.green = (unsigned short)(val);
	xcl.blue = (unsigned short)(val);
	xcl_in = xcl;
	ret = XAllocColor(d, cmap, &xcl);
	if ((ret == Success) ||
	    ((xcl_in.red & sig_mask) != (xcl.red & sig_mask)) ||
	    ((xcl_in.green & sig_mask) != (xcl.green & sig_mask)) ||
	    ((xcl_in.blue & sig_mask) != (xcl.blue & sig_mask)))
	  {
	     unsigned long pixels[256];
	     int j;
	     
	     if (i > 0)
	       {
		  for(j = 0; j < i; j++)
		    pixels[j] = (unsigned long) color_lut[j];
		  XFreeColors(d, cmap, pixels, i, 0);
	       }
	     free(color_lut);
	     return NULL;
	  }
	color_lut[i] = xcl.pixel;
	i++;
     }
   return color_lut;
}

static DATA8 *
x_software_x11_color_alloc_rgb_332(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_rgb(8, 8, 4, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_rgb_666(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_rgb(6, 6, 6, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_rgb_232(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_rgb(4, 8, 4, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_rgb_222(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_rgb(4, 4, 4, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_rgb_221(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_rgb(4, 4, 2, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_rgb_121(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_rgb(2, 4, 2, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_rgb_111(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_rgb(2, 2, 2, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_gray_256(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_gray(256, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_gray_64(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_gray(64, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_gray_16(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_gray(32, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_gray_4(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_gray(16, d, cmap, v);
}

static DATA8 *
x_software_x11_color_alloc_mono(Display *d, Colormap cmap, Visual *v)
{
   return x_software_x11_color_alloc_gray(2, d, cmap, v);
}

void
x_software_x11_color_init(void)
{
   static int initialised = 0;
   
   if (initialised) return;
   x_software_x11_color_alloc[PAL_MODE_NONE]    = NULL;
   x_software_x11_color_count[PAL_MODE_NONE]    = 0;
   
   x_software_x11_color_alloc[PAL_MODE_MONO]    = x_software_x11_color_alloc_mono;
   x_software_x11_color_count[PAL_MODE_MONO]    = 2;
   
   x_software_x11_color_alloc[PAL_MODE_GRAY4]   = x_software_x11_color_alloc_gray_4;
   x_software_x11_color_count[PAL_MODE_GRAY4]   = 4;
   
   x_software_x11_color_alloc[PAL_MODE_GRAY16]  = x_software_x11_color_alloc_gray_16;
   x_software_x11_color_count[PAL_MODE_GRAY16]  = 16;
   
   x_software_x11_color_alloc[PAL_MODE_GRAY64]  = x_software_x11_color_alloc_gray_64;
   x_software_x11_color_count[PAL_MODE_GRAY64]  = 64;
   
   x_software_x11_color_alloc[PAL_MODE_GRAY256] = x_software_x11_color_alloc_gray_256;
   x_software_x11_color_count[PAL_MODE_GRAY256] = 256;
   
   x_software_x11_color_alloc[PAL_MODE_RGB111]  = x_software_x11_color_alloc_rgb_111;
   x_software_x11_color_count[PAL_MODE_RGB111]  = 2 * 2 * 2;
   
   x_software_x11_color_alloc[PAL_MODE_RGB121]  = x_software_x11_color_alloc_rgb_121;
   x_software_x11_color_count[PAL_MODE_RGB121]  = 2 * 4 * 2;
   
   x_software_x11_color_alloc[PAL_MODE_RGB221]  = x_software_x11_color_alloc_rgb_221;
   x_software_x11_color_count[PAL_MODE_RGB221]  = 4 * 4 * 2;
   
   x_software_x11_color_alloc[PAL_MODE_RGB222]  = x_software_x11_color_alloc_rgb_222;
   x_software_x11_color_count[PAL_MODE_RGB222]  = 4 * 4 * 4;
   
   x_software_x11_color_alloc[PAL_MODE_RGB232]  = x_software_x11_color_alloc_rgb_232;
   x_software_x11_color_count[PAL_MODE_RGB232]  = 4 * 8 * 4;
   
   x_software_x11_color_alloc[PAL_MODE_RGB666]  = x_software_x11_color_alloc_rgb_666;
   x_software_x11_color_count[PAL_MODE_RGB666]  = 6 * 6 * 6;
   
   x_software_x11_color_alloc[PAL_MODE_RGB332]  = x_software_x11_color_alloc_rgb_332;
   x_software_x11_color_count[PAL_MODE_RGB332]  = 8 * 8 * 4;
   
   x_software_x11_color_alloc[PAL_MODE_LAST]    = NULL;
   x_software_x11_color_count[PAL_MODE_LAST]    = 0;
   initialised = 1;
}

Convert_Pal *
evas_software_x11_x_software_x11_color_allocate(Display *disp, Colormap cmap, Visual *vis, Convert_Pal_Mode colors)
{
   Convert_Pal_Priv *palpriv;
   Convert_Pal      *pal;
   Convert_Pal_Mode  c;
   Evas_List        *l;

   for (l = palettes; l; l = l->next)
     {
	pal = l->data;
	palpriv = pal->data;
	if ((disp == palpriv->disp) &&
	    (vis == palpriv->vis) &&
	    (cmap == palpriv->cmap))
	  {
	     pal->references++;
	     return pal;
	  }	    
     }
   pal = calloc(1, sizeof(struct _Convert_Pal));
   if (!pal) return NULL;
   for (c = colors; c > PAL_MODE_NONE; c--)
     {
	if (x_software_x11_color_alloc[c])
	  {
	     pal->lookup = (x_software_x11_color_alloc[c])(disp, cmap, vis);
	     if (pal->lookup) break;
	  }
     }
   pal->references = 1;
   pal->colors = c;
   pal->count = x_software_x11_color_count[c];
   palpriv = calloc(1, sizeof(Convert_Pal_Priv));
   pal->data = palpriv;
   if (!palpriv)
     {
	if (pal->lookup) free(pal->lookup);
	free(pal);
	return NULL;
     }
   palpriv->disp = disp;
   palpriv->vis = vis;
   palpriv->cmap = cmap;
   if (pal->colors == PAL_MODE_NONE)
     {
	if (pal->lookup) free(pal->lookup);
	free(pal);
	return NULL;
     }
   palettes = evas_list_append(palettes, pal);
   return pal;
}

void
evas_software_x11_x_software_x11_color_deallocate(Display *disp, Colormap cmap, Visual *vis, 
				Convert_Pal *pal)
{
   unsigned long pixels[256];
   int j;
   
   pal->references--;
   if (pal->references > 0) return;
   if (pal->lookup) 
     {	
	for(j = 0; j < pal->count; j++) 
	  pixels[j] = (unsigned long) pal->lookup[j];
	XFreeColors(disp, cmap, pixels, pal->count, 0);
	free(pal->lookup);
     }
   free(pal->data);
   palettes = evas_list_remove(palettes, pal);
   free(pal);
}
