/* This file is part of `mogul'
 * Copyright (c) 1995 David Deaven
 *
 * In the following, "you" and "You" refer to any entity other than 
 * the Microsoft corporation and its subsidiaries.  All rights are
 * reserved by the author in connection with Microsoft and its
 * subsidiaries.  The Microsoft Network is explicity denied the right
 * to copy, reproduce, or transmit this software.
 *
 *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Author: deaven $
 * $Id: dev_x11.c,v 2.1 1996/07/11 18:41:37 deaven Exp deaven $
 */

/* MIT X11 windows driver
 * (c) 1995 David Deaven
 */

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "dmd/memory.h"
#include "dmd/dmdtimer.h"
#include "MogulP.h"

#define VECTOR_CHUNK	256

/* Since color manipulations are expensive in X, use a lookup
 * table to cache the pixels already resolved.
 */
struct colorTable {
	unsigned int n;			/* dimension in each component */
	double scale;			/* to convert Color structure */
	unsigned char *allocated;	/* allocation flags */
	Pixel *data;			/* the pixel data */
};

static struct colorTable *colorTable = NULL;

static struct colorTable *newColorTable(int n)
{
	int i, size;
	struct colorTable *ct = NewPointer(ct);

	if(n < 1) n = 1;
	if(n > 12) n = 12;

	ct->n = n;
	size = n * n * n;
	ct->scale = 0.95 * (double) (n - 1) / (double) RGB_CMAX;
	ct->allocated = NewPointerArray(ct->allocated, size);
	ct->data = NewPointerArray(ct->data, size);

	for(i=0; i<size; i++) ct->allocated[i] = 0;

	return ct;
}

static void deleteColorTable(struct colorTable *ct)
{
	if(ct) {
	  myfree(ct->allocated);
	  myfree(ct->data);
	  myfree(ct);
	}
}

static Pixel convert_color(struct x11_instance *W, Color c, Pixel fallback)
{
	unsigned int 
	  r = (double) c.r * colorTable->scale,
	  g = (double) c.g * colorTable->scale,
	  b = (double) c.b * colorTable->scale,
	  i = r + colorTable->n * (g + colorTable->n * b);

	if(!(colorTable->allocated[i]))
	{
	  XColor color;
	  double
	    fr = c.r * (65535.0 / (double) RGB_CMAX),
	    fg = c.g * (65535.0 / (double) RGB_CMAX),
	    fb = c.b * (65535.0 / (double) RGB_CMAX);
	  
	  color.red = fr;
	  color.green = fg;
	  color.blue = fb;

	  if(!XAllocColor(W->display, W->colormap, &color)) {
	    colorTable->data[i] = fallback;
	  }
	  else {
	    colorTable->data[i] = color.pixel;
	    colorTable->allocated[i] = 1;
	  }
	}

	return colorTable->data[i];
}

static void update_after_mapping(struct x11_instance *W)
{
	if(W->pixmap)
	  XFreePixmap(W->display, W->pixmap);

	W->pixmap =
	  XCreatePixmap(W->display, W->window,
	    W->width, W->height, DefaultDepthOfScreen(W->screen));

	if(W->gc == NULL) {
	  XGCValues xgcvalues;
	  xgcvalues.foreground = W->foreground;
	  xgcvalues.background = W->background;
	  xgcvalues.arc_mode = ArcChord;
	  xgcvalues.line_width = 0;

	  W->gc = XCreateGC(W->display, W->window, 
	    GCForeground | GCBackground | GCArcMode | GCLineWidth,
	    &xgcvalues);


	}
}

void ClearXInstance(struct x11_instance *W)
{
	static Color black = {0, 0, 0};
	if(W->mapped) {
	  XSetForeground(W->display, W->gc, W->background);
	  XFillRectangle(W->display, W->pixmap, W->gc,
	    0, 0, W->width, W->height);
	  XSetForeground(W->display, W->gc, BlackPixelOfScreen(W->screen));
	  W->currentColor = black;
	}
}

void ResizeXInstance(struct x11_instance *W)
{
	if(W->mapped) {
	  update_after_mapping(W);
	  ClearXInstance(W);
	}
}

void RedisplayXInstance(struct x11_instance *W)
{
	ENTER("x11: RedisplayXInstance");
	if(W->pixmap)
	  XCopyArea(W->display, W->pixmap, W->window,
	    W->gc, 0, 0, W->width, W->height, 0, 0);
	LEAVE();
}

