/*
 * Copyright (c) 1996 Alfredo K. Kojima (kojima@inf.ufrgs.br)
 * 
 * This module 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 of the License, 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 "../configure.h"

#include <stdlib.h>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include "../include/afterstep.h"
#include "../include/parse.h"
#include "../include/misc.h"
#include "../include/screen.h"
#include "../include/hashtable.h"

static int table_initialized = 0;
static HashTable ColorTable;
static Visual *ScreenVisual;
static int rshift, gshift, bshift;
static int Bits;

static unsigned long
getTrueColor (XColor * color)
{
  unsigned long pixel;
  int r, g, b;

  r = (rshift < 0 ? color->red << (-rshift) : color->red >> rshift);

  g = (gshift < 0 ? color->green << (-gshift) : color->green >> gshift);

  b = (bshift < 0 ? color->blue << (-bshift) : color->blue >> bshift);

  pixel = ((r & ScreenVisual->red_mask)
	   | (g & ScreenVisual->green_mask)
	   | (b & ScreenVisual->blue_mask));

  return pixel;
}


static int
get_shifts (unsigned long mask)
{
  int i = 0;

  while (mask)
    {
      mask >>= 1;
      i++;
    }
  return i - 1;
}

/*
 * Allocates a color or get's it from the color cache.
 * If the display is TrueColor, make the color by ourselves.
 */
int
MyAllocColor (Display * dpy, Colormap cmap, XColor * color)
{
  unsigned long key, r, g, b;

  if (!table_initialized)
    {
      table_initialized = 1;
      table_init (&ColorTable);
      ScreenVisual = DefaultVisual (dpy, DefaultScreen (dpy));
      if (ScreenVisual->class == TrueColor)
	{
	  rshift = 15 - get_shifts (ScreenVisual->red_mask);
	  gshift = 15 - get_shifts (ScreenVisual->green_mask);
	  bshift = 15 - get_shifts (ScreenVisual->blue_mask);
	}
      Bits = 16 - ScreenVisual->bits_per_rgb;
    }
  if (ScreenVisual->class == TrueColor)
    {
      /* make true color ourself */
      color->pixel = getTrueColor (color);
      return 1;
    }
  else
    {
      r = color->red >> Bits;
      g = color->green >> Bits;
      b = color->blue >> Bits;
      key = r * r + g * g + b * b;

      if (!table_iget (&ColorTable, key, &color->pixel))
	{
	  /* color not in cache, have to allocate this new color */
	  if (XAllocColor (dpy, cmap, color) == 0)
	    return 0;
	  /* put color in cache */
	  table_iput (&ColorTable, key, color->pixel);
	}
      return 1;
    }
}




/* Masks to apply to color components when allocating colors 
 * you may want to set them to 0xffff if your display supports 16bpp+
 */
#define RED_MASK	0xff00
#define GREEN_MASK	0xff00
#define BLUE_MASK	0xff00
/*
 *********************************************************************** 
 * Allocates colors and fill a pixel value array. Color values are 
 * truncated with RED_MASK, GREEN_MASK and BLUE_MASK
 * 
 * from,to are the primary colors to use
 * maxcols is the number of colors
 * colors is the array of pixel values
 * light,dark are the colors for the relief
 * dr,dg,db are the increment values for the colors
 * alloc_relief if colors for relief should be allocated
 * incr - 0 if gradient is light to dark, 1 if dark to light
 ***********************************************************************
 */
