#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "config.h"
#ifdef X11_DRIVER
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include "aalib.h"
#include "aaint.h"
#include "aaxint.h"
__AA_CONST struct aa_driver X11_d;
#define C2 0x68
#define C1 0xB2
#define dr (d->pixmapmode?d->pi:d->wi)
static void X_flush(aa_context * c);

static int font_error;
static int
mygetpixel (XImage * image, int pos, int y)
{
  int width = image->width;
  int i;
  int sum = font_error;
  int start = (pos * width + 4) / 8;
  int end = ((pos + 1) * width + 4) / 8;
  if (start == end)
    {
      if (start == image->width - 1)
	start--;
      else
	end++;
    }
  for (i = start; i < end; i++)
    sum += XGetPixel (image, i, y) != 0;
  if (sum <= (end - start) / 2)
    {
      font_error = sum;
      return 0;
    }
  else
    font_error = -(end - start - sum);
  return 1;
}
static void
X_AllocColors (struct xdriverdata * d)
{
  static XColor c;
  d->black = d->attr.border_pixel = d->attr.background_pixel = d->invertedbold =
    BlackPixel (d->dp, d->screen);
  d->bold = d->invertedblack = WhitePixel (d->dp, d->screen);

  c.red = C1 * 256;
  c.green = C1 * 256;
  c.blue = C1 * 256;
  if (!XAllocColor (d->dp, d->cmap, &c))
     d->normal = d->bold;
  else
     d->normal = c.pixel;
  c.red = 65536 - c.red;
  c.green = 65536 - c.green;
  c.blue = 65536 - c.blue;
  if (!XAllocColor (d->dp, d->cmap, &c))
     d->invertednormal = d->invertedbold, d->normal = d->bold;
  else
     d->invertednormal = c.pixel;

  c.red = C2 * 256;
  c.green = C2 * 256;
  c.blue = C2 * 256;
  if (d->bold == d->dim && !XAllocColor (d->dp, d->cmap, &c))
     d->dim = d->normal;
  else
     d->dim = c.pixel;
  c.red = 65536 - c.red;
  c.green = 65536 - c.green;
  c.blue = 65536 - c.blue;
  if (!XAllocColor (d->dp, d->cmap, &c))
     d->inverteddim = d->invertednormal, d->dim = d->normal;
  else
     d->inverteddim = c.pixel;

  c.red = 0;
  c.green = 0;
  c.blue = 65535UL;
  if (!XAllocColor (d->dp, d->cmap, &c))
     d->special = d->black;
  else
     d->special = c.pixel;
  c.red = 65535UL/2;
  c.green = 65535UL/2;
  c.blue = 65535UL;
  if (!XAllocColor (d->dp, d->cmap, &c))
     d->invertedspecial = d->invertedblack;
  else
     d->invertedspecial = c.pixel;
}
static void
X_setinversionmode (int inverted, struct xdriverdata *d)
{
  d->inverted = inverted;
  if (d->specialGC)
    XFreeGC (d->dp, d->specialGC);
  if (d->normalGC)
    XFreeGC (d->dp, d->normalGC);
  if (d->boldGC)
    XFreeGC (d->dp, d->boldGC);
  if (d->dimGC)
    XFreeGC (d->dp, d->dimGC);
  d->specialGC = XCreateGC (d->dp, d->wi, 0L, NULL);
  XSetForeground (d->dp, d->specialGC, inverted ? d->invertedspecial : d->special);
  XSetFont (d->dp, d->specialGC, d->font);
  d->normalGC = XCreateGC (d->dp, d->wi, 0L, NULL);
  XSetForeground (d->dp, d->normalGC, inverted ? d->invertednormal : d->normal);
  XSetBackground(d->dp, d->normalGC, inverted ? d->invertedblack : d->black);
  XSetFont (d->dp, d->normalGC, d->font);
  d->boldGC = XCreateGC (d->dp, d->wi, 0L, NULL);
  XSetForeground (d->dp, d->boldGC, inverted ? d->invertedbold : d->bold);
  XSetBackground(d->dp, d->boldGC, inverted ? d->invertedblack : d->black);
  XSetFont (d->dp, d->boldGC, d->font);
  d->dimGC = XCreateGC (d->dp, d->wi, 0L, NULL);
  XSetForeground (d->dp, d->dimGC, inverted ? d->inverteddim : d->dim);
  XSetBackground(d->dp, d->dimGC, inverted ? d->invertedblack : d->black);
  XSetFont (d->dp, d->dimGC, d->font);
  d->blackGC = XCreateGC (d->dp, d->wi, 0L, NULL);
  XSetForeground (d->dp, d->blackGC, inverted ? d->invertedblack : d->black);
  XSetBackground(d->dp, d->blackGC, inverted ? d->invertedblack : d->black);
  d->currGC = d->normalGC;

  if (!d->pixmapmode)
    XSetWindowBackground (d->dp, d->wi, inverted ? d->invertedblack : d->black);
  else
    XFillRectangle(d->dp, d->pi, d->blackGC, 0, 0, d->pixelwidth, d->pixelheight);
  XClearWindow (d->dp, d->wi);
  if (d->previoust != NULL)
      free(d->previoust), free(d->previousa);
  d->previoust=NULL;
  d->previousa=NULL;
}