/* Draw lines/arcs if any have been accumulated.
 */
static void flush(struct x11_instance *W)
{
	ENTER("x11: flush()");
	if(W->nvec) {
	  XDrawLines(W->display, W->pixmap, W->gc,
	    W->vec, W->nvec, CoordModeOrigin);
	  W->nvec = 0;
	}

	if(W->narc) {
	  XDrawArcs(W->display, W->pixmap, W->gc, W->arc, W->narc);
	  W->narc = 0;
	}
	LEAVE();
}

/* mogul specifies RGB color in the same manner that X11 does.
 */
static int PPcolor(Color c, MogulDeviceInstance mdi)
{
	struct x11_instance *W = mdi;

	ENTER("x11: Pcolor");
	if(c.r != W->currentColor.r ||
	   c.g != W->currentColor.g ||
	   c.b != W->currentColor.b)
	{
	  flush(W);
	  XSetForeground(W->display, W->gc,
	    convert_color(W, c, W->foreground));
	  W->currentColor = c;
	}
	LEAVE();

	return 0;
}

#if FANCY_X_SHADE
#define NFILL 16
static int FancyShade(double x, double y, double r, struct x11_instance *W)
{
	int i;
	int red = W->currentColor.r,
	    green = W->currentColor.g,
	    blue = W->currentColor.b;
	double ix, iy, z;
	Color c;
	for(i=0; i<NFILL; ++i)
	{
	  z = 0.40 + i * (0.60 / NFILL);
	  c.r = red * z;
	  c.g = green * z;
	  c.b = blue * z;
	  PPcolor(c, W);
	  ix = x + i * r * (0.12 / NFILL);
	  iy = y + i * r * (0.18 / NFILL);
	  z = ((NFILL - i + 1) * (0.77 / NFILL) + 0.23) * r;
	  XFillArc(W->display, W->pixmap, W->gc, (int) ix, (int) iy,
	    (unsigned int) z, (unsigned int) z, 0, 64 * 360 - 1);
	}

	return 0;
}
#endif

/* Add device-dependent "shading" to the circle of radius r in the
 * {u,v} plane.
 */
static int PPshade(double x[3], double r, double u[3], double v[3],
	MogulDeviceInstance mdi)
{
	int i;
	double pc[3], pr[3];
	struct x11_instance *W = mdi;

	ENTER("x11: Pshade");
	flush(W);
	for(i=0; i<3; ++i) pc[i] = x[i] + r*u[i];
	perscl(pc, pr, W->vdist);
	perscl(x, pc, W->vdist);
	r = (pc[0] - pr[0]) * (pc[0] - pr[0]) +
	    (pc[1] - pr[1]) * (pc[1] - pr[1]) +
	    (pc[2] - pr[2]) * (pc[2] - pr[2]);
	r = W->scale * sqrt(r);
	pc[0] = (pc[0] - W->xmin) * W->scale - r;
	pc[1] = W->height - ((pc[1]-W->ymin)*W->scale + r);
	r *= 2.0;
#if FANCY_X_SHADE
	FancyShade(pc[0], pc[1], r, W);
#else
	XFillArc(W->display, W->pixmap, W->gc, (int) pc[0], (int) pc[1],
	  (unsigned int) r, (unsigned int) r, 0, 64 * 360 - 1);
#endif
	LEAVE();

	return 0;
}

static int PPline(double x[3], MogulDeviceInstance mdi)
{
	double px[3];
	XPoint p;
	struct x11_instance *W = mdi;

	if(W->vdist != 0.0) {
	  perscl(x, px, W->vdist);
	  px[0] = (px[0] - W->xmin) * W->scale;
	  px[1] = (px[1] - W->ymin) * W->scale;
	}
	else {
	  px[0] = (x[0] - W->xmin) * W->scale;
	  px[1] = (x[1] - W->ymin) * W->scale;
	}
	p.x = px[0];
	p.y = W->height - px[1];

	if(W->nvec == 0 || W->cur.x != p.x || W->cur.y != p.y)
	{
	  if((W->nvec + 1) >= W->nvecmax)
	  {
	    W->nvecmax += VECTOR_CHUNK;
	    W->vec = ResizePointerArray(W->vec, W->nvecmax);
	  }
	  W->vec[(W->nvec)++] = W->cur = p;
	}

	return 0;
}

