#include "FontGlyphsTexture.h"

#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/dcmemory.h>
#include <wx/graphics.h>
#include <wx/rawbmp.h>

using namespace std;

namespace VRUT
{

	FontGlyphsTexture::FontGlyphsTexture() : charCount(0), glyphsHeight(0.0f), glyphsDescent(0.0f), texSize(0), texScale(0.0f) {}

	FontGlyphsTexture::FontGlyphsTexture(const wxFont &font, size_t char_count, size_t tex_size, float tex_scale, size_t supersampling):
		charCount(0), glyphsHeight(0.0f), glyphsDescent(0.0f), texSize(0), texScale(0.0f)
	{
		Create(font, char_count, tex_size, tex_scale, supersampling);
	}

	FontGlyphsTexture::FontGlyphsTexture(const wxFont &font, size_t char_count, size_t tex_size):
		charCount(0), glyphsHeight(0.0f), glyphsDescent(0.0f), texSize(0), texScale(0.0f)
	{
		Create(font, char_count, tex_size);
	}

	FontGlyphsTexture::~FontGlyphsTexture()
	{
		releaseTex();
	}

	void FontGlyphsTexture::releaseTex()
	{
		charCount = 0;
		metrics.clear();
		glyphsHeight = 0.0f;
		glyphsDescent = 0.0f;
		texSize = 0;
		texData.reset();
	}

	bool FontGlyphsTexture::Create(const wxFont &font, size_t char_count, size_t tex_size, float tex_scale, size_t supersampling)
	{
		releaseTex();

		size_t font_height = size_t(float(tex_size) / tex_scale);
		float bitmap_size_inv = 1.0f / float(tex_size * supersampling);
		wxBitmap bitmap(tex_size * supersampling, tex_size * supersampling, wxBITMAP_SCREEN_DEPTH);
		if (!bitmap.IsOk())
			return false;
		// We override font size, because of texture size and supersampling
		wxFont _font(font);
		_font.SetPointSize(font_height * supersampling);

		// Everything is OK now, store the parameters
		charCount = char_count;
		metrics.resize(charCount);
		texSize = tex_size;
		texScale = tex_scale;
		glyphsHeight = 0.0f;
		glyphsDescent = 0.0f;
		texData = std::unique_ptr<float []>(new float[texSize*texSize * charCount]);

		//Render4xUtils::VRUTStopWatch total_stop_watch, draw_stop_watch, process_stop_watch, clear_stop_watch;
		//double total_time = 0.0, draw_time = 0.0, process_time = 0.0, clear_time = 0.0;

		//total_stop_watch.Start();
		for (size_t c = 0; c < charCount; c++)
		{
			wchar_t letter[2] = { (wchar_t)c, L'\0' };
			wxString letterwx(letter);

			// Offset where drawing glyph starts so that glyphs do not leak into neighbour textures
			const wxCoord draw_text_offset = wxCoord(float((tex_size - font_height) * supersampling) * 0.3f);		// 0.3 chosed experimentally

			// wxMemoryDC produces some memory leaks, even if created and immediately destroyed.
			// It was reported (or it seems to be reported) at http://trac.wxwidgets.org/ticket/11007
			// in 2009, but unfortunately, it still has not been resolved.
			// After digging through all called functions, we found a "patch" to this. When calling
			// wxStockGDI::DeleteAll(), memory leaks disappear, the memory is released. It may still
			// not be the proper solution to this bug (personally the worst is that it may not be
			// thread-safe).
			wxMemoryDC mem_dc(bitmap);
			if (!mem_dc.IsOk())
			{
				wxStockGDI::DeleteAll();
				return false;
			}

			mem_dc.SetFont(_font);

			mem_dc.SetBackground(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_SOLID));
			mem_dc.SetTextBackground(wxColour(0, 0, 0));
			mem_dc.SetTextForeground(wxColour(255, 255, 255));

			wxCoord w, h, descent;
			mem_dc.GetTextExtent(letterwx, &w, &h, &descent);
			metrics[c].pen_origin.x	= bitmap_size_inv * draw_text_offset;
			metrics[c].pen_origin.y	= bitmap_size_inv * float(texSize*supersampling - h + descent - draw_text_offset);
			metrics[c].x_width		= bitmap_size_inv * float(w);

			glyphsHeight = std::max(glyphsHeight, bitmap_size_inv * float(h));
			glyphsDescent = std::max(glyphsDescent, bitmap_size_inv * float(descent));

			mem_dc.Clear();
			//draw_stop_watch.Start();
			mem_dc.DrawText(letterwx, draw_text_offset, draw_text_offset);
			//draw_time += draw_stop_watch.MeanTimeMS();
			mem_dc.SelectObject(wxNullBitmap);
			//process_stop_watch.Start();
#ifdef __WXMSW__
			wxImage img = bitmap.ConvertToImage();		// This takes the most time
			wxImagePixelData img_data(img);

			for (size_t tex_y = 0; tex_y < texSize; tex_y++)
				for (size_t tex_x = 0; tex_x < texSize; tex_x++)
				{
					wxImagePixelData::Iterator data_iter(img_data);

					float val = 0.0f;

					for (size_t s_x = 0; s_x < supersampling; s_x++)
						for (size_t s_y = 0; s_y < supersampling; s_y++)
						{
							data_iter.MoveTo(img_data, tex_x * supersampling + s_x, tex_y * supersampling + s_y);
							if (data_iter.Red() > 0)
								val += 1.0f;
						}
					texData[texSize * texSize * c + (texSize - tex_y - 1) * texSize + tex_x] = val / float(supersampling * supersampling);
				}
#else
			wxImage img(bitmap.ConvertToImage());
			img.Rescale(texSize, texSize, wxIMAGE_QUALITY_HIGH );
			for (size_t tex_y = 0; tex_y < texSize; tex_y++)
			{
				for (size_t tex_x = 0; tex_x < texSize; tex_x++)
				{
					texData[texSize * texSize * c + (texSize - tex_y - 1) * texSize + tex_x] = (float)img.GetRed(tex_x, tex_y)/255.0f;
				}
			}
#endif
			//process_time += process_stop_watch.MeanTimeMS();

			//clear_stop_watch.Start();
			//clear_time = clear_stop_watch.MeanTimeMS();
		}
		//total_time = total_stop_watch.MeanTimeMS();
		//LOG(wxString::Format(wxT("Creating font textures: draw %f ms, process %f ms, clear %f ms, total %f ms"), draw_time, process_time, clear_time, total_time));