int
MakeColors (Display * dpy, Drawable d, int from[3], int to[3], int maxcols,
	    unsigned long *colors, unsigned long *dark,
	    unsigned long *light, int dr, int dg, int db,
	    int alloc_relief, int incr)
{
  float rv, gv, bv;
  float sr, sg, sb;
  int red, green, blue;
  XColor color;
  int i;

  sr = (float) dr / (float) maxcols;
  sg = (float) dg / (float) maxcols;
  sb = (float) db / (float) maxcols;
  rv = (float) from[0];
  gv = (float) from[1];
  bv = (float) from[2];
  /* kludge to frce color allocation in the first iteration on any case */
  color.red = (from[0] == 0) ? 0xffff : 0;
  color.green = 0;
  color.blue = 0;
  for (i = 0; i < maxcols; i++)
    {
      /* color allocation saver */
      red = ((short) rv) & RED_MASK;
      green = ((short) gv) & GREEN_MASK;
      blue = ((short) bv) & BLUE_MASK;
      if (color.red != red || color.green != green || color.blue != blue)
	{
	  color.red = red;
	  color.green = green;
	  color.blue = blue;
	  if (MyAllocColor (dpy, DefaultColormap (dpy, DefaultScreen (dpy)),
			    &color) == 0)
	    {
	      return 0;
	    }
	}
      colors[i] = color.pixel;
      rv += sr;
      gv += sg;
      bv += sb;
      rv = ((rv > 65535.0) || (rv < 0.0)) ? rv -= sr : rv;
      gv = ((gv > 65535.0) || (gv < 0.0)) ? gv -= sg : gv;
      bv = ((bv > 65535.0) || (bv < 0.0)) ? bv -= sb : bv;
    }
  /* allocate 2 colors for the bevel */
  if (alloc_relief)
    {
      if (incr)
	{
	  rv = (float) from[0] * 0.8;
	  gv = (float) from[1] * 0.8;
	  bv = (float) from[2] * 0.8;
	}
      else
	{
	  rv = (float) to[0] * 0.8;
	  gv = (float) to[1] * 0.8;
	  bv = (float) to[2] * 0.8;
	}
      color.red = (unsigned short) (rv < 0 ? 0 : rv);
      color.green = (unsigned short) (gv < 0 ? 0 : gv);
      color.blue = (unsigned short) (bv < 0 ? 0 : bv);
      if (MyAllocColor (dpy, DefaultColormap (dpy, DefaultScreen (dpy)), &color))
	*dark = color.pixel;
      else
	*dark = colors[incr ? 0 : maxcols - 1];

      if (incr)
	{
	  float avg;
	  avg = (float) (to[0] + to[1] + to[2]) / 3;
	  rv = avg + (float) (to[0] / 2);
	  gv = avg + (float) (to[1] / 2);
	  bv = avg + (float) (to[2] / 2);
	}
      else
	{
	  float avg;
	  avg = (float) (from[0] + from[1] + from[2]) / 3;
	  rv = avg + (float) (from[0] / 2);
	  gv = avg + (float) (from[1] / 2);
	  bv = avg + (float) (from[2] / 2);
	}
      color.red = (unsigned short) (rv > 65535.0 ? 65535.0 : rv);
      color.green = (unsigned short) (gv > 65535.0 ? 65535.0 : gv);
      color.blue = (unsigned short) (bv > 65535.0 ? 65535.0 : bv);
      if (MyAllocColor (dpy, DefaultColormap (dpy, DefaultScreen (dpy)),
			&color))
	*light = color.pixel;
      else
	*light = colors[incr ? maxcols - 1 : 0];
    }
  else
    {
      *light = colors[incr ? maxcols - 1 : 0];
      *dark = colors[incr ? 0 : maxcols - 1];
    }
  return 1;
}

/*
 ************************************************************************
 * 
 * Draws a texture similar to Wharf's button background
 *
 * from: r,g,b values of top left color
 * to: r,g,b values of bottom right color
 * relief: relief to add to the borders. 0=flat, >0 raised, <0 sunken
 * maxcols: # of max colors to use
 * 
 * Aborts and returns 0 on error
 ************************************************************************
 */

