#include "Text.hpp"

#include "Exception.hpp"
#include "gl.hpp"
#include "Helper.hpp"

#include <boost/locale.hpp> //For UTF8->wstring

namespace ftgl
{
#include "FreeTypeGl/freetype-gl.h"
}


TextManager::TextManager(glm::uvec2 screenSize, glm::uvec2 atlasSize):
m_ScreenSize(screenSize)
{
	m_Atlas = ftgl::texture_atlas_new(atlasSize.x, atlasSize.y, 1);
	//the shader:

	const std::string VertexSource = R"glsl(
#version 330

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.f)-vec2(1.f, 0.f);
	ScreenPos=vec2(ScreenPos.x, -ScreenPos.y + 1.f );
	gl_Position=vec4(ScreenPos, -1.f, 1.f);
}

)glsl";


	const std::string FragmentSource = R"glsl(
#version 330

in vec2 varUv;
out vec4 FragColor;
uniform sampler2D BitmapFont;
uniform vec3 FontColor;

void main()
{
	vec4 color=texture(BitmapFont, varUv);
	if(color.x<0.1)
		discard;
	FragColor=vec4(FontColor, color.x);
}

)glsl";

	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::SetScreenSize(glm::vec2 new_size)
{
	m_ScreenSize = new_size;
}

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() << ex_message("Font already loaded") << ex_detail(filename + " in " + std::to_string(size));

	const auto CommonCharacters = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789äÄüÜöÖß?.,-#/\\!\"%&()=' :;<>{}[]@€+-*";
	// TODO: check if font file exists
	if(!FileIsOpenable(filename))
		throw Exception() << ex_message("font file could not be opened") << ex_file(filename);
	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() << ex_message("not all characters could be fitted in") << ex_detail(std::to_string(missed) + " missing") << ex_file(filename);

	m_Fonts[d] = NewFont;
}


Text* TextManager::NewText(std::string text, glm::vec2 Position, float Size, std::string FontFile)
{
	// find the desired font...
	if(m_Fonts.empty())
		throw Exception() << ex_message("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() << ex_message("Font not found: ") << ex_detail(d.Filename + " in " + std::to_string(d.Size));
	auto Font = It->second;

	// ... and create the text
	auto NewText=std::make_unique<Text>(text, Position, Font);
	auto ret = NewText.get();
	m_Texts.push_back(std::move(NewText));
	return ret;
}

void TextManager::RemoveText(Text * text)
{
	for(auto it = m_Texts.begin(); it != m_Texts.end(); )
	{
		if(it->get() == text)
		{
			m_Texts.erase(it);
			return;
		}
		else
			it++;
	}
}


void TextManager::RenderAll()
{
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, m_Atlas->id);

	m_Shader.Set();
	m_Shader.SetUniform("ScreenSize", glm::vec2(m_ScreenSize));
	m_Shader.SetUniform("BitmapFont", 0);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glBlendColor(1, 1, 1, 1);
	

	glDisable(GL_CULL_FACE);

	for(auto& t : m_Texts)
	{
		if(!t->GetText().empty() && t->GetVisible())
		{
			m_Shader.SetUniform("TextPosition", t->GetScreenPosition());
			m_Shader.SetUniform("FontColor", t->GetColor());
			t->DrawCall();
		}
	}
	glEnable(GL_CULL_FACE);

	m_Shader.Unset();

	glBindVertexArray(0);
	glDisable(GL_BLEND);
}

std::string TextManager::GetUtf8StdStringFromWChar(wchar_t* text)
{
	return std::string(boost::locale::conv::utf_to_utf<char, wchar_t>(text));
}




///////////////////////////////////////////////////////////////

Text::Text(std::string text, glm::vec2 Position, ftgl::texture_font_t* Font) :
m_ScreenPosition(Position),
m_Visible(true),
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());

	unsigned int number_of_lines = 1; // for text size estimation

	//Update buffers:
	using glm::vec2;
	float LineHeight = m_Font->height;
	vec2 CursorPosition(0, m_Font->ascender);
	for(auto i = 0u; i < wtext.length(); ++i)
	{
		auto c = wtext[i];
		if('\n' == c)
		{
			CursorPosition.x = 0;
			CursorPosition.y += LineHeight;
			number_of_lines++;
			continue;
		}
		auto glyph = ftgl::texture_font_get_glyph(m_Font, c);
		if(!glyph)
		{
			//TODO apparently it is time for Unicode Exceptions...
			throw Exception() << ex_message(std::string("Glyph not found: ") + static_cast<char>(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;

	// update text size:
	m_DrawSize = vec2(0, 0);
	for(const auto& p : m_Positions)
	{
		m_DrawSize.x = glm::max(m_DrawSize.x, p.x);
		//m_DrawSize.y = glm::max(m_DrawSize.y, p.y);
	}
	m_DrawSize.y = (number_of_lines*LineHeight);
}

std::string Text::GetText()
{
	return m_CurrentText;
}

void Text::DrawCall()
{
	glBindVertexArray(m_VAO);
	glDrawArrays(GL_TRIANGLES, 0, m_Positions.size());
}

void Text::SetVisible(bool visible)
{
	m_Visible = visible;
}

bool Text::GetVisible()
{
	return m_Visible;
}

