#include "Draw.h"

#ifdef PLATFORM_X11

#define Time    XTime
#define Font    XFont
#define Display XDisplay
#define Picture XPicture

#ifndef flagNOGTK
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkprivate.h>
#endif

#undef Picture
#undef Time
#undef Font
#undef Display

NAMESPACE_UPP

#define LLOG(x)     //LOG(x)
#define LTIMING(x)  //TIMING(x)

XDisplay   *Xdisplay;
int         Xscreenno;
Visual     *Xvisual;
Window      Xroot;
Screen     *Xscreen;
Colormap    Xcolormap;
int         Xheight;
int         Xwidth;
int         XheightMM;
int         XwidthMM;
int         Xdepth;
dword       Xblack;
dword       Xwhite;
int         Xconnection;
byte       *Xmapcolor;
byte       *Xunmapcolor;

dword   (*Xgetpixel)(int r, int g, int b);

void StaticExitDraw_()
{
	Draw::FreeFonts();
}

EXITBLOCK_(BLKX_DrawX11)
{
	if(Xdisplay) {
		StaticExitDraw_();
// No CloseDisplay for now...
		XCloseDisplay(Xdisplay);
		LLOG("Xdisplay closed");
		Xdisplay = NULL;
	}
	if(Xmapcolor)
		delete[] Xmapcolor;
	if(Xunmapcolor)
		delete[] Xunmapcolor;
}

void XError()
{
	Panic("X11 error !");
}

void   XError(const char *s)
{
	Panic(String("X11 error:") + s + " !");
}

static int sAcs;

bool sAllocColor(int xr, int xg, int xb)
{
	XColor ce;
	ce.red = xr;
	ce.green = xg;
	ce.blue = xb;
	ce.flags = DoRed | DoGreen | DoBlue;
	if(!XAllocColor(Xdisplay, Xcolormap, &ce)) return false;
	sAcs++;
	return sAcs < 257;
}

void sAllocColors()
{
	int r, g, b;
	XColor ce;
	for(r = 0; r < 2; r++)
		for(g = 0; g < 2; g++)
			for(b = 0; b < 2; b++)
				if(!sAllocColor(65535 * r, 65535 * g, 65535 * b)) return;
	for(r = 0; r < 3; r++)
		for(g = 0; g < 3; g++)
			for(b = 0; b < 3; b++)
				if((r == 1 || g == 1 || b == 1) &&
				   !sAllocColor((65535 * r) / 2, (65535 * g) / 2, (65535 * b) / 2)) return;
	for(r = 5; r >= 0; r--)
		for(g = 5; g >= 0; g--)
			for(b = 5; b >= 0; b--)
				if((r != 0 && r != 5 || g != 0 && g != 5 || b != 0 && b != 5) &&
				   !sAllocColor((65535 * r) / 5, (65535 * g) / 5, (65535 * b) / 5)) return;
	for(r = 1; r <= 11; r += 2)
		if(!sAllocColor((65535 * r) / 11, (65535 * r) / 11, (65535 * r) / 11)) return;
	for(int r = 255; r >= 0; r--)
		if(!sAllocColor(r << 8, r << 8, r << 8)) return;
}

dword GetPseudoColorPixel(int r, int g, int b)
{
	return Xmapcolor[r * 11 / 255 * (24 * 12) + g * 23 / 255 * 12 + b * 11 / 255];
}

static
struct Xshift {
	dword  mask;
	int    bits;
	int    shift;
	dword  Do(int c) { return (c >> bits << shift) & mask; }
}
Xred, Xgreen, Xblue;

Xshift CalcXShift(dword mask)
{
	Xshift f;
	f.mask = mask;
	f.shift = 0;
	f.bits = 0;
	while((mask & 1) == 0) {
		mask >>= 1;
		f.shift++;
	}
	while((mask & 1) == 1) {
		mask >>= 1;
		f.bits++;
	}
	f.bits = 8 - f.bits;
	if(f.bits < 0) {
		f.shift += f.bits;
		f.bits = 0;
	}
	LLOG("xshift(" << FormatIntHex(mask) << "): mask = "
		<< FormatIntHex(f.mask) << ", bits = " << f.bits << ", shift = " << f.shift);
	return f;
}

