/*
 *  Window Maker window manager
 *
 *  Copyright (c) 1997-2003 Alfredo K. Kojima
 *
 *  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 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 *  USA.
 */

#include "wconfig.h"

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

#ifdef TEXTURE_PLUGIN
# ifdef HAVE_DLFCN_H
#  include <dlfcn.h>
# endif
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <wraster.h>

#include "WindowMaker.h"
#include "wcore.h"
#include "texture.h"
#include "funcs.h"

extern WPreferences wPreferences;

static void bevelImage(RImage * image, int relief);

WTexSolid *wTextureMakeSolid(WScreen * scr, XColor * color)
{
	WTexSolid *texture;
	int gcm;
	XGCValues gcv;

	texture = wmalloc(sizeof(WTexture));

	texture->type = WTEX_SOLID;
	texture->subtype = 0;

	XAllocColor(dpy, scr->w_colormap, color);
	texture->normal = *color;
	if (color->red == 0 && color->blue == 0 && color->green == 0) {
		texture->light.red = 0xb6da;
		texture->light.green = 0xb6da;
		texture->light.blue = 0xb6da;
		texture->dim.red = 0x6185;
		texture->dim.green = 0x6185;
		texture->dim.blue = 0x6185;
	} else {
		RColor rgb;
		RHSVColor hsv, hsv2;
		int v;

		rgb.red = color->red >> 8;
		rgb.green = color->green >> 8;
		rgb.blue = color->blue >> 8;
		RRGBtoHSV(&rgb, &hsv);
		RHSVtoRGB(&hsv, &rgb);
		hsv2 = hsv;

		v = hsv.value * 16 / 10;
		hsv.value = (v > 255 ? 255 : v);
		RHSVtoRGB(&hsv, &rgb);
		texture->light.red = rgb.red << 8;
		texture->light.green = rgb.green << 8;
		texture->light.blue = rgb.blue << 8;

		hsv2.value = hsv2.value / 2;
		RHSVtoRGB(&hsv2, &rgb);
		texture->dim.red = rgb.red << 8;
		texture->dim.green = rgb.green << 8;
		texture->dim.blue = rgb.blue << 8;
	}
	texture->dark.red = 0;
	texture->dark.green = 0;
	texture->dark.blue = 0;
	XAllocColor(dpy, scr->w_colormap, &texture->light);
	XAllocColor(dpy, scr->w_colormap, &texture->dim);
	XAllocColor(dpy, scr->w_colormap, &texture->dark);

	gcm = GCForeground | GCBackground | GCGraphicsExposures;
	gcv.graphics_exposures = False;

	gcv.background = gcv.foreground = texture->light.pixel;
	texture->light_gc = XCreateGC(dpy, scr->w_win, gcm, &gcv);

	gcv.background = gcv.foreground = texture->dim.pixel;
	texture->dim_gc = XCreateGC(dpy, scr->w_win, gcm, &gcv);

	gcv.background = gcv.foreground = texture->dark.pixel;
	texture->dark_gc = XCreateGC(dpy, scr->w_win, gcm, &gcv);

	gcv.background = gcv.foreground = color->pixel;
	texture->normal_gc = XCreateGC(dpy, scr->w_win, gcm, &gcv);

	return texture;
}

static int dummyErrorHandler(Display * foo, XErrorEvent * bar)
{
#ifdef DEBUG
	wwarning("your server is buggy. Tell the author if some error related to color occurs");
#endif
	return 0;
}