static int X_init(__AA_CONST struct aa_hardware_params *p, __AA_CONST void *none,struct aa_hardware_params *dest, void **driverdata)
{
    const char *font = "8x13bold";
    static int registered;
    static aa_font aafont;
    __AA_CONST static struct aa_hardware_params def=
    {&aa_fontX13B, AA_DIM_MASK | AA_REVERSE_MASK | AA_NORMAL_MASK | AA_BOLD_MASK | AA_BOLDFONT_MASK | AA_EXTENDED,
     0, 0,
     0, 0,
     80, 32,
     0, 0};
    struct xdriverdata *d;
    *dest=def;
    *driverdata=d=calloc(1,sizeof(*d));
    d->previoust=NULL;
    d->previousa=NULL;
    d->cvisible=1;
    d->width=80;
    d->height=32;
    if ((d->dp = XOpenDisplay(NULL)) == NULL)
	return 0;
    d->screen = DefaultScreen(d->dp);
    if (getenv ("AAFont"))
      font = getenv ("AAFont");
    d->font = XLoadFont(d->dp, font);
    if (!d->font) {
	XCloseDisplay(d->dp);
	return 0;
    }
    d->font_s = XQueryFont(d->dp, d->font);
    if (!d->font_s) {
	XCloseDisplay(d->dp);
	return 0;
    }
    d->fontheight = d->font_s->max_bounds.ascent + d->font_s->max_bounds.descent;
    d->fontwidth = d->font_s->max_bounds.rbearing - d->font_s->min_bounds.lbearing;
    d->realfontwidth = d->font_s->max_bounds.width;
    d->cmap = DefaultColormap(d->dp, d->screen);
    /*c.flags=DoRed | DoGreen | DoBlue; */

    X_AllocColors (d);
    if (d->bold == d->normal)
      dest->supported &= ~AA_BOLD_MASK;
    if (d->dim == d->normal)
      dest->supported &= ~AA_DIM_MASK;

    d->attr.event_mask = ExposureMask;
    d->attr.override_redirect = False;
    if (p->width)
	d->width = p->width;
    if (p->height)
	d->height = p->height;
    if (p->maxwidth && d->width > p->maxwidth)
	d->width = p->maxwidth;
    if (p->minwidth && d->width < p->minwidth)
	d->width = p->minwidth;
    if (p->maxheight && d->height > p->maxheight)
	d->height = p->maxheight;
    if (p->minheight && d->height < p->minheight)
	d->height = p->minheight;
    d->wi = XCreateWindow(d->dp, RootWindow(d->dp, d->screen), 0, 0, d->width * d->realfontwidth, d->height * d->fontheight, 0, DefaultDepth(d->dp, d->screen), InputOutput, DefaultVisual(d->dp, d->screen), CWBackPixel | CWBorderPixel | CWEventMask, &d->attr);
    if (!registered) {
	d->pi = XCreatePixmap(d->dp, d->wi, d->fontwidth, d->fontheight * 256, 1);
	if (d->pi) {
	    int i;
	    unsigned char c;
	    unsigned char *data;
	    XImage *image;
	    registered = 1;
	    d->specialGC = XCreateGC(d->dp, d->pi, 0L, NULL);
	    XSetForeground(d->dp, d->specialGC, 0);
	    XSetBackground(d->dp, d->specialGC, 0);
	    XFillRectangle(d->dp, d->pi, d->specialGC, 0, 0, d->fontwidth, 256 * d->fontheight);
	    XSetForeground(d->dp, d->specialGC, 1);
	    XSetFont(d->dp, d->specialGC, d->font);
	    for (i = 0; i < 256; i++) {
		c = i;
		XDrawString(d->dp, d->pi, d->specialGC, 0, (i + 1) * d->fontheight - d->font_s->descent, (char *)&c, 1);
	    }
	    image = XGetImage(d->dp, d->pi, 0, 0, d->fontwidth, 256 * d->fontheight, 1, XYPixmap);
	    if (image != NULL) {
		data = malloc(256 * d->fontheight);
		for (i = 0; i < 256; i++) {
		    int y;
		    font_error = 0;
		    for (y = 0; y < d->fontheight; y++) {
			int o;
			o = ((mygetpixel(image, 0, i * d->fontheight + y) != 0) << 7) +
			    ((mygetpixel(image, 1, i * d->fontheight + y) != 0) << 6) +
			    ((mygetpixel(image, 2, i * d->fontheight + y) != 0) << 5) +
			    ((mygetpixel(image, 3, i * d->fontheight + y) != 0) << 4) +
			    ((mygetpixel(image, 4, i * d->fontheight + y) != 0) << 3) +
			    ((mygetpixel(image, 5, i * d->fontheight + y) != 0) << 2) +
			    ((mygetpixel(image, 6, i * d->fontheight + y) != 0) << 1) +
			    ((mygetpixel(image, 7, i * d->fontheight + y) != 0) << 0);
			data[i * d->fontheight + y] = o;
		    }
		}
		aafont.name = "Font used by X server";
		aafont.shortname = "current";
		aafont.height = d->fontheight;
		aafont.data = data;
		aa_registerfont(&aafont);
		dest->font = &aafont;
	    }
	}
    }
    XStoreName(d->dp, d->wi, "aa for X");
    XMapWindow(d->dp, d->wi);
    X_setinversionmode (getenv ("AAInverted") != NULL, d);
    d->pixelwidth = -1;
    d->pixelheight = -1;
    XSync(d->dp, 0);
    aa_recommendlowkbd("X11");
    return 1;
}
int __aa_X_getsize(struct aa_context *c,struct xdriverdata *d)
{
    unsigned int px, py;
    int tmp;
    Window wtmp;
    XSync(d->dp, 0);
    XGetGeometry(d->dp, d->wi, &wtmp, &tmp, &tmp, &px, &py, (unsigned int *) &tmp, (unsigned int *) &tmp);
    tmp = 0;
    if (px != d->pixelwidth || py != d->pixelheight)
	tmp = 1;
    d->pixelwidth = px;
    d->pixelheight = py;
    if (tmp) {
	if (d->pixmapmode)
	    XFreePixmap(d->dp, d->pi);
	if (!getenv("AABlink"))
	  d->pi = XCreatePixmap(d->dp, d->wi, d->pixelwidth, d->pixelheight, DefaultDepth(d->dp, d->screen));
	else
	  d->pi = BadAlloc;
	if (d->pi == BadAlloc) {
	    d->pixmapmode = 0;
	    XSetWindowBackground(d->dp, d->wi, d->inverted ? d->invertedblack : d->black);
	} else {
	    d->pixmapmode = 1;
	    XFillRectangle(d->dp, d->pi, d->blackGC, 0, 0, d->pixelwidth, d->pixelheight);
	    XSetWindowBackgroundPixmap(d->dp, d->wi, d->pi);
	}
	c->driverparams.mmwidth = DisplayWidthMM(d->dp, d->screen) * d->pixelwidth / DisplayWidth(d->dp, d->screen);
	c->driverparams.mmheight = DisplayHeightMM(d->dp, d->screen) * d->pixelheight / DisplayHeight(d->dp, d->screen);
	if (d->previoust != NULL)
	    free(d->previoust), free(d->previousa);
	d->previoust=NULL;
	d->previousa=NULL;
	X_flush(c);
	XFlush(d->dp);
    }
    XSync(d->dp, 0);
    return (tmp);
}
static void X_uninit(aa_context * c)
{
    struct xdriverdata *d=c->driverdata;
    if (d->previoust != NULL)
	free(d->previoust), free(d->previousa);
    if (d->pixmapmode)
	XFreePixmap(d->dp, d->pi);
    XCloseDisplay(d->dp);
}
static void X_getsize(aa_context * c, int *width1, int *height1)
{
    struct xdriverdata *d=c->driverdata;
    __aa_X_getsize(c,d);
    *width1 = d->width = d->pixelwidth / d->realfontwidth;
    *height1 = d->height = d->pixelheight / d->fontheight;
}
static void X_setattr(struct xdriverdata *d,int attr)
{
    switch (attr) {
    case 0:
    case 4:
	d->currGC = d->normalGC;
	break;
    case 1:
	d->currGC = d->dimGC;
	break;
    case 2:
	d->currGC = d->boldGC;
	break;
    case 3:
	d->currGC = d->blackGC;
	break;
    }
}
/*quite complex but fast drawing routing for X */
#define NATT 5
#define texty(l,a,x) _texty[((l)*NATT+(a))*d->width+(x)]
#define rectangles(a,x) _rectangles[(a)*d->height*d->width+(x)]
static XTextItem *_texty;
static int (*nitem)[NATT];
static int (*startitem)[NATT];
static XRectangle *_rectangles;
static int nrectangles[4];
static int drawed;
static int area;
static void alloctables(struct xdriverdata *d)
{
    _texty = malloc(sizeof(XTextItem) * d->width * NATT * d->height);
    nitem = calloc(sizeof(*nitem) * d->height,1);
    startitem = calloc(sizeof(*startitem) * d->height,1);
    _rectangles = malloc(sizeof(*_rectangles) * d->width * d->height * 4);
}
static void freetables(void)
{
    free(_texty);
    free(nitem);
    free(startitem);
    free(_rectangles);
}