dword GetTrueColorPixel(int r, int g, int b)
{
	return Xred.Do(r) | Xgreen.Do(g) | Xblue.Do(b);
}

inline int ssq(int x) { return x * x; }

void InitX11Draw(XDisplay *display)
{
	Xdisplay = display;
	if(!Xdisplay) {
		puts(NFormat("No X11 display, errno = %d, %s", errno, strerror(errno)));
		fflush(stdout);
		XError();
	}
	int Xscreenno = DefaultScreen(Xdisplay);
	Xroot = RootWindow(Xdisplay, Xscreenno);
	Xscreen = ScreenOfDisplay(Xdisplay, Xscreenno);
	Xcolormap = DefaultColormap(Xdisplay, Xscreenno);
//	Xcolormap = (Colormap)GDK().gdk_x11_colormap_get_xcolormap(GDK().gdk_rgb_get_colormap());
	Xheight = DisplayHeight(Xdisplay, Xscreenno);
	Xwidth = DisplayWidth(Xdisplay, Xscreenno);
	XheightMM = DisplayHeightMM(Xdisplay, Xscreenno);
	XwidthMM = DisplayWidthMM(Xdisplay, Xscreenno);
	LLOG("Xwidth = " << Xwidth << ", XwidthMM = " << XwidthMM);
	LLOG("Xheight = " << Xheight << ", XheightMM = " << XheightMM);
	Xdepth = DefaultDepth(Xdisplay, Xscreenno);
	Xblack = BlackPixel(Xdisplay, 0);
	Xwhite = WhitePixel(Xdisplay, 0);
	Xconnection = XConnectionNumber(Xdisplay);
	Xvisual = DefaultVisual(Xdisplay, Xscreenno);
	Visual *v = Xvisual;
	if(v->c_class == TrueColor) {
		Xred = CalcXShift(v->red_mask);
		Xgreen = CalcXShift(v->green_mask);
		Xblue = CalcXShift(v->blue_mask);
		Xgetpixel = GetTrueColorPixel;
	}
	else {
		sAllocColors();
		dword color[256];
		int colorcount = max(1 << Xdepth, 256);
		Buffer<XColor> cs(colorcount);
		int i;
		for(i = 0; i < colorcount; i++)
			cs[i].pixel = i;
		XQueryColors(Xdisplay, Xcolormap, cs, colorcount);
		Xunmapcolor = new byte[3 * colorcount];
		for(i = 0; i < colorcount; i++)
		{
			Xunmapcolor[3 * i + 0] = cs[i].blue;
			Xunmapcolor[3 * i + 1] = cs[i].green;
			Xunmapcolor[3 * i + 2] = cs[i].red;
		}
		byte *cm = Xmapcolor = new byte[12 * 24 * 12];
		for(int r = 0; r < 12; r++)
			for(int g = 0; g < 24; g++)
				for(int b = 0; b < 12; b++) {
					int mind = INT_MAX;
					int mini;
					for(int i = 0; i < colorcount; i++) {
						int d = ssq(r * 255 / 11 - (cs[i].red >> 8)) +
						        ssq(g * 255 / 23 - (cs[i].green >> 8)) +
						        ssq(b * 255 / 11 - (cs[i].blue >> 8));
						if(d < mind) {
							mini = i;
							mind = d;
						}
					}
					*cm++ = mini;
				}
		Xgetpixel = GetPseudoColorPixel;
	}
//	XFree(v);

	Draw::SetStdFont(ScreenSans(12));
}