void wTextureDestroy(WScreen * scr, WTexture * texture)
{
	int i;
	int count = 0;
	unsigned long colors[8];

#ifdef DEBUG
	if (texture == NULL) {
		printf("BUG: trying to free NULL texture\n");
		return;
	}
#endif

	/*
	 * some stupid servers don't like white or black being freed...
	 */
#define CANFREE(c) (c!=scr->black_pixel && c!=scr->white_pixel && c!=0)
	switch (texture->any.type) {
	case WTEX_SOLID:
		XFreeGC(dpy, texture->solid.light_gc);
		XFreeGC(dpy, texture->solid.dark_gc);
		XFreeGC(dpy, texture->solid.dim_gc);
		if (CANFREE(texture->solid.light.pixel))
			colors[count++] = texture->solid.light.pixel;
		if (CANFREE(texture->solid.dim.pixel))
			colors[count++] = texture->solid.dim.pixel;
		if (CANFREE(texture->solid.dark.pixel))
			colors[count++] = texture->solid.dark.pixel;
		break;

	case WTEX_PIXMAP:
		RReleaseImage(texture->pixmap.pixmap);
		break;

	case WTEX_MHGRADIENT:
	case WTEX_MVGRADIENT:
	case WTEX_MDGRADIENT:
		for (i = 0; texture->mgradient.colors[i] != NULL; i++) {
			wfree(texture->mgradient.colors[i]);
		}
		wfree(texture->mgradient.colors);
		break;

	case WTEX_THGRADIENT:
	case WTEX_TVGRADIENT:
	case WTEX_TDGRADIENT:
		RReleaseImage(texture->tgradient.pixmap);
		break;

#ifdef TEXTURE_PLUGIN
	case WTEX_FUNCTION:
#ifdef HAVE_DLFCN_H
		if (texture->function.handle) {
			dlclose(texture->function.handle);
		}
#endif
		for (i = 0; i < texture->function.argc; i++) {
			wfree(texture->function.argv[i]);
		}
		wfree(texture->function.argv);
		break;
#endif				/* TEXTURE_PLUGIN */
	}
	if (CANFREE(texture->any.color.pixel))
		colors[count++] = texture->any.color.pixel;
	if (count > 0) {
		XErrorHandler oldhandler;

		/* ignore error from buggy servers that don't know how
		 * to do reference counting for colors. */
		XSync(dpy, 0);
		oldhandler = XSetErrorHandler(dummyErrorHandler);
		XFreeColors(dpy, scr->w_colormap, colors, count, 0);
		XSync(dpy, 0);
		XSetErrorHandler(oldhandler);
	}
	XFreeGC(dpy, texture->any.gc);
	wfree(texture);
#undef CANFREE
}

WTexGradient *wTextureMakeGradient(WScreen * scr, int style, RColor * from, RColor * to)
{
	WTexGradient *texture;
	XGCValues gcv;

	texture = wmalloc(sizeof(WTexture));
	memset(texture, 0, sizeof(WTexture));
	texture->type = style;
	texture->subtype = 0;

	texture->color1 = *from;
	texture->color2 = *to;

	texture->normal.red = (from->red + to->red) << 7;
	texture->normal.green = (from->green + to->green) << 7;
	texture->normal.blue = (from->blue + to->blue) << 7;

	XAllocColor(dpy, scr->w_colormap, &texture->normal);
	gcv.background = gcv.foreground = texture->normal.pixel;
	gcv.graphics_exposures = False;
	texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);

	return texture;
}

WTexIGradient *wTextureMakeIGradient(WScreen * scr, int thickness1, RColor colors1[2],
				     int thickness2, RColor colors2[2])
{
	WTexIGradient *texture;
	XGCValues gcv;
	int i;

	texture = wmalloc(sizeof(WTexture));
	memset(texture, 0, sizeof(WTexture));
	texture->type = WTEX_IGRADIENT;
	for (i = 0; i < 2; i++) {
		texture->colors1[i] = colors1[i];
		texture->colors2[i] = colors2[i];
	}
	texture->thickness1 = thickness1;
	texture->thickness2 = thickness2;
	if (thickness1 >= thickness2) {
		texture->normal.red = (colors1[0].red + colors1[1].red) << 7;
		texture->normal.green = (colors1[0].green + colors1[1].green) << 7;
		texture->normal.blue = (colors1[0].blue + colors1[1].blue) << 7;
	} else {
		texture->normal.red = (colors2[0].red + colors2[1].red) << 7;
		texture->normal.green = (colors2[0].green + colors2[1].green) << 7;
		texture->normal.blue = (colors2[0].blue + colors2[1].blue) << 7;
	}
	XAllocColor(dpy, scr->w_colormap, &texture->normal);
	gcv.background = gcv.foreground = texture->normal.pixel;
	gcv.graphics_exposures = False;
	texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);

	return texture;
}

