/***************************************************************************

	CFont.cpp

	(c) 2000-2017 Benoît Minisini <benoit.minisini@gambas-basic.org>

	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, 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., 51 Franklin Street, Fifth Floor, Boston,
	MA 02110-1301, USA.

***************************************************************************/

#define __CFONT_CPP

#include "gambas.h"
#include "main.h"

#include <math.h>

#include <QApplication>
#include <QStringList>
#include <QFontMetrics>
#include <QFontDatabase>
#include <QTextDocument>

#include "CWidget.h"
#include "CDraw.h"
#include "CFont.h"

#include "gb.form.font.h"

#ifdef USE_DPI
int CFONT_dpi = 96;
#endif

static GB_CLASS CLASS_Font;

static QStringList _families;
static QFontDatabase *_info = 0;

static void init_font_database()
{
	if (_info)
		return;

	_info = new QFontDatabase();
	_families = _info->families();
}

static void exit_font_database()
{
	if (_info)
		delete _info;
}


CFONT *CFONT_create(const QFont &font, FONT_FUNC func, void *object)
{
	CFONT *_object = (CFONT *)GB.New(CLASS_Font, NULL, NULL);
	
	*(THIS->font) = font;
	THIS->func = func;
	THIS->object = object;
	if (object)
		GB.Ref(object);

	return THIS;
}

void CFONT_set(FONT_FUNC func, void *font, void *object)
{
	if (!font)
	{
		QFont f;
		(*func)(f, object);
	}
	else
		(*func)(*(((CFONT *)font)->font), object);
}


double CFONT_size_real_to_virtual(double size)
{
	#ifdef USE_DPI
	return size * (double)QPaintDevice::x11AppDpiY() / CFONT_dpi;
	#else
	return size;
	#endif
}

double CFONT_size_virtual_to_real(double size)
{
	#ifdef USE_DPI
	return size * CFONT_dpi / (double)QPaintDevice::x11AppDpiY();
	#else
	return size;
	#endif
}


static void set_font_from_string(CFONT *_object, QString &str)
{
	QStringList list;
	QString name, elt, flag;
	double size = 0;
	bool number;
	QFont f;

	// (!) Remove this test later, it is for backward compatibility

	if (str.length())
	{
		list = str.split(",");

		for (QStringList::Iterator it = list.begin(); it != list.end(); ++it )
		{
			elt = (*it);
			elt = elt.trimmed();
			flag = elt.toUpper();
			size = elt.toDouble(&number);

			if (flag == "BOLD")
			{
				f.setBold(true);
#if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0)
				f.setStyleName("Bold");
#endif
			}
			else if (flag == "ITALIC")
				f.setItalic(true);
			else if (flag == "UNDERLINE")
				f.setUnderline(true);
			else if (flag == "STRIKEOUT")
				f.setStrikeOut(true);
			else if (flag[0] == '+' || flag[0] == '-' || flag[0] == '0')
			{
				//f.setPointSizeFloat((int)(powf(qApp->font().pointSizeFloat(), 1.0 + ((int)size / 10.0)) + 0.5));
				f.setPointSizeF(GRADE_TO_SIZE(size, qApp->font().pointSizeF()));
			}
			else if (number && size > 0.0)
				f.setPointSizeF(SIZE_VIRTUAL_TO_REAL(size));
			else if (elt.length())
			{
				f.setBold(false);
				f.setItalic(false);
				f.setUnderline(false);
				f.setStrikeOut(false);
				if (elt.startsWith('"') && elt.endsWith('"'))
					elt = elt.mid(1, elt.length() - 2);
				f.setFamily(elt);
#if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0)
				f.setStyleName("");
#endif
			}
		}
	}

	*(THIS->font) = f;
}


BEGIN_METHOD_VOID(Font_init)

	CLASS_Font = GB.FindClass("Font");

END_METHOD


BEGIN_METHOD_VOID(Font_exit)

	exit_font_database();

END_METHOD


BEGIN_METHOD(Font_new, GB_STRING font)

	QString s;

	THIS->font = new QFont;
	THIS->func = 0;
	THIS->object = 0;

	if (!MISSING(font))
		QString s = QSTRING_ARG(font);

	set_font_from_string(THIS, s);

END_METHOD


BEGIN_METHOD_VOID(Font_free)

	if (THIS->object)
		GB.Unref(POINTER(&THIS->object));
	delete THIS->font;

END_METHOD


