#include "Draw.h"

NAMESPACE_UPP

#ifdef PLATFORM_XFT

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

struct XFTFontFaceInfo {
	String      name;
	bool        fixed:1;
	bool        scaleable:1;
	bool        compose:1;

	XFTFontFaceInfo()
	{
		fixed = scaleable = false;
	}
};

ArrayMap<String, XFTFontFaceInfo>& XFTFontFace()
{
	static ArrayMap<String, XFTFontFaceInfo> x;
	return x;
}

FontInfo::Data::Data()
{
	refcount = 1;
	for(int i = 0; i < 64; i++)
		base[i] = NULL;
	xftfont = NULL;
}

FontInfo::Data::~Data()
{
	DrawLock __;
	if(xftfont0 && xftfont0 != xftfont)
		XftFontClose(Xdisplay, xftfont0);
	if(xftfont)
		XftFontClose(Xdisplay, xftfont);
	for(int i = 0; i < 64; i++)
		if(base[i]) delete[] base[i];
}

void FontInfo::Data::GetMetrics(CharMetrics *t, int from, int count)
{
	DrawLock __;
	LTIMING("GetMetrics");
	LLOG("GetMetrics " << font << " " << from << ", " << count);
	if(xftfont) {
		for(int i = 0; i < count; i++) {
			LTIMING("XftTextExtents16");
			wchar h = from + i;
			XGlyphInfo info;
			XftTextExtents16(Xdisplay, xftfont0, &h, 1, &info);
			t[i].width = info.xOff;
			t[i].lspc = -info.x;
			t[i].rspc = info.xOff - info.width + info.x;
		}
	}
}

const char *basic_fonts[] = {
	"sans-serif",
	"serif",
	"sans-serif",
	"monospace",
	"serif",
	"sans-serif",
	"monospace",
};

static int sCheckComposed(const char *face)
{
	XftFont *xftfont = XftFontOpen(Xdisplay, Xscreenno,
	                               XFT_FAMILY, XftTypeString, (char *)face,
	                               XFT_PIXEL_SIZE, XftTypeInteger, 20,
	                               (void *)0);
	if(xftfont == NULL )
		return -1;
	int n = 0;
	for(int c = 0; c < 128; c++)
		if(!XftCharExists(Xdisplay, xftfont, c + 256))
			n++;
	XftFontClose(Xdisplay, xftfont);
	return n > 10;
}

void Draw::InitPlatformFonts()
{
	for(int i = 0; i < __countof(basic_fonts); i++) {
		XFTFontFaceInfo& f = XFTFontFace().Add(basic_fonts[i]);
		f.name = basic_fonts[i];
		f.scaleable = true;
		f.fixed = i == 3 || i == 6;
		f.compose = sCheckComposed(basic_fonts[i]);
	}
	FcFontSet *fs = XftListFonts(Xdisplay, Xscreenno, (void *)0, XFT_FAMILY, XFT_SPACING,
	                             XFT_SCALABLE, (void *)0);
	for(int i = 0; i < fs->nfont; i++) {
		FcChar8 *family = NULL;
		if(FcPatternGetString(fs->fonts[i], FC_FAMILY, 0, &family) == 0 && family) {
			int comp = sCheckComposed((char *)family);
			if(comp >= 0) {
				XFTFontFaceInfo& f = XFTFontFace().GetAdd((char *)family);
				int spacing;
				if(FcPatternGetInteger(fs->fonts[i], FC_SPACING, 0, &spacing) == 0 && spacing == XFT_MONO)
					f.fixed = true;
				FcBool scaleable;
				if(FcPatternGetBool(fs->fonts[i], FC_SCALABLE, 0, &scaleable) == 0 && scaleable)
					f.scaleable = true;
				f.compose = comp;
			}
		}
	}
	FcFontSetDestroy(fs);
}

int    Font::GetFaceCount()
{
	if(!Draw::sFini) Draw::InitFonts();
	return XFTFontFace().GetCount();
}

String Font::GetFaceName(int index)
{
	if(!Draw::sFini) Draw::InitFonts();
	return index >= 0 && index < XFTFontFace().GetCount() ? XFTFontFace().GetKey(index)
	                                                      : Null;
}

dword Font::GetFaceInfo(int index) {
	if(!Draw::sFini) Draw::InitFonts();
	dword w = 0;
	if(index >= 0 && index < XFTFontFace().GetCount()) {
		XFTFontFaceInfo& fi = XFTFontFace()[index];
		if(fi.fixed)
			w |= FIXEDPITCH;
		if(fi.scaleable)
			w |= SCALEABLE;
		if(fi.compose)
			w |= COMPOSED;
	}
	return w;
}