WTexMGradient *wTextureMakeMGradient(WScreen * scr, int style, RColor ** colors)
{
	WTexMGradient *texture;
	XGCValues gcv;
	int i;

	texture = wmalloc(sizeof(WTexture));
	memset(texture, 0, sizeof(WTexture));
	texture->type = style;
	texture->subtype = 0;

	i = 0;
	while (colors[i] != NULL)
		i++;
	i--;
	texture->normal.red = (colors[0]->red << 8);
	texture->normal.green = (colors[0]->green << 8);
	texture->normal.blue = (colors[0]->blue << 8);

	texture->colors = colors;

	XAllocColor(dpy, scr->w_colormap, &texture->normal);
	gcv.background = gcv.foreground = texture->normal.pixel;
	gcv.graphics_exposures = False;
	texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);

	return texture;
}

WTexPixmap *wTextureMakePixmap(WScreen * scr, int style, char *pixmap_file, XColor * color)
{
	WTexPixmap *texture;
	XGCValues gcv;
	RImage *image;
	char *file;

	file = FindImage(wPreferences.pixmap_path, pixmap_file);
	if (!file) {
		wwarning(_("image file \"%s\" used as texture could not be found."), pixmap_file);
		return NULL;
	}
	image = RLoadImage(scr->rcontext, file, 0);
	if (!image) {
		wwarning(_("could not load texture pixmap \"%s\":%s"), file, RMessageForError(RErrorCode));
		wfree(file);
		return NULL;
	}
	wfree(file);

	texture = wmalloc(sizeof(WTexture));
	memset(texture, 0, sizeof(WTexture));
	texture->type = WTEX_PIXMAP;
	texture->subtype = style;

	texture->normal = *color;

	XAllocColor(dpy, scr->w_colormap, &texture->normal);
	gcv.background = gcv.foreground = texture->normal.pixel;
	gcv.graphics_exposures = False;
	texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);

	texture->pixmap = image;

	return texture;
}

WTexTGradient *wTextureMakeTGradient(WScreen * scr, int style, RColor * from, RColor * to,
				     char *pixmap_file, int opacity)
{
	WTexTGradient *texture;
	XGCValues gcv;
	RImage *image;
	char *file;

	file = FindImage(wPreferences.pixmap_path, pixmap_file);
	if (!file) {
		wwarning(_("image file \"%s\" used as texture could not be found."), pixmap_file);
		return NULL;
	}
	image = RLoadImage(scr->rcontext, file, 0);
	if (!image) {
		wwarning(_("could not load texture pixmap \"%s\":%s"), file, RMessageForError(RErrorCode));
		wfree(file);
		return NULL;
	}
	wfree(file);

	texture = wmalloc(sizeof(WTexture));
	memset(texture, 0, sizeof(WTexture));
	texture->type = style;

	texture->opacity = opacity;

	texture->color1 = *from;
	texture->color2 = *to;

	texture->normal.red = (from->red + to->red) << 7;
	texture->normal.green = (from->green + to->green) << 7;
	texture->normal.blue = (from->blue + to->blue) << 7;

	XAllocColor(dpy, scr->w_colormap, &texture->normal);
	gcv.background = gcv.foreground = texture->normal.pixel;
	gcv.graphics_exposures = False;
	texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);

	texture->pixmap = image;

	return texture;
}

#ifdef TEXTURE_PLUGIN
WTexFunction *wTextureMakeFunction(WScreen * scr, char *lib, char *func, int argc, char **argv)
{
	XColor fallbackColor;
	XGCValues gcv;
	WTexFunction *texture;

	texture = wmalloc(sizeof(WTexture));
	memset(&fallbackColor, 0, sizeof(fallbackColor));

	texture->type = WTEX_FUNCTION;
	texture->handle = NULL;
	texture->render = 0;
	texture->argc = argc;
	texture->argv = argv;

	fallbackColor.red = 0x8000;
	fallbackColor.green = 0x8000;
	fallbackColor.blue = 0x8000;

	gcv.background = gcv.foreground = fallbackColor.pixel;
	gcv.graphics_exposures = False;
	texture->normal_gc = XCreateGC(dpy, scr->w_win, GCForeground | GCBackground | GCGraphicsExposures, &gcv);

# ifdef HAVE_DLFCN_H
	/* open the library */
	texture->handle = dlopen(lib, RTLD_LAZY);
	if (!texture->handle) {
		wwarning(_("library \"%s\" cound not be opened."), lib);
		wfree(argv);
		wfree(texture);
		return NULL;
	}

	/* find the function */
	texture->render = dlsym(texture->handle, func);
	if (!texture->render) {
		wwarning(_("function \"%s\" not found in library \"%s\""), func, lib);
		wfree(argv);
		dlclose(texture->handle);
		wfree(texture);
		return NULL;
	}
# else
	wwarning(_("function textures not supported on this system, sorry."));
# endif

	/* success! */
	return texture;
}
#endif				/* TEXTURE_PLUGIN */