static void MyDrawString(struct xdriverdata *d,int attr, int x, int y, unsigned char *c, int i)
{
    XTextItem *it;
    XRectangle *rect;
    int n, a;
    switch (attr) {
    case AA_NORMAL:
    case AA_DIM:
    case AA_BOLD:
    case AA_BOLDFONT:
    default:
	n = 0;
	break;
    case AA_REVERSE:
	n = 1;
	break;
    case AA_SPECIAL:
	n = 2;
	break;
    }
    switch (attr) {
    default:
    case AA_SPECIAL:
    case AA_NORMAL:
	a = 0;
	break;
    case AA_DIM:
	a = 1;
	break;
    case AA_BOLD:
	a = 2;
	break;
    case AA_REVERSE:
	a = 3;
	break;
    case AA_BOLDFONT:
	a = 4;
	break;
    }
    it = &texty(y, a, nitem[y][a]);
    it->delta = x * d->realfontwidth - startitem[y][a];
    if (!it->delta && x) {
	it--;
	it->nchars += i;
    } else {
	nitem[y][a]++;
	it->chars = (char *)c;
	it->nchars = i;
	it->font = d->font;
	drawed = 1;
    }
    startitem[y][a] = (x + i) * d->realfontwidth;
    rect = &rectangles(n, nrectangles[n]);
    rect->x = x * d->realfontwidth;
    rect->y = (y) * d->fontheight + 1;
    rect->width = i * d->realfontwidth;
    if (nrectangles[n] && (rect - 1)->y == rect->y &&
	(rect - 1)->x + (rect - 1)->width == rect->x)
	nrectangles[n]--, (--rect)->width += i * d->realfontwidth;
    rect->height = d->fontheight;
    nrectangles[n]++;
    rect = &rectangles(n, nrectangles[3]);
    rect->x = x * d->realfontwidth;
    rect->y = (y) * d->fontheight + 1;
    rect->width = i * d->realfontwidth;
    if (nrectangles[3] && (rect - 1)->y == rect->y &&
	(rect - 1)->x + (rect - 1)->width == rect->x)
	nrectangles[3]--, (--rect)->width += i * d->realfontwidth;
    rect->height = d->fontheight;
    nrectangles[3]++;
    area += i;
}
__AA_CONST static int Black[] =
{0, 0, 0, 0, 1, 1};


