#include "Text.hpp"

#include "Exception.hpp"
#include "gl4.h"

#include <boost/locale.hpp> //For UTF8->wstring

namespace ftgl
{
#include "FreeTypeGl/freetype-gl.h"
}


TextManager::TextManager(glm::vec2 AtlasSize)
{
	m_Atlas = ftgl::texture_atlas_new(AtlasSize.x, AtlasSize.y, 1);
	//the shader:

	/* Vertex:

	#version 430 core

	layout(location=1) in vec2 attPosition;
	layout(location=2) in vec2 attUv;

	out vec2 varUv;

	uniform vec2 ScreenSize;
	uniform vec2 TextPosition;

	void main()
	{
	varUv=attUv;
	vec2 ScreenPos=((attPosition+TextPosition) / ScreenSize * 2)-vec2(1, 0);
	ScreenPos=vec2(ScreenPos.x, -ScreenPos.y + 1 );
	gl_Position=vec4(ScreenPos, -1, 1);
	}

	*/

	/* Fragment

	#version 430 core

	in vec2 varUv;
	out vec4 FragColor;
	uniform sampler2D BitmapFont;

	void main()
	{
	vec4 color=texture(BitmapFont, varUv);
	FragColor=vec4(1, 1, 1, color.x);
	}
	*/

	//neat tool for stringification: http://tomeko.net/online_tools/cpp_text_escape.php?lang=en

	std::string VertexSource = "\t#version 430 core\n\n\tlayout(location=1) in vec2 attPosition;\n\tlayout(location=2) in vec2 attUv;\n\n\tout vec2 varUv;\n\n\tuniform vec2 ScreenSize;\n\tuniform vec2 TextPosition;\n\n\tvoid main()\n\t{\n\tvarUv=attUv;\n\tvec2 ScreenPos=((attPosition+TextPosition) / ScreenSize * 2)-vec2(1, 0);\n\tScreenPos=vec2(ScreenPos.x, -ScreenPos.y + 1 );\n\tgl_Position=vec4(ScreenPos, -1, 1);\n\t}";
	std::string FragmentSource = "\t\t#version 430 core\n\n\t\tin vec2 varUv;\n\t\tout vec4 FragColor;\n\t\tuniform sampler2D BitmapFont;\n\n\t\tvoid main()\n\t\t{\n\t\t\tvec4 color=texture(BitmapFont, varUv);\n\t\t\tFragColor=vec4(1, 1, 1, color.x);\n\t\t}";
	m_Shader.Create(VertexSource, FragmentSource);
}

TextManager::~TextManager()
{
	m_Texts.clear();// explicitly delete all texts, before we destroy the fonts

	for(auto& f : m_Fonts)
		ftgl::texture_font_delete(f.second);

	ftgl::texture_atlas_delete(m_Atlas);
}

void TextManager::AddFont(std::string Filename, float Size)
{
	FontDescription d;
	d.Filename = Filename;
	d.Size = Size;

	if(m_Fonts.find(d) != m_Fonts.end())
		THROW(Exception("Font already loaded") << ErrorInfo(Filename + " in " + std::to_string(Size)));

	const auto CommonCharacters = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789äÄüÜöÖß?.,-#/\!\"%&()=' :;<>{}[]@€+-*";
	auto NewFont = ftgl::texture_font_new_from_file(m_Atlas, Size, Filename.c_str());
	auto missed = ftgl::texture_font_load_glyphs(NewFont, CommonCharacters);
	if(0 != missed)
		THROW(Exception("not all characters could be fitted in") << ErrorInfo(std::to_string(missed) + " missing"));

	m_Fonts[d] = NewFont;
}


Text* TextManager::NewText(std::string text, glm::vec2 Position, float Size, std::string FontFile)
{
	if(m_Fonts.empty())
		THROW(Exception("no Fonts loaded"));
	FontDescription d;
	d.Filename = FontFile;
	d.Size = Size;
	if(0 == Size)
		d.Size = m_Fonts.begin()->first.Size;
	if("" == FontFile)
		d.Filename = m_Fonts.begin()->first.Filename;
	auto It = m_Fonts.find(d);
	if(It == m_Fonts.end())
		THROW(Exception("Font not found: ") << ErrorInfo(d.Filename + " in " + std::to_string(d.Size)));
	auto Font = It->second;

	auto NewText=std::make_unique<Text>(text, Position, Font);
	auto ret = NewText.get();
	m_Texts.push_back(std::move(NewText));
	return ret;
}