void InitX11Draw(const char *dispname)
{
#ifdef flagNOGTK
	if(!dispname || !*dispname) {
		int f = Environment().Find("DISPLAY");
		dispname = (f >= 0 ? ~Environment()[f] : ":0.0");
	}
	InitX11Draw(XOpenDisplay(dispname));
#else
	const Vector<String>& cmd = CommandLine();
	char **argv = (char**) MemoryAllocPermanent(sizeof(char *) * cmd.GetCount());
	for(int i = 0; i < cmd.GetCount(); i++)
	    argv[i] = PermanentCopy(cmd[i]);
	int argc = cmd.GetCount();
	gtk_init (&argc, &argv);
	GtkWidget *w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
	GdkDisplay *d = gtk_widget_get_display(w);
	gdk_x11_display_get_xdisplay(d);
	InitX11Draw(gdk_x11_display_get_xdisplay(d));
	gtk_widget_destroy(w);
#endif
/*
#ifndef flagNOGTK
	if(GTK() && GDK() && GOBJ()) {
	}
	else
#endif
	{
		if(!dispname || !*dispname) {
			int f = Environment().Find("DISPLAY");
			dispname = (f >= 0 ? ~Environment()[f] : ":0.0");
		}
		InitX11Draw(XOpenDisplay(dispname));
	}
*/
}

#ifdef PLATFORM_XFT
void SetClip(GC gc, XftDraw *xftdraw, const Vector<Rect>& cl)
#else
void SetClip(GC gc, const Vector<Rect>& cl)
#endif
{
	DrawLock __;
	LTIMING("SetClip");
	Buffer<XRectangle> xr(cl.GetCount());
	LLOG("SetClip");
	for(int i = 0; i < cl.GetCount(); i++) {
		XRectangle& r = xr[i];
		const Rect& cr = cl[i];
		LLOG("[" << i << "] = " << cr);
		r.x = cr.left;
		r.y = cr.top;
		r.width = cr.Width();
		r.height = cr.Height();
	}
	XSetClipRectangles(Xdisplay, gc, 0, 0, xr, cl.GetCount(), Unsorted);
#ifdef PLATFORM_XFT
	LLOG("XftDrawSetClipRectangles, # = " << cl.GetCount() << ", xftdraw = " << FormatIntHex(xftdraw));
	XftDrawSetClipRectangles(xftdraw, 0, 0, xr, cl.GetCount());
	LLOG("//XftDrawSetClipRectangles");
#endif
}

void Draw::CloneClip()
{
	if(cloff.GetCount() > 1 && cloff.Top().clipi == cloff[cloff.GetCount() - 2].clipi) {
		const Vector<Rect>& cl = clip.Top();
		cloff.Top().clipi = clip.GetCount();
		clip.Add() <<= cl;
	}
}

void Draw::SetForeground(Color color)
{
	DrawLock __;
	LTIMING("SetForeground");
	if(IsDrawing()) return;
	int p = GetXPixel(color.GetR(), color.GetG(), color.GetB());
	if(p == foreground) return;
	LTIMING("XSetForeground");
	LLOG("XSetForeground " << color);
	foreground = p;
	XSetForeground(Xdisplay, gc, foreground);
}

void Draw::SetClip() {
	DrawLock __;
	if(IsDrawing() || dw == Xroot) return;
	LTIMING("SetClip");
#ifdef PLATFORM_XFT
	UPP::SetClip(gc, xftdraw, clip.Top());
#else
	UPP::SetClip(gc, clip.Top());
#endif
}

void Draw::SetLineStyle(int width)
{
	DrawLock __;
	if(IsDrawing()) return;
	if(width == linewidth) return;
	linewidth = width;
	if(IsNull(width))
		width = 1;
	if(width < PEN_SOLID) {
		static const char dash[] = { 18, 6 };
		static const char dot[] = { 3, 3 };
		static const char dashdot[] = { 9, 6, 3, 6 };
		static const char dashdotdot[] = { 9, 3, 3, 3, 3, 3 };
		static struct {
			const char *dash;
			int   len;
		} ds[] = {
			dash, __countof(dash),
			dot, __countof(dot),
			dashdot, __countof(dashdot),
			dashdotdot, __countof(dashdotdot)
		};
		int i = -(width - PEN_DASH);
		ASSERT(i >= 0 && i < 4);
		XSetDashes(Xdisplay, gc, 0, ds[i].dash, ds[i].len);
	}
	XSetLineAttributes(Xdisplay, gc, max(width, 1),
	                   width < PEN_SOLID ? LineOnOffDash : LineSolid, CapRound, JoinRound);
}