int
DrawDegradeRelief (Display * dpy, Drawable d, int x, int y, int w, int h,
		   int from[3], int to[3], int relief, int maxcols)
{
  int i = 0, j, k;
  int dr, dg, db;		/* delta */
  int dmax, dmax2;
  GC gc, gc2;
  XGCValues gcv;
  int px, py, pd;
  unsigned long lightcolor, darkcolor;
  int alloc_relief;
  unsigned long *colors;
  float c1, s1;

  if (w <= 0 || h <= 0)
    return 0;

  gcv.foreground = 1;
  gc = XCreateGC (dpy, d, GCForeground, &gcv);
  gc2 = XCreateGC (dpy, d, GCForeground, &gcv);

  dr = to[0] - from[0];
  dg = to[1] - from[1];
  db = to[2] - from[2];

  dmax = abs (dr);
  dmax2 = dr;
  if (dmax < abs (dg))
    {
      dmax = abs (dg);
      dmax2 = dg;
    }
  if (dmax < abs (db))
    {
      dmax = abs (db);
      dmax2 = db;
    }
  /* if colors from and to are the same, force to one color */
  if (dmax == 0)
    dmax = 1;

  if (relief != 0)
    {
      if (maxcols > 4)
	{
	  alloc_relief = 1;
	  maxcols -= 2;		/* reserve two colors for the relief */
	}
      else
	{
	  alloc_relief = 0;
	}
    }
  else
    alloc_relief = 0;

  if (dmax < maxcols)
    {
      maxcols = dmax;		/* we need less colors than we expected */
    }
  colors = (unsigned long *) safemalloc (sizeof (unsigned long) * maxcols);
  /* alloc colors */
  if (!MakeColors (dpy, d, from, to, maxcols, colors, &darkcolor, &lightcolor,
		   dr, dg, db, alloc_relief, dmax2 > 0))
    return 0;

  /* display it */
  s1 = (float) (maxcols) / ((float) w * 2.0);
  pd = relief == 0 ? 0 : 1;
  for (py = pd; py < h - pd; py++)
    {
      c1 = ((float) maxcols / 2.0) * ((float) py / (float) ((h - pd * 2) - 1));
      j = -1;
      k = pd + x;
      for (px = pd + x; px < w - pd + x; px++)
	{
	  i = (int) c1;
	  if (i >= maxcols)
	    i = maxcols - 1;
	  if (j != colors[i])
	    {
	      j = colors[i];
	      XSetForeground (dpy, gc, j);
	      XDrawLine (dpy, d, gc, k, y + py, px, y + py);
	      k = px;
	    }
	  c1 += s1;
	}
      XSetForeground (dpy, gc, colors[i]);
      XDrawLine (dpy, d, gc, k, y + py, w - pd + x, y + py);
    }
  /* draw borders */
  if (relief != 0)
    {
      if (relief > 0)
	{
	  XSetForeground (dpy, gc, lightcolor);
	  XSetForeground (dpy, gc2, darkcolor);
	}
      else
	{
	  XSetForeground (dpy, gc, darkcolor);
	  XSetForeground (dpy, gc2, lightcolor);
	}
      XDrawLine (dpy, d, gc, x, y, x, y + h - 1);
      XDrawLine (dpy, d, gc, x, y, x + w - 1, y);
      XDrawLine (dpy, d, gc2, x, y + h - 1, x + w - 1, y + h - 1);
      XDrawLine (dpy, d, gc2, x + w - 1, y, x + w - 1, y + h - 1);
    }
  XFreeGC (dpy, gc);
  XFreeGC (dpy, gc2);
  free (colors);
  return 1;
}


/*
 ************************************************************************
 * 
 * Draws a horizontal gradient
 *
 * from: r,g,b values of top color
 * to: r,g,b values of bottom color
 * relief: relief to add to the borders. 0=flat, >0 raised, <0 sunken
 * maxcols: max colors to use
 * type: gradient type. 0 for one-way, != 0 for cilindrical
 * 
 * aborts and returns 0 on error.
 ************************************************************************
 */