		wxStockGDI::DeleteAll();
		return true;
	}

	bool FontGlyphsTexture::Create(const wxFont &font, size_t char_count, size_t tex_size)
	{
		return Create(font, char_count, tex_size, 2.0f, 2);
	}

	//------------------------------------
	//----    TEXT LETTERS DATA R3    ----
	//------------------------------------

	//--  MemPool

	void TextLettersData::UpdateTextMemPool::Reset()
	{
		widths.clear();
		if (lines.empty())
			lines.resize(1);
		GetCurrentLine().clear();
	}

	void TextLettersData::UpdateTextMemPool::PushLine(float line_width)
	{
		widths.push_back(line_width);
		if (lines.size() < (widths.size() + 1))
			lines.resize(widths.size() + 1);
		GetCurrentLine().clear();
	}

	//--  TextLettersData

	TextLettersData::TextLettersData(shared_ptr<FontGlyphsTexture> font_glyphs, float font_size, float spacing,
		GeometryText::Justify x_justify, GeometryText::Justify y_justify)
	{
		UpdateFont(font_glyphs, font_size, spacing, x_justify, y_justify);
	}

	TextLettersData::TextLettersData(const wxString &text, shared_ptr<FontGlyphsTexture> font_glyphs, float font_size, float spacing,
		GeometryText::Justify x_justify, GeometryText::Justify y_justify)
	{
		UpdateFont(font_glyphs, font_size, spacing, x_justify, y_justify);
		UpdateText(text);
	}

	void TextLettersData::UpdateFont(shared_ptr<FontGlyphsTexture> font_glyphs, float font_size, float spacing,
		GeometryText::Justify x_justify, GeometryText::Justify y_justify)
	{
		fontGlyphs = font_glyphs;
		fontSize = font_size;
		this->spacing = spacing;
		xJustify = x_justify;
		yJustify = y_justify;

		letters.clear();
	}

	void TextLettersData::UpdateText(const wxString &text)
	{
		UpdateTextMemPool mem_pool;
		UpdateText(text, mem_pool);
	}

	void TextLettersData::UpdateText(const wxString &text, UpdateTextMemPool &mem_pool)
	{
		if (!fontGlyphs)
			return;

		//-----------------------------------------
		//--  Create descriptors of all letters  --

		// Height of a single line
		float line_height = GetRectSize() * fontGlyphs->GetGlyphsHeight() * spacing;

		// Width of currently processed line
		float cummulative_x_offset = 0.0f;

		// Create a letter descriptor for each letter in the text
		mem_pool.Reset();
		for (size_t i = 0; i < text.size(); i++)
		{
			if (text[i] == wxT('\n'))
			{
				// Move to the other line and reset currently processed line
				mem_pool.PushLine(cummulative_x_offset);
				cummulative_x_offset = 0.0f;
			}
			else
			{
				// Create a descriptor of the letter and save it
				LetterDesc desc;
				if (text[i] < fontGlyphs->GetMetrics().size())
					desc.glyph = text[i];
				else
					desc.glyph = wxT('_'); //the letter not exist for us so use _ instead

				desc.position = -fontGlyphs->GetMetrics()[desc.glyph].pen_origin * GetRectSize();
				desc.position.x += cummulative_x_offset;
				cummulative_x_offset += fontGlyphs->GetMetrics()[desc.glyph].x_width * GetRectSize();
				desc.position.y -= float(mem_pool.GetNumberOfFinishedLines()) * line_height;
				mem_pool.GetCurrentLine().push_back(desc);
			}
		}
		// Push the last line
		if (!mem_pool.GetCurrentLine().empty())
		{
			mem_pool.PushLine(cummulative_x_offset);
			//cummulative_x_offset = 0.0f;
		}
		size_t number_of_finished_lines = mem_pool.GetNumberOfFinishedLines();

		//---------------------------
		//--  Apply justification  --

		if (xJustify == VRUT::GeometryText::Justify_First)
		{
			// Nothing
		}
		if (xJustify == VRUT::GeometryText::Justify_Begin)
		{
			// Nothing
		}
		if (xJustify == VRUT::GeometryText::Justify_Middle)
		{
			for (size_t line = 0; line < number_of_finished_lines; line++)
			{
				for (size_t c = 0; c < mem_pool.lines[line].size(); c++)
				{
					mem_pool.lines[line][c].position.x -= mem_pool.widths[line] * 0.5f;
				}
			}
		}
		if (xJustify == VRUT::GeometryText::Justify_End)
		{
			for (size_t line = 0; line < number_of_finished_lines; line++)
			{
				for (size_t c = 0; c < mem_pool.lines[line].size(); c++)
				{
					mem_pool.lines[line][c].position.x -= mem_pool.widths[line];
				}
			}
		}
		if (yJustify == VRUT::GeometryText::Justify_First)
		{
			// Nothing
		}
		if (yJustify == VRUT::GeometryText::Justify_Begin)
		{
			for (size_t line = 0; line < number_of_finished_lines; line++)
			{
				for (size_t c = 0; c < mem_pool.lines[line].size(); c++)
				{
					mem_pool.lines[line][c].position.y += -(fontGlyphs->GetGlyphsHeight() - fontGlyphs->GetGlyphsDescent()) * GetRectSize();
				}
			}
		}
		if (yJustify == VRUT::GeometryText::Justify_Middle)
		{
			for (size_t line = 0; line < number_of_finished_lines; line++)
			{
				for (size_t c = 0; c < mem_pool.lines[line].size(); c++)
				{
					mem_pool.lines[line][c].position.y += -(fontGlyphs->GetGlyphsHeight() - fontGlyphs->GetGlyphsDescent()) * GetRectSize() +
						float(number_of_finished_lines) * line_height * 0.5f;
				}
			}
		}
		if (yJustify == VRUT::GeometryText::Justify_End)
		{
			for (size_t line = 0; line < number_of_finished_lines; line++)
			{
				for (size_t c = 0; c < mem_pool.lines[line].size(); c++)
				{
					mem_pool.lines[line][c].position.y += float(number_of_finished_lines - 1) * line_height;
				}
			}
		}

		//----------------
		//--  Finalize  --

		size_t total_char_count = 0;
		for (size_t line = 0; line < number_of_finished_lines; line++)
		{
			total_char_count += mem_pool.lines[line].size();
		}
		letters.clear();
		letters.reserve(total_char_count);
		for (size_t line = 0; line < number_of_finished_lines; line++)
		{
			letters.insert(letters.end(), mem_pool.lines[line].begin(), mem_pool.lines[line].end());
		}
	}
}
