/*
GUI text
by Gustav Larsson
gee@pyro.nu
*/

#include "precompiled.h"

#include "GUI.h"
#include "ps/CLogger.h"
#include "ps/Parser.h"
#include <algorithm>

#define LOG_CATEGORY "gui"

#include "ps/Font.h"

using namespace std;

static const wchar_t TagStart = '[';
static const wchar_t TagEnd   = ']';

void CGUIString::SFeedback::Reset()
{
	m_Images[Left].clear();
	m_Images[Right].clear();
	m_TextCalls.clear();
	m_SpriteCalls.clear();
	m_Size = CSize();
	m_NewLine=false;
}

void CGUIString::GenerateTextCall(SFeedback &Feedback,
								  const CStr& DefaultFont,
								  const int &from, const int &to,
								  const bool FirstLine,
								  const IGUIObject *pObject) const
{
	// Reset width and height, because they will be determined with incrementation
	//  or comparisons.
	Feedback.Reset();

	// Check out which text chunk this is within.
	//bool match_found = false;
	vector<TextChunk>::const_iterator itTextChunk;
	for (itTextChunk=m_TextChunks.begin(); itTextChunk!=m_TextChunks.end(); ++itTextChunk)
	{
		// - GL - Temp
		TextChunk tc = *itTextChunk;
		// -- GL

		// Get the area that is overlapped by both the TextChunk and
		//  by the from/to inputted.
		int _from, _to;
		_from = max(from, itTextChunk->m_From);
		_to = min(to, itTextChunk->m_To);
		
		// If from is larger than to, than they are not overlapping
		if (_to == _from && itTextChunk->m_From == itTextChunk->m_To)
		{
			// These should never be able to have more than one tag.
			debug_assert(itTextChunk->m_Tags.size()==1);

			// Now do second check
			//  because icons and images are placed on exactly one position
			//  in the words-list, it can be counted twice if placed on an
			//  edge. But there is always only one logical preference that
			//  we want. This check filters the unwanted.
			
			// it's in the end of one word, and the icon 
			//  should really belong to the beginning of the next one
			if (_to == to && to >= 1)
			{
				if (GetRawString()[to-1] == ' ' ||
					GetRawString()[to-1] == '-' ||
					GetRawString()[to-1] == '\n')
					continue;
			}
			// This string is just a break
			if (_from == from && from >= 1)
			{
				if (GetRawString()[from] == '\n' && 
					GetRawString()[from-1] != '\n' && 
					GetRawString()[from-1] != ' ' &&
					GetRawString()[from-1] != '-')
					continue;
			}

			// Single tags
			if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGLEFT)
			{
				// Only add the image if the icon exists.
				if (g_GUI.IconExists(itTextChunk->m_Tags[0].m_TagValue))
				{
					Feedback.m_Images[SFeedback::Left].push_back(itTextChunk->m_Tags[0].m_TagValue);
				}
				else if (pObject)
				{
					LOG(ERROR, LOG_CATEGORY, "Trying to use an [imgleft]-tag with an undefined icon (\"%s\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
				}
			}
			else
			if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_IMGRIGHT)
			{
				// Only add the image if the icon exists.
				if (g_GUI.IconExists(itTextChunk->m_Tags[0].m_TagValue))
				{
					Feedback.m_Images[SFeedback::Right].push_back(itTextChunk->m_Tags[0].m_TagValue);
				}
				else if (pObject)
				{
					LOG(ERROR, LOG_CATEGORY, "Trying to use an [imgright]-tag with an undefined icon (\"%s\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
				}
			}
			else
			if (itTextChunk->m_Tags[0].m_TagType == CGUIString::TextChunk::Tag::TAG_ICON)
			{
				// Only add the image if the icon exists.
				if (g_GUI.IconExists(itTextChunk->m_Tags[0].m_TagValue))
				{
					// We'll need to setup a text-call that will point
					//  to the icon, this is to be able to iterate
					//  through the text-calls without having to
					//  complex the structure virtually for nothing more.
					SGUIText::STextCall TextCall;

					// Also add it to the sprites being rendered.
					SGUIText::SSpriteCall SpriteCall;

					// Get Icon from icon database in g_GUI
					SGUIIcon icon = g_GUI.GetIcon(itTextChunk->m_Tags[0].m_TagValue);

					CSize size = icon.m_Size;

					// append width, and make maximum height the height.
					Feedback.m_Size.cx += size.cx;
					Feedback.m_Size.cy = max(Feedback.m_Size.cy, size.cy);

					// These are also needed later
					TextCall.m_Size = size;
					SpriteCall.m_Area = size;

					// Now displace the sprite if the additional value in the tag
					//  exists
					if (itTextChunk->m_Tags[0].m_TagAdditionalValue != string())
					{
						CSize displacement;
						// Parse the value
						if (!GUI<CSize>::ParseString(itTextChunk->m_Tags[0].m_TagAdditionalValue, displacement))
							LOG(ERROR, LOG_CATEGORY, "Error parsing 'displace' value for tag [ICON]");
						else
							SpriteCall.m_Area += displacement;
					}

					SpriteCall.m_Sprite = icon.m_SpriteName;
					SpriteCall.m_CellID = icon.m_CellID;

					// Add sprite call
					Feedback.m_SpriteCalls.push_back(SpriteCall);

					// Finalize text call
					TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back();

					// Add text call
					Feedback.m_TextCalls.push_back(TextCall);
				}
				else if (pObject)
				{
					LOG(ERROR, LOG_CATEGORY, "Trying to use an [icon]-tag with an undefined icon (\"%s\").", itTextChunk->m_Tags[0].m_TagValue.c_str());
				}
			}
		}
		else
		if (_to > _from && !Feedback.m_NewLine)
		{
			SGUIText::STextCall TextCall;

			// Set defaults
			TextCall.m_Font = DefaultFont;
			TextCall.m_UseCustomColor = false;

			// Extract substring from RawString.
			TextCall.m_String = GetRawString().GetSubstring(_from, _to-_from);
			
			// Go through tags and apply changes.
			vector<CGUIString::TextChunk::Tag>::const_iterator it2;
			for (it2 = itTextChunk->m_Tags.begin(); it2 != itTextChunk->m_Tags.end(); ++it2)
			{
				if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_COLOR)
				{
					// Set custom color
					TextCall.m_UseCustomColor = true;
					
					// Try parsing the color string
					if (!GUI<CColor>::ParseString(it2->m_TagValue, TextCall.m_Color))
					{
						if (pObject)
							LOG(ERROR, LOG_CATEGORY, "Error parsing the value of a [color]-tag in GUI text when reading object \"%s\".", pObject->GetPresentableName().c_str());
					}
				}
				else
				if (it2->m_TagType == CGUIString::TextChunk::Tag::TAG_FONT)
				{
					// TODO Gee: (2004-08-15) Check if Font exists?
					TextCall.m_Font = it2->m_TagValue;
				}
			}

			// Calculate the size of the font
			CSize size;
			int cx, cy;
			CFont font (TextCall.m_Font);
			font.CalculateStringSize(TextCall.m_String, cx, cy);
			// For anything other than the first line, the line spacing
			// needs to be considered rather than just the height of the text
			if (! FirstLine)
				cy = font.GetLineSpacing();

			size.cx = (float)cx;
			size.cy = (float)cy;

			// Append width, and make maximum height the height.
			Feedback.m_Size.cx += size.cx;
			Feedback.m_Size.cy = max(Feedback.m_Size.cy, size.cy);

			// These are also needed later
			TextCall.m_Size = size;

			if (TextCall.m_String.Length() >= 1)
			{
				if (TextCall.m_String[0] == '\n')
				{
					Feedback.m_NewLine = true;
				}
			}

			// Add text-chunk
			Feedback.m_TextCalls.push_back(TextCall);
		}
	}
}