int
DrawHGradient (Display * dpy, Drawable d, int x, int y, int w, int h,
	       int from[3], int to[3], int relief, int maxcols,
	       int type)
{
  int i, j;
  XGCValues gcv;
  GC gc, gc2;
  int dr, dg, db;
  float s, c;
  int dmax;
  unsigned long *colors, lightcolor, darkcolor;
  int py, pd;
  int alloc_relief;

  if (w <= 1 || h <= 1)
    return 0;

  gcv.foreground = 1;
  gc = XCreateGC (dpy, d, GCForeground, &gcv);
  gc2 = XCreateGC (dpy, d, GCForeground, &gcv);

  dr = to[0] - from[0];
  dg = to[1] - from[1];
  db = to[2] - from[2];

  dmax = dr;
  if (abs (dmax) < abs (dg))
    {
      dmax = dg;
    }
  if (abs (dmax) < abs (db))
    {
      dmax = db;
    }
  if (relief != 0)
    {
      if (maxcols > 4)
	{
	  alloc_relief = 1;
	  maxcols -= 2;		/* reserve two colors for the relief */
	}
      else
	{
	  alloc_relief = 0;
	}
    }
  else
    alloc_relief = 0;

  if (type)
    {
      if (h / 2 < maxcols)
	{
	  maxcols = h / 2;	/* we need less colors than we expected */
	}
    }
  else
    {
      if (h < maxcols)
	{
	  maxcols = h;		/* we need less colors than we expected */
	}
    }
  /* alloc colors */
  colors = (unsigned long *) safemalloc (maxcols * sizeof (unsigned long));
  if (!MakeColors (dpy, d, from, to, maxcols, colors, &darkcolor, &lightcolor,
		   dr, dg, db, alloc_relief, dmax > 0))
    return 0;
  /* display it */
  if (type)
    {
      s = ((float) maxcols * 2) / (float) h;
    }
  else
    {
      s = (float) maxcols / (float) h;
    }
  pd = relief == 0 ? 0 : 1;
  c = 0;
  j = -1;
  if (type == 0)
    {				/* one-way */
      for (py = pd; py < h - pd; py++)
	{
	  i = (int) c;
	  if (i >= maxcols)
	    i = maxcols - 1;
	  if (j != i)
	    {
	      XSetForeground (dpy, gc, colors[i]);
	    }
	  XDrawLine (dpy, d, gc, x + pd, y + py, x + w - 1 - pd, y + py);
	  j = i;
	  c += s;
	}
    }
  else
    {				/* cylindrical */
      for (py = pd; py < h - pd; py++)
	{
	  i = (int) c;
	  if (i >= maxcols)
	    i = maxcols - 1;
	  if (j != i)
	    {
	      XSetForeground (dpy, gc, colors[i]);
	    }
	  XDrawLine (dpy, d, gc, x + pd, y + py, x + w - 1 - pd, y + py);
	  j = i;
	  if (py == h / 2)
	    s = -s;
	  c += s;
	}
    }
  /* draw borders */
  if (relief != 0)
    {
      if (relief > 0)
	{
	  XSetForeground (dpy, gc, lightcolor);
	  XSetForeground (dpy, gc2, darkcolor);
	}
      else
	{
	  XSetForeground (dpy, gc, darkcolor);
	  XSetForeground (dpy, gc2, lightcolor);
	}
      XDrawLine (dpy, d, gc, x, y, x, y + h - 1);
      XDrawLine (dpy, d, gc, x, y, x + w - 1, y);
      XDrawLine (dpy, d, gc2, x, y + h - 1, x + w - 1, y + h - 1);
      XDrawLine (dpy, d, gc2, x + w - 1, y, x + w - 1, y + h - 1);
    }
  XFreeGC (dpy, gc);
  XFreeGC (dpy, gc2);
  free (colors);
  return 1;
}


/*
 ************************************************************************
 * 
 * Draws a vertical gradient
 *
 * from: r,g,b values of left color
 * to: r,g,b values of right color
 * relief: relief to add to the borders. 0=flat, >0 raised, <0 sunken
 * maxcols: max colors to use
 * type: gradient type. 0 for one-way, != 0 for cilindrical
 * 
 * aborts and returns 0 on error.
 ************************************************************************
 */
