/*
 * 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 <X11/Xlib.h>
#include <stdlib.h>
#include <stdio.h>
#include "../configure.h"

#include "afterstep.h"
#include <X11/Xatom.h>
#include "misc.h"

#include "screen.h"
#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.
 */
static 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, Scr.screen);
	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, 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 = malloc(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 = malloc(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 = malloc(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
 ************************************************************************/
void DrawTexturedText(Display * dpy, Drawable d, XFontStruct * font,
		    int x, int y, Pixmap gradient, char *text, int chars)
{
    Pixmap mask;
    int w, h;
    GC gc;
    XGCValues gcv;

    /* make the mask pixmap */
    w = XTextWidth(font, text, chars);
    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);
    XDrawString(dpy, mask, gc, 0, font->ascent, text, chars);
    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 i;
    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;
}