void TextManager::RenderAll()
{
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_Atlas->id);

	m_Shader.Set();
	m_Shader.SetUniform("ScreenSize", m_ScreenSize);
	m_Shader.SetUniform("BitmapFont", 0);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glBlendColor(1, 1, 1, 1);

	for(auto& t : m_Texts)
	{
		if(!t->GetText().empty())
		{
			m_Shader.SetUniform("TextPosition", t->GetScreenPosition());
			t->DrawCall();
		}
	}

	m_Shader.Unset();
	glBindVertexArray(0);
}



Text::Text(std::string text, glm::vec2 Position, ftgl::texture_font_t* Font) :
m_ScreenPosition(Position),
m_Font(Font)
{
	//generate buffers and Vertex Array object
	glGenBuffers(1, &m_PositionBuffer);
	glGenBuffers(1, &m_UVBuffer);
	glGenVertexArrays(1, &m_VAO);
	glBindVertexArray(m_VAO);

	const auto PositionLocation = 1u;
	const auto UvLocation = 2u;

	glBindBuffer(GL_ARRAY_BUFFER, m_PositionBuffer);
	glEnableVertexAttribArray(PositionLocation);
	glVertexAttribPointer(PositionLocation, 2, GL_FLOAT, GL_FALSE, 0, nullptr);

	glBindBuffer(GL_ARRAY_BUFFER, m_UVBuffer);
	glEnableVertexAttribArray(UvLocation);
	glVertexAttribPointer(UvLocation, 2, GL_FLOAT, GL_FALSE, 0, nullptr);

	//unbind stuff
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindVertexArray(0);


	SetText(text);
}

Text::~Text()
{
	glDeleteBuffers(1, &m_PositionBuffer);
	glDeleteBuffers(1, &m_UVBuffer);
	glDeleteVertexArrays(1, &m_VAO);
}


void Text::SetText(std::string text)
{
	if(text == m_CurrentText)
		return;
	m_Positions.clear();
	m_UVs.clear();
	m_CurrentText.clear();
	AddText(text);
}

void Text::AddText(std::string text)
{
	//convert text from UTF8 to wchar
	std::wstring wtext = boost::locale::conv::utf_to_utf<wchar_t, char>(text.c_str());

	//Update buffers:
	using glm::vec2;
	float LineHeight = m_Font->height;
	vec2 CursorPosition(0, LineHeight);
	for(auto i = 0u; i < wtext.length(); ++i)
	{
		auto c = wtext[i];
		if('\n' == c)
		{
			CursorPosition.x = 0;
			CursorPosition.y += LineHeight;
			continue;
		}
		auto glyph = ftgl::texture_font_get_glyph(m_Font, c);
		if(!glyph)
		{
			THROW(Exception("Glyph not found: " + c));
		}

		//Kerning
		if(i > 0)
			CursorPosition.x += ftgl::texture_glyph_get_kerning(glyph, wtext[i - 1]);

		//TP=TopLeft, BR=BottomRight

		//Position
		auto PosTL = CursorPosition + vec2(glyph->offset_x, -glyph->offset_y);
		auto PosBR = PosTL + vec2(glyph->width, glyph->height);

		//TexCoord
		auto TexTL = vec2(glyph->s0, glyph->t0);
		auto TexBR = vec2(glyph->s1, glyph->t1);

		//TODO use index buffer, hehe
		m_Positions.push_back(vec2(PosTL.x, PosTL.y));
		m_Positions.push_back(vec2(PosBR.x, PosTL.y));
		m_Positions.push_back(vec2(PosTL.x, PosBR.y));
		m_Positions.push_back(vec2(PosBR.x, PosTL.y));
		m_Positions.push_back(vec2(PosBR.x, PosBR.y));
		m_Positions.push_back(vec2(PosTL.x, PosBR.y));

		m_UVs.push_back(vec2(TexTL.x, TexTL.y));
		m_UVs.push_back(vec2(TexBR.x, TexTL.y));
		m_UVs.push_back(vec2(TexTL.x, TexBR.y));
		m_UVs.push_back(vec2(TexBR.x, TexTL.y));
		m_UVs.push_back(vec2(TexBR.x, TexBR.y));
		m_UVs.push_back(vec2(TexTL.x, TexBR.y));

		CursorPosition.x += glyph->advance_x;
	}

	//Update Buffers:
	glBindBuffer(GL_ARRAY_BUFFER, m_PositionBuffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 2 * m_Positions.size(), m_Positions.data(), GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, m_UVBuffer);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 2 * m_UVs.size(), m_UVs.data(), GL_STATIC_DRAW);
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	m_CurrentText += text;
}

std::string Text::GetText()
{
	return m_CurrentText;
}

void Text::DrawCall()
{
	glBindVertexArray(m_VAO);
	glDrawArrays(GL_TRIANGLES, 0, m_Positions.size());
}