int
DrawVGradient (Display * dpy, Drawable d, int x, int y, int w, int h,
	       int from[3], int to[3], int relief, int maxcols,
	       int type)
{
  int i, j;
  XGCValues gcv;
  GC gc, gc2;
  int dr, dg, db;
  float s, c;
  int dmax;
  unsigned long *colors, lightcolor, darkcolor;
  int px, pd;
  int alloc_relief;

  if (w <= 1 || h <= 1)
    return 0;

  gcv.foreground = 1;
  gc = XCreateGC (dpy, d, GCForeground, &gcv);
  gc2 = XCreateGC (dpy, d, GCForeground, &gcv);

  dr = to[0] - from[0];
  dg = to[1] - from[1];
  db = to[2] - from[2];

  dmax = dr;
  if (abs (dmax) < abs (dg))
    {
      dmax = dg;
    }
  if (abs (dmax) < abs (db))
    {
      dmax = db;
    }
  if (relief != 0)
    {
      if (maxcols > 4)
	{
	  alloc_relief = 1;
	  maxcols -= 2;		/* reserve two colors for the relief */
	}
      else
	{
	  alloc_relief = 0;
	}
    }
  else
    alloc_relief = 0;

  if (type)
    {
      if (w / 2 < maxcols)
	{
	  maxcols = w / 2;	/* we need less colors than we expected */
	}
    }
  else
    {
      if (w < maxcols)
	{
	  maxcols = w;		/* we need less colors than we expected */
	}
    }
  /* alloc colors */
  colors = (unsigned long *) safemalloc (maxcols * sizeof (unsigned long));
  if (!MakeColors (dpy, d, from, to, maxcols, colors, &darkcolor, &lightcolor,
		   dr, dg, db, alloc_relief, dmax > 0))
    return 0;
  /* display it */
  if (type)
    {
      s = ((float) maxcols * 2) / (float) w;
    }
  else
    {
      s = (float) maxcols / (float) w;
    }
  pd = relief == 0 ? 0 : 1;
  c = 0;
  j = -1;
  if (type == 0)
    {				/* one-way */
      for (px = pd; px < w - pd; px++)
	{
	  i = (int) c;
	  if (i >= maxcols)
	    i = maxcols - 1;
	  if (j != i)
	    {
	      XSetForeground (dpy, gc, colors[i]);
	    }
	  XDrawLine (dpy, d, gc, x + px, y + pd, x + px, y + h - pd - 1);
	  j = i;
	  c += s;
	}
    }
  else
    {				/* cylindrical */
      for (px = pd; px < w - pd; px++)
	{
	  i = (int) c;
	  if (i >= maxcols)
	    i = maxcols - 1;
	  if (j != i)
	    {
	      XSetForeground (dpy, gc, colors[i]);
	    }
	  XDrawLine (dpy, d, gc, x + px, y + pd, x + px, y + h - pd - 1);
	  j = i;
	  if (px == w / 2)
	    s = -s;
	  c += s;
	}
    }
  /* draw borders */
  if (relief != 0)
    {
      if (relief > 0)
	{
	  XSetForeground (dpy, gc, lightcolor);
	  XSetForeground (dpy, gc2, darkcolor);
	}
      else
	{
	  XSetForeground (dpy, gc, darkcolor);
	  XSetForeground (dpy, gc2, lightcolor);
	}
      XDrawLine (dpy, d, gc, x, y, x, y + h - 1);
      XDrawLine (dpy, d, gc, x, y, x + w - 1, y);
      XDrawLine (dpy, d, gc2, x, y + h - 1, x + w - 1, y + h - 1);
      XDrawLine (dpy, d, gc2, x + w - 1, y, x + w - 1, y + h - 1);
    }
  XFreeGC (dpy, gc);
  XFreeGC (dpy, gc2);
  free (colors);
  return 1;
}


/************************************************************************
 * 
 * Draws text with a texture
 * 
 * d - target drawable
 * font - font to draw text
 * x,y - position of text
 * gradient - texture pixmap. size must be at least as large as text
 * text - text to draw
 * chars - chars in text
 ************************************************************************/
