/***************************************************************************
  
  CFont.cpp
  
  (c) 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 = (GB_CLASS)0;

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;
}


void CFONT_update_spacing(void *_object)
{
	THIS->spacing = THIS->font->letterSpacing() * 10.0 / THIS->font->pointSizeF();
}


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

	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 apply_spacing(CFONT *_object)
{
	THIS->font->setLetterSpacing(QFont::AbsoluteSpacing, THIS->font->pointSizeF() * THIS->spacing / 10.0);
}

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

BEGIN_METHOD_VOID(Font_exit)

	exit_font_database();

END_METHOD


BEGIN_METHOD_VOID(Font_new)

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

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;

			case CFONT::Spacing:
				GB.ReturnFloat(THIS->spacing);
				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);
				apply_spacing(THIS);
				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()));
					apply_spacing(THIS);
				}
				break;
				
			case CFONT::Bold:
				f->setBold(VPROP(GB_BOOLEAN));
#if QT_VERSION >= QT_VERSION_CHECK(4, 8, 0)
				if (VPROP(GB_BOOLEAN))
					f->setStyleName("Bold");
				else
					f->setStyleName("");
#endif
				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;
				
			case CFONT::Spacing:
				THIS->spacing = VPROP(GB_FLOAT);
				apply_spacing(THIS);
				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

BEGIN_PROPERTY(Font_Spacing)

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

END_PROPERTY


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("_exit", NULL, Font_exit, NULL),
	GB_METHOD("_new", NULL, Font_new, NULL),
	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("Spacing", "f", Font_Spacing),
	GB_PROPERTY("Modified", "b", Font_Modified),

	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]"),

	#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
};