static void CFONT_manage(int prop, CFONT *_object, void *_param)
{
	bool noResize = false;
	QFont *f = THIS->font;
	double size;

	noResize = true; //((long)THIS->control == CFONT_DRAW && !DRAW_must_resize_font());

	if (READ_PROPERTY)
	{
		switch(prop)
		{
			case CFONT::Name: 
				GB.ReturnNewZeroString(f->family().toUtf8());
				break;
			
			case CFONT::Size:
				if (noResize)
					GB.ReturnFloat(f->pointSizeF());
				else
					GB.ReturnFloat(SIZE_REAL_TO_VIRTUAL(f->pointSizeF()));
				break;
				
			case CFONT::Grade:
				GB.ReturnInteger(SIZE_TO_GRADE(f->pointSizeF(), qApp->font().pointSizeF()));
				break;
				
			case CFONT::Bold:
				GB.ReturnBoolean(f->bold());
				break;
			
			case CFONT::Italic:
				GB.ReturnBoolean(f->italic());
				break;
			
			case CFONT::Underline:
				GB.ReturnBoolean(f->underline());
				break;
			
			case CFONT::Strikeout:
				GB.ReturnBoolean(f->strikeOut());
				break;
		}
	}
	else
	{
		switch (prop)
		{
			case CFONT::Name:
				f->setFamily(GB.ToZeroString(PROP(GB_STRING)));
				break;
				
			case CFONT::Size:
				if (noResize)
					size = VPROP(GB_FLOAT);
				else
					size = SIZE_VIRTUAL_TO_REAL(VPROP(GB_FLOAT));
				
				if (size <= 0)
				{
					GB.Error("Bad font size");
					return;
				}
				
				f->setPointSizeF(size);
				break;
				
			case CFONT::Grade:
				{
					int g = VPROP(GB_INTEGER);
					if (g < FONT_GRADE_MIN)
						g = FONT_GRADE_MIN;
					else if (g > FONT_GRADE_MAX)
						g = FONT_GRADE_MAX;
					f->setPointSizeF(GRADE_TO_SIZE(g, qApp->font().pointSizeF()));
				}
				break;
				
			case CFONT::Bold:
				f->setBold(VPROP(GB_BOOLEAN));
				break;
				
			case CFONT::Italic:
				f->setItalic(VPROP(GB_BOOLEAN));
				break;
				
			case CFONT::Underline:
				f->setUnderline(VPROP(GB_BOOLEAN));
				break;
				
			case CFONT::Strikeout:
				f->setStrikeOut(VPROP(GB_BOOLEAN));
				break;
		}

		if (THIS->func)
			(*(THIS->func))(*f, THIS->object);
		else if (THIS->object)
		{
			GB_VALUE value;
			value.type = GB_T_OBJECT;
			value._object.value = THIS;
			GB.SetProperty(THIS->object, "Font", &value);
		}
		
		THIS->modified = TRUE;
	}
}


BEGIN_PROPERTY(Font_Name)

	CFONT_manage(CFONT::Name, OBJECT(CFONT), _param);

END_PROPERTY

BEGIN_PROPERTY(Font_Size)

	CFONT_manage(CFONT::Size, OBJECT(CFONT), _param);

END_PROPERTY

BEGIN_PROPERTY(Font_Grade)

	CFONT_manage(CFONT::Grade, OBJECT(CFONT), _param);

END_PROPERTY

BEGIN_PROPERTY(Font_Bold)

	CFONT_manage(CFONT::Bold, OBJECT(CFONT), _param);

END_PROPERTY

BEGIN_PROPERTY(Font_Italic)

	CFONT_manage(CFONT::Italic, OBJECT(CFONT), _param);

END_PROPERTY

BEGIN_PROPERTY(Font_Underline)

	CFONT_manage(CFONT::Underline, OBJECT(CFONT), _param);

END_PROPERTY

BEGIN_PROPERTY(Font_Strikeout)

	CFONT_manage(CFONT::Strikeout, OBJECT(CFONT), _param);

END_PROPERTY


static void add(QString &str, const QString& data)
{
	if (str.length())
		str += ',';

	str += data;
}

BEGIN_METHOD_VOID(Font_ToString)

	QFont *f = THIS->font;
	QString str;
	double size;
	QString family;
	bool number;

	//str = qfont.family().left(1).upper() + qfont.family().mid(1).lower() + " " + QString::number(qfont.pointSize());
	family = f->family();
	family.toDouble(&number);
	if (number)
		str = '"' + str + '"';
	add(str, family);
	
	size = SIZE_REAL_TO_VIRTUAL(f->pointSizeF());
	size = (double)((int)(size * 10 + 0.5)) / 10;
	add(str, QString::number(size));
	if (f->bold())
		add(str, "Bold");
	if (f->italic())
		add(str, "Italic");
	if (f->underline())
		add(str, "Underline");
	if (f->strikeOut())
		add(str, "StrikeOut");

	RETURN_NEW_STRING(str);