bool CGUIString::TextChunk::Tag::SetTagType(const CStr& tagtype)
{
	CStr _tagtype = tagtype.UpperCase();	

	if (_tagtype == CStr("B"))
	{
		m_TagType = TAG_B;
		return true;
	}
	else
	if (_tagtype == CStr("I"))
	{
		m_TagType = TAG_I;
		return true;
	}
	else
	if (_tagtype == CStr("COLOR"))
	{
		m_TagType = TAG_COLOR;
		return true;
	}
	else
	if (_tagtype == CStr("FONT"))
	{
		m_TagType = TAG_FONT;
		return true;
	}
	else
	if (_tagtype == CStr("ICON"))
	{
		m_TagType = TAG_ICON;
		return true;
	}
	else
	if (_tagtype == CStr("IMGLEFT"))
	{
		m_TagType = TAG_IMGLEFT;
		return true;
	}
	else
	if (_tagtype == CStr("IMGRIGHT"))
	{
		m_TagType = TAG_IMGRIGHT;
		return true;
	}

	return false;
}

void CGUIString::SetValue(const CStrW& str)
{
	// clear
	m_TextChunks.clear();
	m_Words.clear();
	m_RawString = CStrW();

	// Setup parser
	// TODO Gee: (2004-08-16) Create and store this parser object somewhere to save loading time.
	// TODO PT: Extended CParserCache so that the above is possible (since it currently only
	// likes one-task parsers)
	CParser Parser;
	// I've added the option of an additional parameter. Only used for icons when writing this.
	Parser.InputTaskType("start", "$ident[_=_$value_[$ident_=_$value_]]");
	Parser.InputTaskType("end", "/$ident");

	long position = 0;
	long from=0;		// the position in the raw string where the last tag ended
	long from_nonraw=0;	// like from only in position of the REAL string, with tags.
	long curpos = 0;

	// Current Text Chunk
	CGUIString::TextChunk CurrentTextChunk;

	for (;;position = curpos+1)
	{
		// Find next TagStart character
		curpos = str.Find(position, TagStart);

		if (curpos == -1)
		{
			m_RawString += str.GetSubstring(position, str.Length()-position);

			if (from != (long)m_RawString.Length())
			{
				CurrentTextChunk.m_From = from;
				CurrentTextChunk.m_To = (int)m_RawString.Length();
				m_TextChunks.push_back(CurrentTextChunk);
			}

			break;
		}
		else
		{
			// First check if there is another TagStart before a TagEnd,
			//  in that case it's just a regular TagStart and we can continue.
			long pos_left = str.Find(curpos+1, TagStart);
			long pos_right = str.Find(curpos+1, TagEnd);

			if (pos_right == -1)
			{
				m_RawString += str.GetSubstring(position, curpos-position+1);
				continue;
			}
			else
			if (pos_left != -1 && pos_left < pos_right)
			{
				m_RawString += str.GetSubstring(position, pos_left-position);
				continue;
			}
			else
			{
				m_RawString += str.GetSubstring(position, curpos-position);

				// Okay we've found a TagStart and TagEnd, positioned
				//  at pos and pos_right. Now let's extract the
				//  interior and try parsing.
				CStr tagstr = str.GetSubstring(curpos+1, pos_right-curpos-1);

				CParserLine Line;
				Line.ParseString(Parser, (const char*)tagstr);

				// Set to true if the tag is just text.
				bool justtext = false;

				if (Line.m_ParseOK)
				{
					if (Line.m_TaskTypeName == "start")
					{
                        // The tag
						TextChunk::Tag tag;
						std::string Str_TagType;

						Line.GetArgString(0, Str_TagType);

						if (!tag.SetTagType(Str_TagType))
						{
							justtext = true;
						}
						else
						{
							// Check for possible value-strings
							if (Line.GetArgCount() >= 2)
								Line.GetArgString(1, tag.m_TagValue);

							// Check if an additional parameter was specified
							if (Line.GetArgCount() == 4)
							{
								string str;
								Line.GetArgString(2, str);

								if (tag.m_TagType == TextChunk::Tag::TAG_ICON && 
									str == "displace")
								{
									Line.GetArgString(3, tag.m_TagAdditionalValue);
								}
								else
								{
									LOG(WARNING, LOG_CATEGORY, "Trying to declare an additional attribute ('%s') in a [%s]-tag, which the tag isn't accepting", str.c_str(), Str_TagType.c_str());
								}
							}

							// Finalize last
							if (curpos != from_nonraw)
							{
								CurrentTextChunk.m_From = from;
								CurrentTextChunk.m_To = from + curpos - from_nonraw;
								m_TextChunks.push_back(CurrentTextChunk);
								from = CurrentTextChunk.m_To;
							}
							from_nonraw = pos_right+1;

							// Some tags does not have a closure, and should be
							//  stored without text. Like a <tag /> in XML.
							if (tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT ||
								tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT ||
								tag.m_TagType == TextChunk::Tag::TAG_ICON)
							{
								// We need to use a fresh text chunk
								//  because 'tag' should be the *only* tag.
								TextChunk FreshTextChunk;

								// They does not work with the text system.
								FreshTextChunk.m_From = from + pos_right+1 - from_nonraw;
								FreshTextChunk.m_To = from + pos_right+1 - from_nonraw;

								FreshTextChunk.m_Tags.push_back(tag);

								m_TextChunks.push_back(FreshTextChunk);
							}
							else
							{
								// Add that tag, but first, erase previous occurences of the
								//  same tag.
								vector<TextChunk::Tag>::iterator it;
								for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
								{
									if (it->m_TagType == tag.m_TagType)
									{
										CurrentTextChunk.m_Tags.erase(it);
										break;
									}
								}
		
								// Add!
								CurrentTextChunk.m_Tags.push_back(tag);
							}
						}
					}
					else
					if (Line.m_TaskTypeName == "end")
					{
						// The tag
						TextChunk::Tag tag;
						std::string Str_TagType;

						Line.GetArgString(0, Str_TagType);

						if (!tag.SetTagType(Str_TagType))
						{
							justtext = true;
						}
						else
						{
							// Finalize the previous chunk
							if (curpos != from_nonraw)
							{
								CurrentTextChunk.m_From = from;
								CurrentTextChunk.m_To = from + curpos - from_nonraw;
								m_TextChunks.push_back(CurrentTextChunk);
								from = CurrentTextChunk.m_To;
							}
							from_nonraw = pos_right+1;

							// Search for the tag, if it's not added, then 
							//  pass it as plain text.
							vector<TextChunk::Tag>::iterator it;
							for (it = CurrentTextChunk.m_Tags.begin(); it != CurrentTextChunk.m_Tags.end(); ++it)
							{
								if (it->m_TagType == tag.m_TagType)
								{
									CurrentTextChunk.m_Tags.erase(it);
									break;
								}
							}
						}
					}
				}
				else justtext = true;

				if (justtext)
				{
					// What was within the tags could not be interpreted
					//  so we'll assume it's just text.
					m_RawString += str.GetSubstring(curpos, pos_right-curpos+1);
				}

				curpos = pos_right;

				continue;
			}
		}
	}

	// Add a delimiter at start and at end, it helps when
	//  processing later, because we don't have make exceptions for
	//  those cases.
	// We'll sort later.
	m_Words.push_back(0);
	m_Words.push_back((int)m_RawString.Length());

	// Space: ' '
	for (position=0, curpos=0;;position = curpos+1)
	{
		// Find the next word-delimiter.
		long dl = m_RawString.Find(position, ' ');

		if (dl == -1)
			break;

		curpos = dl;
		m_Words.push_back((int)dl+1);
	}

	// Dash: '-'
	for (position=0, curpos=0;;position = curpos+1)
	{
		// Find the next word-delimiter.
		long dl = m_RawString.Find(position, '-');

		if (dl == -1)
			break;

		curpos = dl;
		m_Words.push_back((int)dl+1);
	}

	// New Line: '\n'
	for (position=0, curpos=0;;position = curpos+1)
	{
		// Find the next word-delimiter.
		long dl = m_RawString.Find(position, '\n');

		if (dl == -1)
			break;

		curpos = dl;

		// Add before and 
		m_Words.push_back((int)dl);
		m_Words.push_back((int)dl+1);
	}

	sort(m_Words.begin(), m_Words.end());

	// Remove duplicates (only if larger than 2)
	if (m_Words.size() > 2)
	{
		vector<int>::iterator it;
		int last_word = -1;
		for (it = m_Words.begin(); it != m_Words.end(); )
		{
			if (last_word == *it)
			{
				it = m_Words.erase(it);
			}
			else
			{
				last_word = *it;
				++it;
			}
		}
	}

#if 0
	for (int i=0; i<(int)m_Words.size(); ++i)
	{
		LOG(NORMAL, LOG_CATEGORY, "m_Words[%d] = %d", i, m_Words[i]);
	}

	for (int i=0; i<(int)m_TextChunks.size(); ++i)
	{
		LOG(NORMAL, LOG_CATEGORY, "m_TextChunk[%d] = [%d,%d]", i, m_TextChunks[i].m_From, m_TextChunks[i].m_To);
		for (int j=0; j<(int)m_TextChunks[i].m_Tags.size(); ++j)
		{
			LOG(NORMAL, LOG_CATEGORY, "--Tag: %d \"%s\"", (int)m_TextChunks[i].m_Tags[j].m_TagType, m_TextChunks[i].m_Tags[j].m_TagValue.c_str());
		}
	}
#endif
}