RImage *wTextureRenderImage(WTexture * texture, int width, int height, int relief)
{
	RImage *image = NULL;
	RColor color1;
	int d;
	int subtype;

	switch (texture->any.type) {
	case WTEX_SOLID:
		image = RCreateImage(width, height, False);

		color1.red = texture->solid.normal.red >> 8;
		color1.green = texture->solid.normal.green >> 8;
		color1.blue = texture->solid.normal.blue >> 8;
		color1.alpha = 255;

		RClearImage(image, &color1);
		break;

	case WTEX_PIXMAP:
		if (texture->pixmap.subtype == WTP_TILE) {
			image = RMakeTiledImage(texture->pixmap.pixmap, width, height);
		} else if (texture->pixmap.subtype == WTP_CENTER) {
			color1.red = texture->pixmap.normal.red >> 8;
			color1.green = texture->pixmap.normal.green >> 8;
			color1.blue = texture->pixmap.normal.blue >> 8;
			color1.alpha = 255;
			image = RMakeCenteredImage(texture->pixmap.pixmap, width, height, &color1);
		} else {
			image = RScaleImage(texture->pixmap.pixmap, width, height);
		}
		break;

	case WTEX_IGRADIENT:
		image = RRenderInterwovenGradient(width, height,
						  texture->igradient.colors1,
						  texture->igradient.thickness1,
						  texture->igradient.colors2, texture->igradient.thickness2);
		break;

	case WTEX_HGRADIENT:
		subtype = RGRD_HORIZONTAL;
		goto render_gradient;

	case WTEX_VGRADIENT:
		subtype = RGRD_VERTICAL;
		goto render_gradient;

	case WTEX_DGRADIENT:
		subtype = RGRD_DIAGONAL;
 render_gradient:

		image = RRenderGradient(width, height, &texture->gradient.color1,
					&texture->gradient.color2, subtype);
		break;

	case WTEX_MHGRADIENT:
		subtype = RGRD_HORIZONTAL;
		goto render_mgradient;

	case WTEX_MVGRADIENT:
		subtype = RGRD_VERTICAL;
		goto render_mgradient;

	case WTEX_MDGRADIENT:
		subtype = RGRD_DIAGONAL;
 render_mgradient:
		image = RRenderMultiGradient(width, height, &(texture->mgradient.colors[1]), subtype);
		break;

	case WTEX_THGRADIENT:
		subtype = RGRD_HORIZONTAL;
		goto render_tgradient;

	case WTEX_TVGRADIENT:
		subtype = RGRD_VERTICAL;
		goto render_tgradient;

	case WTEX_TDGRADIENT:
		subtype = RGRD_DIAGONAL;
 render_tgradient:
		{
			RImage *grad;

			image = RMakeTiledImage(texture->tgradient.pixmap, width, height);
			if (!image)
				break;

			grad = RRenderGradient(width, height, &texture->tgradient.color1,
					       &texture->tgradient.color2, subtype);
			if (!grad) {
				RReleaseImage(image);
				image = NULL;
				break;
			}

			RCombineImagesWithOpaqueness(image, grad, texture->tgradient.opacity);
			RReleaseImage(grad);
		}
		break;

#ifdef TEXTURE_PLUGIN
	case WTEX_FUNCTION:
#ifdef HAVE_DLFCN_H
		if (texture->function.render) {
			image = texture->function.render(texture->function.argc, texture->function.argv,
							 width, height, relief);
		}
#endif
		if (!image) {
			RErrorCode = RERR_INTERNAL;
		}
		break;
#endif				/* TEXTURE_PLUGIN */

	default:
		puts("ERROR in wTextureRenderImage()");
		image = NULL;
		break;
	}

	if (!image) {
		RColor gray;

		wwarning(_("could not render texture: %s"), RMessageForError(RErrorCode));

		image = RCreateImage(width, height, False);
		if (image == NULL) {
			wwarning(_("could not allocate image buffer"));
			return NULL;
		}

		gray.red = 190;
		gray.green = 190;
		gray.blue = 190;
		gray.alpha = 255;
		RClearImage(image, &gray);
	}

	/* render bevel */

	switch (relief) {
	case WREL_ICON:
		d = RBEV_RAISED3;
		break;

	case WREL_RAISED:
		d = RBEV_RAISED2;
		break;

	case WREL_SUNKEN:
		d = RBEV_SUNKEN;
		break;

	case WREL_FLAT:
		d = 0;
		break;

	case WREL_MENUENTRY:
		d = -WREL_MENUENTRY;
		break;

	default:
		d = 0;
	}

	if (d > 0) {
		RBevelImage(image, d);
	} else if (d < 0) {
		bevelImage(image, -d);
	}

	return image;
}