END_METHOD


BEGIN_METHOD(Font_get, GB_STRING str)

	CFONT *font;
	QString s = QSTRING_ARG(str);

	//qDebug(">> Font_get: %s", s.latin1());

	font = (CFONT *)GB.New(CLASS_Font, NULL, NULL);
	set_font_from_string(font, s);

	GB.ReturnObject(font);

	//qDebug("<< Font_get");

END_METHOD

BEGIN_METHOD_VOID(Font_Copy)

	QFont f;
	
	f.fromString(THIS->font->toString());
	GB.ReturnObject(CFONT_create(f));

END_METHOD


BEGIN_PROPERTY(Font_Ascent)

	QFontMetrics fm(*(THIS->font));

	GB.ReturnInteger(fm.ascent());

END_PROPERTY


BEGIN_PROPERTY(Font_Descent)

	QFontMetrics fm(*(THIS->font));

	GB.ReturnInteger(fm.descent());

END_PROPERTY


BEGIN_PROPERTY(Font_Height)

	QFontMetricsF fm(*(THIS->font));

	GB.ReturnInteger(fm.lineSpacing());

END_PROPERTY

static void get_text_size(CFONT *_object, QString s, int *w, int *h)
{
	QFontMetricsF fm(*(THIS->font));
	
	if (w)
	{
		QStringList sl;
		qreal wt, width = 0;
		int i;

		sl = s.split('\n');

		for (i = 0; i < (int)sl.count(); i++)
		{
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
			wt = fm.horizontalAdvance(sl[i]);
#else
			wt = fm.width(sl[i]);
#endif
			if (wt > width) width = wt;
		}
		
		*w = ::ceilf(width);
	}
	
	if (h)
	{
		int nl;

		nl = s.count('\n');

		*h = ::ceilf(fm.height() * (1 + nl) + fm.leading() * nl);
	}
}

BEGIN_METHOD(Font_TextHeight, GB_STRING text)

	int h;
	get_text_size(THIS, QSTRING_ARG(text), NULL, &h);
	GB.ReturnInteger(h);

END_METHOD


BEGIN_METHOD(Font_TextWidth, GB_STRING text)

	int w;
	get_text_size(THIS, QSTRING_ARG(text), &w, NULL);
	GB.ReturnInteger(w);

END_METHOD


BEGIN_METHOD(Font_TextSize, GB_STRING text)

	GEOM_RECT *rect = GEOM.CreateRect();
	get_text_size(THIS, QSTRING_ARG(text), &rect->w, &rect->h);
	GB.ReturnObject(rect);

END_METHOD


static void get_rich_text_size(CFONT *_object, char *text, int len, int sw, int *w, int *h)
{
	QTextDocument rt;
	
	DRAW_init_rich_text(&rt, *(THIS->font));
	
	rt.setHtml(QString::fromUtf8((const char *)text, len));

	if (sw > 0)
		rt.setTextWidth(sw);
	
	if (w) *w = ::ceilf(rt.idealWidth());
	if (h) *h = ::ceilf(rt.size().height());
}


BEGIN_METHOD(Font_RichTextWidth, GB_STRING text; GB_INTEGER width)

	int w;
	get_rich_text_size(THIS, STRING(text), LENGTH(text), VARGOPT(width, -1), &w, NULL);
	GB.ReturnInteger(w);

END_METHOD


BEGIN_METHOD(Font_RichTextHeight, GB_STRING text; GB_INTEGER width)

	int h;
	get_rich_text_size(THIS, STRING(text), LENGTH(text), VARGOPT(width, -1), NULL, &h);
	GB.ReturnInteger(h);

END_METHOD


BEGIN_METHOD(Font_RichTextSize, GB_STRING text; GB_INTEGER width)

	GEOM_RECT *rect = GEOM.CreateRect();
	get_rich_text_size(THIS, STRING(text), LENGTH(text), VARGOPT(width, -1), &rect->w, &rect->h);
	GB.ReturnObject(rect);

END_METHOD




#ifdef USE_DPI
BEGIN_PROPERTY(Font_Resolution)

	if (READ_PROPERTY)
		GB.ReturnInteger(CFONT_dpi);
	else
	{
		CFONT_dpi = VPROP(GB_INTEGER);
		if (CFONT_dpi < 1)
			CFONT_dpi = 96;
	}

END_PROPERTY
#endif

BEGIN_METHOD_VOID(Fonts_next)

	QString s;
	int *index = (int *)GB.GetEnum();

	if (*index == 0)
		init_font_database();

	if (*index >= _families.count())
		GB.StopEnum();
	else
	{
		s = _families[*index];
		RETURN_NEW_STRING(s);
		(*index)++;
	}