void Draw::Init()
{
	DrawLock __;
	pagePixels = Size(Xwidth, Xheight);
	pageMMs = Size(XwidthMM, XheightMM);
	inchPixels = 254 * pagePixels / pageMMs / 10;
	sheetPixels = pagePixels;
	pageOffset = Point(0, 0);
	InitFonts();
	cloff.Clear();
	clip.Clear();
	foreground = linewidth = Null;
	device = 0;
	device = 0;
	pixels = true;
	printer = aborted = backdraw = is_mono = false;
}

void Draw::Init(const Vector<Rect>& _clip, Point _offset)
{
	DrawLock __;
	Init();
	clip.Add() <<= _clip;
	offset.Add(_offset);
	actual_offset = _offset;
	Cloff& f = cloff.Add();
	f.offseti = 0;
	f.clipi = 0;
	SetClip();
}

Draw::Draw()
{
	DrawLock __;
	dw = None;
	gc = None;
	actual_offset = Point(0, 0);
	Init();
}

#ifdef PLATFORM_XFT
Draw::Draw(Drawable _dw, GC _gc, XftDraw *_xftdraw, const Vector<Rect>& _clip)
#else
Draw::Draw(Drawable _dw, GC _gc, const Vector<Rect>& _clip)
#endif
{
	LLOG("Draw");
	dw = _dw;
	gc = _gc;
#ifdef PLATFORM_XFT
	xftdraw = _xftdraw;
#endif
	Init(_clip);
}

Draw::~Draw()
{
}

void BackDraw::Create(Draw& w, int cx, int cy)
{
	DrawLock __;
	LLOG("Creating BackDraw " << cx << "x" << cy);
	Destroy();
	size.cx = cx;
	size.cy = cy;
	dw = XCreatePixmap(Xdisplay, w.GetDrawable(), max(cx, 1), max(cy, 1), Xdepth);
	gc = XCreateGC(Xdisplay, dw, 0, 0);
#ifdef PLATFORM_XFT
	xftdraw = XftDrawCreate(Xdisplay, (Drawable) dw,
	                        DefaultVisual(Xdisplay, Xscreenno), Xcolormap);
#endif
	Vector<Rect> clip;
	clip.Add(RectC(0, 0, cx, cy));
	Init(clip, Point(0, 0));
	backdraw = true;
}

void BackDraw::Put(Draw& w, int x, int y)
{
	DrawLock __;
	LLOG("Putting BackDraw");
	ASSERT(dw != None);
	XCopyArea(Xdisplay, dw, w.GetDrawable(), w.GetGC(), 0, 0, size.cx, size.cy,
	          x + w.GetOffset().x, y + w.GetOffset().y);
}

void BackDraw::Destroy()
{
	DrawLock __;
	if(dw != None) {
	#ifdef PLATFORM_XFT
		XftDrawDestroy(xftdraw);
	#endif
		XFreePixmap(Xdisplay, dw);
		XFreeGC(Xdisplay, gc);
	}
}

NilDraw::NilDraw()
{
	DrawLock __;
	dw = Xroot;
	gc = XCreateGC(Xdisplay, Xroot, 0, 0);
	pixels = false;
	Init(Vector<Rect>());
}

NilDraw::~NilDraw()
{
	DrawLock __;
	XFreeGC(Xdisplay, gc);
}

Draw& ScreenInfo() { return Single<NilDraw>(); }

END_UPP_NAMESPACE

#endif