int    gtk_antialias = -1;
int    gtk_hinting = -1;
String gtk_hintstyle;
String gtk_rgba;

XftFont *Draw::CreateXftFont(Font font, int angle)
{
	LTIMING("CreateXftFont");
	XftFont *xftfont;
	double sina, cosa;
	int hg = abs(font.GetHeight());
	if(hg == 0) hg = 10;
	int i = font.GetFace();
	if(i < 0 || i >= XFTFontFace().GetCount())
		i = 0;
	const char *face = i < 7 ? basic_fonts[i] : ~XFTFontFace().GetKey(i);
	FcPattern *p = FcPatternCreate();
	FcPatternAddString(p, FC_FAMILY, (FcChar8*)face);
	FcPatternAddInteger(p, FC_SLANT, font.IsItalic() ? 110 : 0);
	FcPatternAddInteger(p, FC_PIXEL_SIZE, hg);
	FcPatternAddInteger(p, FC_WEIGHT, font.IsBold() ? 200 : 100);
	FcPatternAddBool(p, FC_MINSPACE, 1);
	if(angle) {
		FcMatrix mx;
		SinCos(angle, sina, cosa);
		mx.xx = cosa;
		mx.xy = -sina;
		mx.yx = sina;
		mx.yy = cosa;
		FcPatternAddMatrix(p, FC_MATRIX, &mx);
	}
	FcResult result;
	FcPattern *m = XftFontMatch(Xdisplay, Xscreenno, p, &result);
	if(font.IsNonAntiAliased() || gtk_antialias >= 0) {
		FcPatternDel(m, FC_ANTIALIAS);
		FcPatternAddBool(m, FC_ANTIALIAS,
		                 font.IsNonAntiAliased() ? FcFalse : gtk_antialias ? FcTrue : FcFalse);
	}
	if(gtk_hinting >= 0) {
		FcPatternDel(m, FC_HINTING);
		FcPatternAddBool(m, FC_HINTING, gtk_hinting);
	}
	const char *hs[] = { "hintnone", "hintslight", "hintmedium", "hintfull" };
	for(int i = 0; i < 4; i++)
		if(gtk_hintstyle == hs[i]) {
#ifndef PLATFORM_SOLARIS
			FcPatternDel(m, FC_HINT_STYLE);
			FcPatternAddInteger(m, FC_HINT_STYLE, i);
#else
			FcPatternDel(m, FC_STYLE);
			FcPatternAddInteger(m, FC_STYLE, i);
#endif
		}
	const char *rgba[] = { "_", "rgb", "bgr", "vrgb", "vbgr" };
	for(int i = 0; i < __countof(rgba); i++)
		if(gtk_rgba == rgba[i]) {
			FcPatternDel(m, FC_RGBA);
			FcPatternAddInteger(m, FC_RGBA, i);
		}
	xftfont = XftFontOpenPattern(Xdisplay, m);
	FcPatternDestroy(p);
	return xftfont;
}

FontInfo Draw::Acquire(Font font, int angle, int device)
{
	DrawLock __;
	LTIMING("Acquire");
	if(IsNull(font))
		font = StdFont();
	if(font.GetFace() == 0)
		font.Face(AStdFont.GetFace());
	if(font.GetHeight() == 0)
		font.Height(AStdFont.GetHeight());
	FontInfo::Data *f, *fh;
	f = fh = GetFontHash((font.GetHashValue() ^ angle ^ (device << 9)) % (int)FONTHASH);
	LLOG("Acquire " << font << " device: " << device);
	for(;;) {
		f = f->GetNext(HASH);
		if(f == fh) break;
		if(f->font == font && f->angle == angle && f->device == device)
		{
			LLOG("Reusing " << f->font);
			if(f->InList(LRU)) {
				f->Unlink(LRU);
				FontCached--;
				LLOG("Removing from cache " << f->font << " cached:" << FontCached);
			}
			f->refcount++;
			return f;
		}
	}
	LLOG("New " << font);
	LTIMING("Acquire New");
	f = fh->InsertNext(HASH);
	f->font = font;
	f->angle = angle;
	f->device = device;
	int hg = abs(font.GetHeight());
	if(hg == 0) hg = 10;
	f->xftfont0 = f->xftfont = CreateXftFont(font, angle);
	if(angle)
		f->xftfont0 = CreateXftFont(font, 0);
	f->filename = NULL;
	f->ascent = (int16)f->xftfont0->ascent;
	f->descent = (int16)f->xftfont0->descent;
	f->height = f->ascent + f->descent;
	f->lineheight = (int16)f->xftfont0->height;
	f->external = 0;
	f->internal = 0;
	f->overhang = 0;
	f->maxwidth = (int16)f->xftfont0->max_advance_width;
	f->avewidth = f->maxwidth;
	f->default_char = '?';
	f->fixedpitch = font.GetFaceInfo() & Font::FIXEDPITCH;
	f->underline_thickness = max(hg / 20, 1);
	f->underline_position = max(hg / 15, int(f->descent > 0));
	if(angle) {
		SinCos(angle, f->sina, f->cosa);
		f->offset.cx = fround(f->ascent * f->sina);
		f->offset.cy = fround(f->ascent * f->cosa);
	}
	FontInfo fi = FontInfo(f);
	fi.GetPage(0);
	return fi;
}