END_METHOD


BEGIN_METHOD(Fonts_Exist, GB_STRING family)

	int i;
	char *family = GB.ToZeroString(ARG(family));
	
	init_font_database();
	
	for (i = 0; i < _families.count(); i++)
	{
		if (_families[i] == family)
		{
			GB.ReturnBoolean(true);
			return;
		}
	}

	GB.ReturnBoolean(false);

END_METHOD


BEGIN_PROPERTY(Fonts_Count)

	init_font_database();
	GB.ReturnInteger(_families.count());

END_PROPERTY


BEGIN_PROPERTY(Font_Fixed)

	init_font_database();
	GB.ReturnBoolean(_info->isFixedPitch(THIS->font->family()));

END_PROPERTY


BEGIN_PROPERTY(Font_Scalable)

	init_font_database();
	GB.ReturnBoolean(_info->isSmoothlyScalable(THIS->font->family()));

END_PROPERTY


BEGIN_PROPERTY(Font_Styles)

	QStringList styles;
	GB_ARRAY array;
	int i;

	init_font_database();
	styles = _info->styles(THIS->font->family());

	GB.Array.New(&array, GB_T_STRING, styles.count());
	for (i = 0; i < styles.count(); i++)
		*(char **)GB.Array.Get(array, i) = NEW_STRING(styles[i]);

	GB.ReturnObject(array);

END_PROPERTY


BEGIN_PROPERTY(Font_Modified)

	if (READ_PROPERTY)
		GB.ReturnBoolean(THIS->modified);
	else
		THIS->modified = VPROP(GB_BOOLEAN);

END_PROPERTY

//---------------------------------------------------------------------------

GB_DESC CFontsDesc[] =
{
	GB_DECLARE_STATIC("Fonts"),

	GB_STATIC_METHOD("Exist", "b", Fonts_Exist, "(Family)s"),
	GB_STATIC_METHOD("_next", "s", Fonts_next, NULL),
	GB_STATIC_PROPERTY_READ("Count", "i", Fonts_Count),

	GB_END_DECLARE
};


GB_DESC CFontDesc[] =
{
	GB_DECLARE("Font", sizeof(CFONT)),
	//GB_NOT_CREATABLE(),

	GB_STATIC_METHOD("_init", NULL, Font_init, NULL),
	GB_STATIC_METHOD("_exit", NULL, Font_exit, NULL),
	GB_METHOD("_new", NULL, Font_new, "[(Font)s]"),
	GB_METHOD("_free", NULL, Font_free, NULL),
	GB_METHOD("Copy", "Font", Font_Copy, NULL),

	GB_PROPERTY("Name", "s", Font_Name),
	GB_PROPERTY("Size", "f", Font_Size),
	GB_PROPERTY("Grade", "i", Font_Grade),
	GB_PROPERTY("Bold", "b", Font_Bold),
	GB_PROPERTY("Italic", "b", Font_Italic),
	GB_PROPERTY("Underline", "b", Font_Underline),
	GB_PROPERTY("Strikeout", "b", Font_Strikeout),
	GB_PROPERTY("Modified", "b", Font_Modified),

	GB_METHOD("ToString", "s", Font_ToString, NULL),

	GB_METHOD("TextWidth", "i", Font_TextWidth, "(Text)s"),
	GB_METHOD("TextHeight", "i", Font_TextHeight, "(Text)s"),
	GB_METHOD("TextSize", "Rect", Font_TextSize, "(Text)s"),

	GB_METHOD("RichTextWidth", "i", Font_RichTextWidth, "(Text)s[(Width)i]"),
	GB_METHOD("RichTextHeight", "i", Font_RichTextHeight, "(Text)s[(Width)i]"),
	GB_METHOD("RichTextSize", "Rect", Font_RichTextSize, "(Text)s[(Width)i]"),

	GB_STATIC_METHOD("_get", "Font", Font_get, "(Font)s"),

	#ifdef USE_DPI
	GB_STATIC_PROPERTY("Resolution", "i", Font_Resolution),
	#endif

	GB_PROPERTY_READ("Ascent", "i", Font_Ascent),
	GB_PROPERTY_READ("Descent", "i", Font_Descent),
	GB_PROPERTY_READ("Height", "i", Font_Height),
	GB_PROPERTY_READ("H", "i", Font_Height),

	GB_PROPERTY_READ("Fixed", "b", Font_Fixed),
	GB_PROPERTY_READ("Scalable", "b", Font_Scalable),
	GB_PROPERTY_READ("Styles", "String[]", Font_Styles),

	GB_END_DECLARE
};