static int PPmove(double x[3], MogulDeviceInstance mdi)
{
	struct x11_instance *W = mdi;
	flush(W);
	return PPline(x, mdi);
}

static int PParc(double x[3], double r, double u[3], double v[3],
	double t0, double t1, MogulDeviceInstance mdi)
{
	struct x11_instance *W = mdi;
	ENTER("x11: Parc");

	Parc_from_lines(x, r, u, v, t0, t1, RESOLUTION / W->scale,
	  PPline, mdi);

	LEAVE();

	return 0;
}

static int PPcircle(double x[3], double r, MogulDeviceInstance mdi)
{
	struct x11_instance *W = mdi;
	int ix, iy;
	unsigned int ur;
	double px[3];
	XArc *a;
	ENTER("x11: Pcircle");

	r *= W->scale * perscl(x, px, W->vdist);
	ix = (px[0] - W->xmin) * W->scale - r;
	iy = W->height - ((px[1] - W->ymin) * W->scale + r);
	ur = 2 * r;

	if((W->narc + 1) >= W->narcmax)
	{
	  W->narcmax += VECTOR_CHUNK;
	  W->arc = ResizePointerArray(W->arc, W->narcmax);
	}
	a = W->arc + (W->narc)++;

	a->x = ix;
	a->y = iy;
	a->width = a->height = ur;
	a->angle1 = 0;
	a->angle2 = 64 * 360;

	LEAVE();

	return 0;
}

/* Draw the text string in the current color, starting
 * at the current position.  The letters should be fracheight
 * of the frame high.  The current point should be advanced.
 *
 * In X11, I still have not found a good way to draw arbitrary
 * height text. 
 */
static int PPtext(char *s, double fracheight, MogulDeviceInstance mdi)
{
	struct x11_instance *W = mdi;
	ENTER("x11: Ptext");
	XDrawString(W->display, W->pixmap, W->gc, 
	  W->cur.x, W->cur.y, s, (int) strlen(s));
	LEAVE();
	return 0;
}

/* Fill the accumulated vectors as a solid shape.
 */
static int PPclose(MogulDeviceInstance mdi)
{
	struct x11_instance *W = mdi;
	ENTER("x11: Pclose");
	if(W->nvec)
	{
	  XFillPolygon(W->display, W->pixmap, W->gc, W->vec, W->nvec,
	    Convex, CoordModeOrigin);
	  W->nvec = 0;
	}
	if(W->narc)
	{
	  XFillArcs(W->display, W->pixmap, W->gc, W->arc, W->narc);
	  W->narc = 0;
	}
	LEAVE();
	return 0;
}

static char *followsKey(char *s, char *key)
{
	char *r;
	if(s && key && (r = strstr(s, key))) return (r + strlen(key));
	return NULL;
}

/* The device info can contain a pointer to an existing instance,
 * which has the public part and the display/window information
 * filled in already.  If it doesn't, a new window will be raised
 * on the default display.
 */