#ifdef I18N
void
DrawTexturedText (Display * dpy, Drawable d, XFontStruct * font, XFontSet fontset, int x, int y, Pixmap gradient, char *text, int chars)
#else
void
DrawTexturedText (Display * dpy, Drawable d, XFontStruct * font, int x, int y, Pixmap gradient, char *text, int chars)
#endif
{
  Pixmap mask;
  int w, h;
  GC gc;
  XGCValues gcv;

  /* make the mask pixmap */
#ifdef I18N
  w = XmbTextEscapement (fontset, text, chars);
#else
  w = XTextWidth (font, text, chars);
#endif
  h = font->ascent + font->descent;
  mask = XCreatePixmap (dpy, DefaultRootWindow (dpy), w + 1, h + 1, 1);
  gcv.foreground = 0;
  gcv.function = GXcopy;
  gcv.font = font->fid;
  gc = XCreateGC (dpy, mask, GCFunction | GCForeground | GCFont, &gcv);
  XFillRectangle (dpy, mask, gc, 0, 0, w, h);
  XSetForeground (dpy, gc, 1);
#ifdef I18N
  XmbDrawString (dpy, mask, fontset, gc, 0, font->ascent, text, chars);
#else
  XDrawString (dpy, mask, gc, 0, font->ascent, text, chars);
#endif
  XFreeGC (dpy, gc);
  /* draw the texture */
  gcv.function = GXcopy;
  gc = XCreateGC (dpy, d, GCFunction, &gcv);
  XSetClipOrigin (dpy, gc, x, y);
  XSetClipMask (dpy, gc, mask);
  XCopyArea (dpy, gradient, d, gc, 0, 0, w, h, x, y);
  XFreeGC (dpy, gc);
  XFreePixmap (dpy, mask);
}




int
MakeShadowColors (Display * dpy, int from[3], int to[3],
		  unsigned long *dark, unsigned long *light)
{
  float rv, gv, bv;
  XColor color;
  int incr;
  int dr, dg, db, dmax;

  dr = to[0] - from[0];
  dg = to[1] - from[1];
  db = to[2] - from[2];

  dmax = dr;
  if (abs (dmax) < abs (dg))
    {
      dmax = dg;
    }
  if (abs (dmax) < abs (db))
    {
      dmax = db;
    }
  incr = (dmax > 0);
  /* allocate 2 colors for the bevel */
  if (incr)
    {
      rv = (float) from[0] * 0.8;
      gv = (float) from[1] * 0.8;
      bv = (float) from[2] * 0.8;
    }
  else
    {
      rv = (float) to[0] * 0.8;
      gv = (float) to[1] * 0.8;
      bv = (float) to[2] * 0.8;
    }
  color.red = (short) (rv < 0 ? 0 : rv);
  color.green = (short) (gv < 0 ? 0 : gv);
  color.blue = (short) (bv < 0 ? 0 : bv);
  if (MyAllocColor (dpy, DefaultColormap (dpy, DefaultScreen (dpy)), &color))
    *dark = color.pixel;
  else
    return 0;

  if (incr)
    {
      float avg;
      avg = (float) (to[0] + to[1] + to[2]) / 3;
      rv = avg + (float) (to[0] / 2);
      gv = avg + (float) (to[1] / 2);
      bv = avg + (float) (to[2] / 2);
    }
  else
    {
      float avg;
      avg = (float) (from[0] + from[1] + from[2]) / 3;
      rv = avg + (float) (from[0] / 2);
      gv = avg + (float) (from[1] / 2);
      bv = avg + (float) (from[2] / 2);
    }
  color.red = (unsigned short) (rv > 65535.0 ? 65535.0 : rv);
  color.green = (unsigned short) (gv > 65535.0 ? 65535.0 : gv);
  color.blue = (unsigned short) (bv > 65535.0 ? 65535.0 : bv);
  if (MyAllocColor (dpy, DefaultColormap (dpy, DefaultScreen (dpy)),
		    &color))
    *light = color.pixel;
  else
    return 0;
  return 1;
}
