#include "CtrlCore.h"

#ifdef GUI_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;
bool        Xpalette;

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

EXITBLOCK
{
	if(Xdisplay) {
		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;
	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();
	}
	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 {
		Xpalette = true;
		sAllocColors();
		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 = 0;
					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);

	Font::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
	MemoryIgnoreLeaksBlock __;
	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
}

void SetClip(GC gc, XftDraw *xftdraw, const Vector<Rect>& cl)
{
	GuiLock __;
	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);
	LLOG("XftDrawSetClipRectangles, # = " << cl.GetCount() << ", xftdraw = " << FormatIntHex(xftdraw));
	XftDrawSetClipRectangles(xftdraw, 0, 0, xr, cl.GetCount());
	LLOG("//XftDrawSetClipRectangles");
}

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

void SystemDraw::SetForeground(Color color)
{
	GuiLock __;
	LTIMING("SetForeground");
	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 SystemDraw::SetClip() {
	GuiLock __;
	drawingclip = Rect(-(INT_MAX >> 1), -(INT_MAX >> 1), +(INT_MAX >> 1), +(INT_MAX >> 1));
	if(!clip.IsEmpty()) {
		const Vector<Rect>& topclip = clip.Top();
		drawingclip = Rect(0, 0, 0, 0);
		if(!topclip.IsEmpty()) {
			drawingclip = topclip[0];
			for(int i = 1; i < topclip.GetCount(); i++)
				drawingclip |= topclip[i];
		}
		drawingclip -= actual_offset;
	}
	if(dw == Xroot) return;
	LTIMING("SetClip");
	UPP::SetClip(gc, xftdraw, clip.Top());
}

void SystemDraw::SetLineStyle(int width)
{
	GuiLock __;
	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 SystemDraw::Init()
{
	GuiLock __;
	pageSize = Size(Xwidth, Xheight);
	cloff.Clear();
	clip.Clear();
	drawingclip = Rect(-(INT_MAX >> 1), -(INT_MAX >> 1), +(INT_MAX >> 1), +(INT_MAX >> 1));
	foreground = linewidth = Null;
}

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

dword SystemDraw::GetInfo() const
{
	return 0;
}

Size SystemDraw::GetPageSize() const
{
	return pageSize;
}

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

Size SystemDraw::GetNativeDpi() const
{
	return Size(96, 96);
}

void SystemDraw::BeginNative() {}
void SystemDraw::EndNative() {}

int SystemDraw::GetCloffLevel() const
{
	return cloff.GetCount();
}

Rect SystemDraw::GetClip() const
{
	LLOG("Draw::GetClipOp; #clip = " << clip.GetCount() << ", #cloff = " << cloff.GetCount()
		<< ", clipi = " << cloff.Top().clipi);
	const Vector<Rect>& cl = clip[cloff.Top().clipi];
	Rect box(0, 0, 0, 0);
	if(!cl.GetCount()) return box;
	box = cl[0];
	LLOG("cl[0] = " << box);
	for(int i = 1; i < cl.GetCount(); i++) {
		LLOG("cl[" << i << "] = " << cl[i]);
		box |= cl[i];
	}
	LLOG("out box = " << box << ", actual offset = " << actual_offset);
	return box - actual_offset;
}

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

void BackDraw::Create(SystemDraw& w, int cx, int cy)
{
	GuiLock __;
	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);
	xftdraw = XftDrawCreate(Xdisplay, (Drawable) dw,
	                        DefaultVisual(Xdisplay, Xscreenno), Xcolormap);
	Vector<Rect> clip;
	clip.Add(RectC(0, 0, cx, cy));
	Init(clip, Point(0, 0));
}

void BackDraw::Put(SystemDraw& w, int x, int y)
{
	GuiLock __;
	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()
{
	GuiLock __;
	if(dw != None) {
		XftDrawDestroy(xftdraw);
		XFreePixmap(Xdisplay, dw);
		XFreeGC(Xdisplay, gc);
	}
}

bool ScreenInPaletteMode()
{
	return Xpalette;
}

Size GetScreenSize()
{
	return Size(Xwidth, Xheight);
}

END_UPP_NAMESPACE

#endif