static void X_flush(aa_context * c)
{
    struct xdriverdata *d=c->driverdata;
    int x, y, attr;
    int xs = 0, ys = 0;
    int l, same;
    int s = 0;
    int pos;
    attr = AA_NORMAL;
    alloctables(d);
    drawed = 0;
    area = 0;
    if (aa_imgwidth(c) != d->imgwidth || aa_imgheight(c) != d->imgheight)
      {
	if (d->previoust != NULL)
	    free(d->previoust), free(d->previousa);
	d->previoust=NULL;
	d->previousa=NULL;
        d->imgwidth = aa_imgwidth(c);
        d->imgheight =aa_imgheight(c);
	if (!d->pixmapmode) {
	    XSetWindowBackground(d->dp, d->wi, d->inverted ? d->invertedblack : d->black);
	} else {
	    XFillRectangle(d->dp, d->pi, d->blackGC, 0, 0, d->pixelwidth, d->pixelheight);
	    XSetWindowBackgroundPixmap(d->dp, d->wi, d->pi);
	    XClearWindow(d->dp, d->wi);
	}
      }
    nrectangles[0] = 0;
    nrectangles[1] = 0;
    nrectangles[2] = 0;
    nrectangles[3] = 0;
    if (d->previoust == NULL) {
	d->previoust = malloc(d->width * d->height);
	d->previousa = calloc(d->width * d->height, 1);
	memset(d->previoust, ' ', d->width * d->height);
    }
    for (y = 0; y < aa_scrheight(c); y++) {
	s = l = 0;
	xs = 0;
	ys = y;
	for (x = 0; x < aa_scrwidth(c); x++) {
	    pos = x + y * aa_scrwidth(c);
	    if (s > 5 || (c->attrbuffer[pos] != attr && (c->textbuffer[pos] != ' ' || Black[c->attrbuffer[pos]] || Black[attr]))) {
		if (l - s)
		    MyDrawString(d,attr, xs, ys,
			&c->textbuffer[xs + ys * aa_scrwidth(c)], l - s);
		attr = c->attrbuffer[pos];
		s = l = 0;
		xs = x;
		ys = y;
	    }
	    if ((d->previoust[pos] == c->textbuffer[pos] && d->previousa[pos] == c->attrbuffer[pos]) || (!Black[attr] && d->previoust[pos] == ' ' && c->textbuffer[pos] == ' ' && !Black[d->previousa[pos]]))
		same = 1;
	    else
		same = 0;
	    if (xs == x && same)
		xs++;
	    else {
		if (same)
		    s++;
		else
		    s = 0;
		l++;
	    }
	}
	if (l - s)
	    MyDrawString(d,attr, xs, ys,
			 &c->textbuffer[xs + ys * aa_scrwidth(c)], l - s);
    }
    if (drawed) {
	memcpy(d->previousa, c->attrbuffer, d->width * d->height);
	memcpy(d->previoust, c->textbuffer, d->width * d->height);
	if (nrectangles[0])
	    XFillRectangles(d->dp, dr, d->blackGC, &rectangles(0, 0), nrectangles[0]);
	if (nrectangles[1])
	    XFillRectangles(d->dp, dr, d->normalGC, &rectangles(1, 0), nrectangles[1]);
	if (nrectangles[2])
	    XFillRectangles(d->dp, dr, d->specialGC, &rectangles(2, 0), nrectangles[2]);
	if (d->cvisible)
	    XDrawLine(d->dp, dr, d->normalGC, d->Xpos * d->realfontwidth, (d->Ypos + 1) * d->fontheight - 1, (d->Xpos + 1) * d->realfontwidth - 1, (d->Ypos + 1) * d->fontheight - 1);

	for (y = 0; y < d->height; y++) {
	    for (x = 0; x < NATT; x++) {
		if (nitem[y][x]) {
		    X_setattr(d,x);
		    XDrawText(d->dp, dr, d->currGC, 0, (y + 1) * d->fontheight - d->font_s->descent, &texty(y, x, 0), nitem[y][x]);
		    if (x == 4)
			XDrawText(d->dp, dr, d->currGC, 1, (y + 1) * d->fontheight - d->font_s->descent, &texty(y, x, 0), nitem[y][x]);
		}
	    }
	}
	if (d->pixmapmode) {
	    if (nrectangles[3] && area < d->width*d->height/2 && nrectangles[3] < 5)
	      {
		int i;
	        /*fprintf (stderr, "%i %i\n",nrectangles[3], area);*/
		for (i = 0; i < nrectangles[3]; i++)
		  XClearArea (d->dp, d->wi, rectangles(3, i).x, rectangles(3,i).y,
			      rectangles(3,i).width, rectangles(3,i).height, 0);
	      }
	    else
	      XClearWindow(d->dp, d->wi);
	}
	/*if(!d->pixmapmode) */
	XSync(d->dp, 0);
    }
    freetables();
}
void __aa_X_redraw(aa_context *c)
{
    struct xdriverdata *d=c->driverdata;
    if (d->pixmapmode && d->previoust != NULL) {
	    XFlush(d->dp);
	    return;
    }
    if (d->previoust != NULL)
	free(d->previoust), free(d->previousa);
    d->previoust=NULL;
    d->previousa=NULL;
    X_flush(c);
    XFlush(d->dp);
}
static void X_gotoxy(aa_context * c, int x, int y)
{
    struct xdriverdata *d=c->driverdata;
    if (d->Xpos != x || d->Ypos != y) {
	if (d->previoust != NULL)
	    d->previoust[d->Ypos * d->width + d->Xpos] = 255;
	d->Xpos = x;
	d->Ypos = y;
	X_flush(c);
    }
}
static void X_cursor(aa_context * c, int mode)
{
    struct xdriverdata *d=c->driverdata;
    d->cvisible = mode;
}

__AA_CONST struct aa_driver X11_d =
{
    "X11", "X11 driver 1.1",
    X_init,
    X_uninit,
    X_getsize,
    NULL,
    NULL,
    X_gotoxy,
    X_flush,
    X_cursor
};
#endif