static void bevelImage(RImage * image, int relief)
{
	int width = image->width;
	int height = image->height;
	RColor color;

	switch (relief) {
	case WREL_MENUENTRY:
		color.red = color.green = color.blue = 80;
		color.alpha = 0;
		 /**/ ROperateLine(image, RAddOperation, 1, 0, width - 2, 0, &color);
		 /**/ ROperateLine(image, RAddOperation, 0, 0, 0, height - 1, &color);

		color.red = color.green = color.blue = 40;
		color.alpha = 0;
		ROperateLine(image, RSubtractOperation, width - 1, 0, width - 1, height - 1, &color);

		 /**/ ROperateLine(image, RSubtractOperation, 1, height - 2, width - 2, height - 2, &color);

		color.red = color.green = color.blue = 0;
		color.alpha = 255;
		RDrawLine(image, 0, height - 1, width - 1, height - 1, &color);
		 /**/ break;

	}
}

void wDrawBevel(Drawable d, unsigned width, unsigned height, WTexSolid * texture, int relief)
{
	GC light, dim, dark;
	XSegment segs[4];

	if (relief == WREL_FLAT)
		return;

	light = texture->light_gc;
	dim = texture->dim_gc;
	dark = texture->dark_gc;
	switch (relief) {
	case WREL_FLAT:
		return;
	case WREL_MENUENTRY:
	case WREL_RAISED:
	case WREL_ICON:
		segs[0].x1 = 1;
		segs[0].x2 = width - 2;
		segs[0].y2 = segs[0].y1 = height - 2;
		segs[1].x1 = width - 2;
		segs[1].y1 = 1;
		segs[1].x2 = width - 2;
		segs[1].y2 = height - 2;
		XDrawSegments(dpy, d, dim, segs, 2);
		segs[0].x1 = 0;
		segs[0].x2 = width - 1;
		segs[0].y2 = segs[0].y1 = height - 1;
		segs[1].x1 = segs[1].x2 = width - 1;
		segs[1].y1 = 0;
		segs[1].y2 = height - 1;
		XDrawSegments(dpy, d, dark, segs, 2);
		segs[0].x1 = segs[0].y1 = segs[0].y2 = 0;
		segs[0].x2 = width - 2;
		segs[1].x1 = segs[1].y1 = 0;
		segs[1].x2 = 0;
		segs[1].y2 = height - 2;
		XDrawSegments(dpy, d, light, segs, 2);
		if (relief == WREL_ICON) {
			segs[0].x1 = segs[0].y1 = segs[0].y2 = 1;
			segs[0].x2 = width - 2;
			segs[1].x1 = segs[1].y1 = 1;
			segs[1].x2 = 1;
			segs[1].y2 = height - 2;
			XDrawSegments(dpy, d, light, segs, 2);
		}
		break;
#ifdef unused
	case WREL_SUNKEN:
		segs[0].x1 = 0;
		segs[0].x2 = width - 1;
		segs[0].y2 = segs[0].y1 = 0;
		segs[1].x1 = segs[1].x2 = 0;
		segs[1].y1 = 0;
		segs[1].y2 = height - 1;
		XDrawSegments(dpy, d, dark, segs, 2);

		segs[0].x1 = 0;
		segs[0].y1 = segs[0].y2 = height - 1;
		segs[0].x2 = width - 1;
		segs[1].x2 = segs[1].x1 = width - 1;
		segs[1].y1 = 1;
		segs[1].y2 = height - 1;
		XDrawSegments(dpy, d, light, segs, 2);
		break;
#endif
	}
}