static MogulDeviceInstance PPinit(char *devinfo)
{
	char *s;
	struct x11_instance *W;

	if(devinfo && (s = followsKey(devinfo, "instance=")) && sscanf(s, "%p", &W) == 1)
	{
	  W->owned = 0; /* I did not create this window */
	}
	else
	{
	  /* Contact the server and setup the shared information
	   * regarding it.
	   */
	  Window rootwindow;
	  XSetWindowAttributes attributes;
	  unsigned long attr_mask;
	  XSizeHints sizehints;

	  W = NewPointer(W);
	  W->owned = 1; /* I did create this window */
	  W->display = XOpenDisplay(NULL);
	  if(W->display == NULL)
	  {
	    fprintf(stderr, "Cannot connect to X server\n");
	    return NULL;
	  }
	  W->screen = DefaultScreenOfDisplay(W->display);
	  W->width = DEFAULT_WIDTH;
	  W->height = DEFAULT_HEIGHT;
	  W->foreground = BlackPixelOfScreen(W->screen);
	  W->background = WhitePixelOfScreen(W->screen);

	  rootwindow = RootWindowOfScreen(W->screen);

	  attributes.event_mask = ExposureMask | StructureNotifyMask;
	  attributes.border_pixel = BlackPixelOfScreen(W->screen);
	  attributes.background_pixel = WhitePixelOfScreen(W->screen);

	  attr_mask = CWEventMask | CWBackPixel | CWBorderPixel;

	  W->window = XCreateWindow(W->display, rootwindow,
	      0, 0, W->width, W->height,
	      BORDER_WIDTH, DefaultDepthOfScreen(W->screen),
	      InputOutput, DefaultVisualOfScreen(W->screen),
	      attr_mask, &attributes);

	  sizehints.min_height = 64;
	  sizehints.min_width = 64;
	  sizehints.base_height = W->height;
	  sizehints.base_width = W->width;
	  sizehints.flags = PMinSize | PBaseSize;

	  XSetWMNormalHints(W->display, W->window, &sizehints);

	  XMapWindow(W->display, W->window);

	  /* This part is tricky.  I need to have a valid device
	   * entry in the X11 instance, yet this function is 
	   * creating part of the device!  So I'll just put an
	   * acceptable device in when dev_x11 is raising the 
	   * window itself... XXX
	   */
	  {
	    extern MogulDevice dev_x11;
	    W->device = NewPointer(W->device);
	    *(W->device) = *dev_x11;
	    W->device->instance = W; /* ughh */
	  }

	}

	if(colorTable == NULL)
	{ int depth = DefaultDepthOfScreen(W->screen);
	  colorTable = newColorTable(depth == 1 ? 2 : depth > 2 ? 6 : 4);
	}

	W->colormap = DefaultColormapOfScreen(W->screen);

	W->pixmap = NULL;
	W->gc = NULL;

	W->vec = NULL;
	W->nvec = W->nvecmax = 0; /* no vectors yet */

	W->arc = NULL;
	W->narc = W->narcmax = 0; /* no arcs yet */

	W->mapped = 0; /* window-dependent maps have not been set */

	W->rendered = 0;

	return (MogulDeviceInstance) W;
}

/* We set the GC and clear the current pixmap.
 */
static int PPstart(MogulPlot p, MogulDeviceInstance mdi)
{
	struct x11_instance *W = mdi;

	ENTER("x11: Pstart");

	{ double sx = W->width / p->attr.width, sy = W->height / p->attr.height;
	  W->scale = sx < sy ? sx : sy;
	}
#ifdef DEBUG
	printf("dev_x11: screen size %dx%d camera %gx%g\n", 
	  W->width, W->height, p->attr.width, p->attr.height);
#endif

	W->vdist = p->attr.vdist;
	W->speedy = p->speedy;

	if(!(W->mapped)) {
	  W->mapped = 1;
	  update_after_mapping(W);
	}

	if(W->owned) 
	  XStoreName(W->display, W->window, "Mogul");

	if(W->font == NULL)
	  W->font = XLoadQueryFont(W->display, DEFAULT_FONTNAME);

	XSetFont(W->display, W->gc, W->font->fid);

	W->foreground = convert_color(W, p->attr.foreground, 
	  BlackPixelOfScreen(W->screen));
	W->background = convert_color(W, p->attr.background, 
	  WhitePixelOfScreen(W->screen));

	W->xmin = -0.5 * W->width / W->scale;
	W->ymin = -0.5 * W->height / W->scale;

	W->nvec = 0;
	W->narc = 0;

	ClearXInstance(W);

	LEAVE();
	return 0;
}

/* A frame is now complete.  Update the display.
 */
static int PPend(MogulPlot p, MogulDeviceInstance mdi)
{
	struct x11_instance *W = mdi;

	ENTER("x11: Pend");

	flush(W);

	XCopyArea(W->display, W->pixmap, W->window,
	  W->gc, 0, 0, W->width, W->height, 0, 0);
	XFlush(W->display);

	W->rendered = 1;

	LEAVE();

	return 0;
}

static int PPstop(MogulDeviceInstance mdi)
{
	struct x11_instance *W = mdi;

	/* Clear up resources and return.
	 */
	XFreePixmap(W->display, W->pixmap);
	myfree(W->vec);
	myfree(W->arc);
	if(W->owned) {
	  XDestroyWindow(W->display, W->window);
	  XCloseDisplay(W->display);
	  myfree(W);
	}
	return 0;
}

static struct MogulDeviceRec dev = {
  "x11",
  "MIT X-Windows X11R6",
  PPcolor,
  PParc,
  PPcircle,
  PPshade,
  PPmove,
  PPline,
  PPclose,
  PPtext,
  PPinit,
  PPstart,
  PPend,
  PPstop,
  NULL
};

MogulDevice dev_x11 = &dev;

