/* -----------------------------------------------------------------------------
	GSFramework
	Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
----------------------------------------------------------------------------- */


		#include <cstring>
		#include "font/font_renderer.h"
		#include "picture/pict.h"
		#include "ascii/parser.h"
		#include "log/log.h"

		using namespace GS;
		using namespace GS::AsciiParser;

		float FontRenderer::default_text_tracking = 0;
		float FontRenderer::default_text_heading = 0;

//
struct	GS::SubString
{
		const char	*entry;
		int			char_count;
		int			space_count;
		int			width;				// 26.6
		int			height;

		void	Reset(const char *string)
		{
			entry = string;
			char_count = 0;
			space_count = 0;
			width = 0;
			height = 0;
		}
};

		int			substring_count = 0;
		SubString	substring_array[1024];


//------------------------------------------------------------------------------
const char			*FontRenderer::CheckCommand(const char *string, Command &command)
{
	command.code = CommandNone;
	if	(!string[0] || !string[1])
		return string;

	if	((string[0] == '~') && (string[1] == '~'))
	{
		//----------------------------------------------------------------------
		#define PARSE_COMPONENT(_C_, _M_)\
		{\
			string = SkipSpace(string + 1, eos);\
			command.vector._C_ = (float)String::atoi(string);\
			string = NextEntry(string, eos);\
			if	(string[0] != (_M_))\
			{\
				command.code = CommandParseError;\
				__LOG_E__ << "Error parsing text command components.\n";\
				return NULL;\
			}\
		}
		//----------------------------------------------------------------------

		const char *eos = string + std::strlen(string);

		if	(!strncmp("Color(", string + 2, 6) || !strncmp("COLOR(", string + 2, 6))	// [EJ] 1st may: range is [0;255]
		{
			command.code = CommandColor;
			string += 7;

			const char *eop = Find(string, eos, ')');
			if	(!eop)
			{
				command.code = CommandParseError;
				return NULL;
			}

			PARSE_COMPONENT(x, ',');
			PARSE_COMPONENT(y, ',');
			PARSE_COMPONENT(z, ',');
			PARSE_COMPONENT(w, ')');
			string = eop + 1;
		}
		else	if	(!strncmp("Size(", string + 2, 5) || !strncmp("SIZE(", string + 2, 5))
		{
			command.code = CommandSize;

			string += 6;

			const char *eop = Find(string, eos, ')');
			if	(!eop)
			{
				command.code = CommandParseError;
				return NULL;
			}

			PARSE_COMPONENT(x, ')');
			string = eop + 1;
		}
	}
	return string;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
const char			*FontRenderer::FetchSubString(const char *string, SubString &substring, TextState &state, int max_width, int max_char)
{
	if	(!string)
		return NULL;

	// Jump over leading spaces.
	forever
	{
		if	(string[0] != ' ')
			break;

		if	(!string[0] || (string[0] == '\n'))
		{
			substring.Reset(NULL);
			return string;
		}
		string++;
	}

	// Start sub-string.
	substring.Reset(string);
	int previous_codepoint = 0;

	state.font->SetPixelSize(state.GetSize());
	max_width <<= 6;

	// Word cut point.
	bool rollback_available = false;
	SubString rollback_substring;
	const char *rollback_string = NULL;

	int tracking = int(state.GetTracking() * 64.f);

	forever
	{
		Command command;
		if	((string = CheckCommand(string, command)) == 0)
			break;

		if	(command.code == CommandNone)
		{
			if	(string[0] == '\n')
			{
				string++;
				break;
			}
			if	((string[0] == '\\') && (string[1] == 'n'))
			{
				string += 2;
				break;
			}
			if	(string[0] == 0)
				break;
			if	((max_char > 0) && (substring.char_count == max_char))
				break;

			// Get glyph.
			uint codepoint;
			int codelength = String::Utf8toUtf32((const uchar *)string, &codepoint);

			state.font->LoadGlyph(codepoint, false);

			// Retrieve glyph formatting informations.
			int advance = state.font->GetAdvance();
			int kerning = (state.font->HasKerning() && (previous_codepoint != 0)) ? state.font->GetKerning(previous_codepoint, codepoint) : 0;

			// Width constraint.
			if	((max_width > 0) && ((substring.width + advance) >= max_width))
			{
				rollback_available = true;
				break;
			}

			substring.width += advance + kerning + tracking;
			substring.height = Types::Max(state.font->GetHeight(), substring.height);

			// Count space.
			if	(string[0] == ' ')
			{
				substring.space_count++;

				// Store the word rollback position.
				Command dummy_command;
				CheckCommand(string, dummy_command);

				if	((dummy_command.code == CommandNone) && (string[1] != ' '))
				{
					rollback_substring = substring;
					rollback_string = string;
				}
			}
			substring.char_count++;

			// Next glyph.
			string += codelength ? codelength : 1;
			previous_codepoint = codepoint;
		}
		else
			switch (command.code)
			{
				case CommandColor:	// Irrelevant when not composing.
					break;

				case CommandSize:
					state.SetSize((int)command.vector.x);
					state.font->SetPixelSize(state.GetSize());
					break;

				default: break;
			}
	}

	// Rollback to the last word position.
	if	(rollback_available && rollback_string)
	{
		substring = rollback_substring;
		string = rollback_string;
	}
	return string;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void				FontRenderer::DrawSubString(SubString &line, Picture &output, TextState &state, const iRect &_out_rect, const iRect &clip_rect, int justification)
{
	const char *string = line.entry;
	state.font->SetPixelSize(state.GetSize());

	iRect out_rect(_out_rect);
	uint previous_codepoint = 0;

	int tracking = int(state.GetTracking() * 64.f);

	for	(int n = 0; n < line.char_count; ++n)
	{
		Command	command;
		string = CheckCommand(string, command);

		if	(command.code == CommandNone)
		{
			// Get glyph.
			uint codepoint;
			int codelength = String::Utf8toUtf32((const uchar *)string, &codepoint);

			state.font->LoadGlyph(codepoint, true);

			// Retrieve glyph formatting informations.
			int advance = state.font->GetAdvance();
			int kerning = (state.font->HasKerning() && (previous_codepoint != 0)) ? state.font->GetKerning(previous_codepoint, codepoint) : 0;

			// Render.
			int px = out_rect.sx >> 6;
			int py = (out_rect.sy + (line.height * 3) / 4) >> 6;	// FIXME smells the hack... at best!

			state.font->RenderCurrentGlyph(output, iPoint(px, py), clip_rect, state.color);

			out_rect.sx += advance + kerning + tracking;

			// Next glyph.
			string += codelength;
			previous_codepoint = codepoint;
		}
		else
		{
			switch (command.code)
			{
				case CommandColor:
					state.color = command.vector ;/// 255.f;
					break;

				case CommandSize:
					state.SetSize((int)command.vector.x);
					state.font->SetPixelSize(state.GetSize());
					break;

				default: break;
			}
			--n;
		}
	}
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
iRect				FontRenderer::Format(const char *txt, const TextState &in_state, const iRect &out_rect)
{
	if	(!in_state.font)
		return iRect (0, 0, 0, 0);

	// Create working state.
	TextState state = in_state;

	// Setup formatting rules.
	int	max_width = (state.format == TextState::Line) ? -1 : out_rect.GetWidth(),
		max_char = (state.format != TextState::Column) ? -1 : state.column_width;

	// Fetch all substrings.
	substring_count = 0;
	while (txt && txt[0])
		txt = FetchSubString(txt, substring_array[substring_count++], state, max_width, max_char);

	// Create full text rectangle.
	iRect text_rect;
	text_rect.Set(0, 0, 0, 0);

	int leading = int(state.GetLeading() * 64);

	for	(int n = 0; n < substring_count; ++n)
	{
		if	(text_rect.ex < substring_array[n].width)
			text_rect.ex = substring_array[n].width;
		text_rect.ey += substring_array[n].height + leading;
	}
	if	(substring_count > 0)
		text_rect.ey -= leading;

	text_rect.ex = text_rect.ex / 64;
	text_rect.ey = text_rect.ey / 64;
	return text_rect;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
iRect				FontRenderer::Compose(Picture &output, const char *txt, const TextState &in_state, const iRect &out_rect, const iRect &clip_rect)
{
	if	(in_state.font.IsNull() || (output.GetPixelFormat().GetBpp() != 32))
		return iRect(0, 0, 0, 0);

	// Create working state.
	TextState state = in_state;
	int leading = int(state.GetLeading() * 64);

	// Draw substrings.
	iRect work_out_rect(out_rect * 64);

	for	(int n = 0; n < substring_count; ++n)
	{
		iRect line_rect(work_out_rect);
		line_rect.ex = line_rect.sx + substring_array[n].width;

		int offset = 0, justification = 0;

		switch (state.alignment)
		{
			case TextState::Left:
				offset = out_rect.sx * 64 - line_rect.sx;
				break;
			case TextState::Center:
				offset = (out_rect.GetWidth() * 64 - line_rect.GetWidth()) / 2;
				break;
			case TextState::Right:
				offset = out_rect.ex * 64 - line_rect.ex;
				break;

			case TextState::Justify:
				if	(n < (substring_count - 1))
					justification = (out_rect.GetWidth() * 64 - line_rect.GetWidth()) / substring_array[n].space_count;
				break;

			default: break;
		}

		line_rect.sx += offset;

		DrawSubString(substring_array[n], output, state, line_rect, clip_rect, justification);
		work_out_rect.sy += substring_array[n].height + leading;
	}
	return out_rect;
}
//------------------------------------------------------------------------------