String FontInfo::GetFileName() const
{
	if(IsNull(ptr->filename)) {
		char *fn = NULL;
		XftPatternGetString(ptr->xftfont->pattern, XFT_FILE, 0, &fn);
		if(fn)
			ptr->filename = fn;
	}
	return ptr->filename;
}

void Draw::DrawTextOp(int x, int y, int angle, const wchar *text, Font font,
                      Color ink, int n, const int *dx) {
	LTIMING("DrawText");
	LLOG("DrawText " << ToUtf8(WString(text, n)) << " color:" << ink << " font:" << font);
	//TODO - X11 seems to crash when displaying too long strings (?)
	int ox = x + actual_offset.x;
	int oy = y + actual_offset.y;
	SetForeground(ink);
	SetFont(font, angle);
	const FontInfo::Data *fd = lastFont.ptr;
	XftColor c;
	c.color.red = ink.GetR() << 8;
	c.color.green = ink.GetG() << 8;
	c.color.blue = ink.GetB() << 8;
	c.color.alpha = 0xffff;
	c.pixel = GetXPixel(ink.GetR(), ink.GetG(), ink.GetB());
	if(angle) {
		int xpos = 0;
		for(int i = 0; i < n; i++) {
			wchar h = text[i];
			XftDrawString16(xftdraw, &c, fd->xftfont,
			                int(ox + xpos * fd->cosa + fd->offset.cx),
			                int(oy - xpos * fd->sina + fd->offset.cy),
			                (FcChar16 *)&h, 1);
			xpos += dx ? dx[i] : lastFont[text[i]];
		}
		if(font.IsUnderline() || font.IsStrikeout()) {
			x += fd->offset.cx;
			y += fd->offset.cy;
			if(font.IsUnderline())
				DrawLine(
					int(x + fd->underline_position * fd->sina),
					int(y + fd->underline_position * fd->cosa),
					int(x + xpos * fd->cosa + fd->underline_position * fd->sina),
					int(y - xpos * fd->sina + fd->underline_position * fd->cosa),
					fd->underline_thickness,
					ink
				);
			if(font.IsStrikeout()) {
				int p = 2 * fd->ascent / 3;
				DrawLine(
					int(x + p * fd->sina),
					int(y + p * fd->cosa),
					int(x + xpos * fd->cosa + p * fd->sina),
					int(y - xpos * fd->sina + p * fd->cosa),
					fd->underline_thickness,
					ink
				);
			}
		}
	}
	else {
		if(dx) {
			int xpos = ox;
			Buffer<XftCharSpec> ch(n);
			for(int i = 0; i < n; i++) {
				ch[i].ucs4 = text[i];
				ch[i].x = xpos;
				ch[i].y = oy + fd->ascent;
				xpos += dx[i];
			}
			XftDrawCharSpec(xftdraw, &c, fd->xftfont, ch, n);
		}
		else
			XftDrawString16(xftdraw, &c, fd->xftfont, ox, oy + fd->ascent,
			                (FcChar16 *)text, n);
		LLOG("XftColor: r=" << c.color.red << ", g=" << c.color.green << ", b=" << c.color.blue
			<< ", alpha=" << c.color.alpha << ", pixel=" << FormatIntHex(c.pixel));
		if(font.IsUnderline() || font.IsStrikeout()) {
			int cx;
			if(dx && n > 0) {
				cx = 0;
				Sum(cx, dx, dx + n - 1);
				cx += lastFont[text[n - 1]];
			}
			else
				cx = GetTextSize(text, font, n).cx;
			if(font.IsUnderline())
				DrawRect(x, y + lastFont.GetAscent() + lastFont.ptr->underline_position,
				         cx, lastFont.ptr->underline_thickness, ink);
			if(font.IsStrikeout())
				DrawRect(x, y + 2 * lastFont.GetAscent() / 3,
				         cx, lastFont.ptr->underline_thickness, ink);
		}
	}
}

#endif

END_UPP_NAMESPACE
