/******************************************************************************
*	File		-	glText.cpp
*	Author		-	Joey Pollack
*	Date		-	2021/09/07 (y/m/d)
*	Mod Date	-	2021/09/07 (y/m/d)
*	Description	-	Used to load and manage fonts. Renders text using freetype
*                   and openGL.
******************************************************************************/

#include "glText.h"
#include <utils/logger.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <ft2build.h>
#include FT_FREETYPE_H 

namespace lunarium
{
    int glText::LoadFont(const char* fontFile, const char* name, float size, int weight)
    {
        if (!mTextShader.IsBuilt())
		{
			Logger::Error(LogCategory::GRAPHICS, "Unable to load font because no text rendering shaders are loaded");
			return -1;
		}

		FT_Library ft;
		if (FT_Init_FreeType(&ft))
		{
			Logger::Error(LogCategory::GRAPHICS,"FREETYPE: Could not init FreeType Library");
			return -1;
		}

		FT_Face face;
		if (FT_New_Face(ft, fontFile, 0, &face))
		{
			Logger::Error(LogCategory::GRAPHICS, "FREETYPE: Failed to load font from file: %s", fontFile);
			return -1;
		}

		return CreateFont(name, ft, face, size, weight);
    }

	int glText::LoadFont(const unsigned char* fontData, int dataSize, const char* name, float size, int weight)
    {
        if (!mTextShader.IsBuilt())
		{
			Logger::Error(LogCategory::GRAPHICS, "Unable to load font because no text rendering shaders are loaded");
			return -1;
		}

		FT_Library ft;
		if (FT_Init_FreeType(&ft))
		{
			Logger::Error(LogCategory::GRAPHICS, "FREETYPE: Could not init FreeType Library");
			return -1;
		}

		FT_Face face;
		if (FT_New_Memory_Face(ft, fontData, dataSize, 0, &face))
		{
			Logger::Error(LogCategory::GRAPHICS, "FREETYPE: Failed to load font from buffer. Font name: %s", name);
			return -1;
		}

		return CreateFont(name, ft, face, size, weight);
    }

	int glText::CreateFont(const char* name, FT_Library ft, FT_Face face, float size, int weight)
	{
		FT_Set_Pixel_Sizes(face, 0, 48);

		glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // Disable byte-alignment restriction

		// NOTE: Testing code
		float sizes[128] = { 0.0f };

		mFonts.push_back(Font());
		int fontIdx = (int)(mFonts.size() - 1);
		mFonts.back().Name = name;
		for (GLubyte c = 0; c < 128; c++)
		{
			// Load character glyph 
			if (FT_Load_Char(face, c, FT_LOAD_RENDER))
			{
				Logger::Error(LogCategory::GRAPHICS, "FREETYTPE: Failed to load Glyph %c from font: %s", c, name);
				continue;
			}
			
			// Generate texture
			GLuint texture;
			glGenTextures(1, &texture);
			glBindTexture(GL_TEXTURE_2D, texture);
			glTexImage2D(
				GL_TEXTURE_2D,
				0,
				GL_RED,
				face->glyph->bitmap.width,
				face->glyph->bitmap.rows,
				0,
				GL_RED,
				GL_UNSIGNED_BYTE,
				face->glyph->bitmap.buffer
			);
			// Set texture options
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

			// Now store character for later use
			Character character = {
				texture,
				{ (float)face->glyph->bitmap.width, (float)face->glyph->bitmap.rows },
				glm::vec2((float)face->glyph->bitmap_left, (float)face->glyph->bitmap_top),
				(int)face->glyph->bitmap.rows - face->glyph->bitmap_top,
				(unsigned int)face->glyph->advance.x
			};
			mFonts.back().CharSet.insert(std::pair<GLchar, Character>(c, character));
			
			// Find Max (positive) Down Shift
			if (character.DownShift > 0 && ((int)mFonts.back().MaxDownShift) < character.DownShift)
			{
				mFonts.back().MaxDownShift = character.DownShift;
			}

			// Determines the distance from the top down to the baseline
			if (character.Size.Height == character.Bearing.y && mFonts.back().MaxHeight < character.Size.Height)
			{
				mFonts.back().MaxHeight = (unsigned int)character.Size.Height;
			}
        }
		
		FT_Done_Face(face);
		FT_Done_FreeType(ft);

		return fontIdx;
	}

    OpRes glText::Initialize()
    {
        glGenVertexArrays(1, &mTextVAO);
		glGenBuffers(1, &mTextVBO);
		glBindVertexArray(mTextVAO);
		glBindBuffer(GL_ARRAY_BUFFER, mTextVBO);
		glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
		glEnableVertexAttribArray(0);
		glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		glBindVertexArray(0);


		// Load text shaders
		mTextShader.SetSource(glShader::TYPE_VERTEX, VertShader, false);
		mTextShader.SetSource(glShader::TYPE_FRAGMENT, FragShader, false);
		if (!mTextShader.BuildProgram())
		{
			return OpRes::Fail("Unable to build text shader program. Fonts will not load and text will not render.");
		}

        return OpRes::OK();
    }

    void glText::DrawString(int fontID, const char* text, glm::vec2 position, Color color, float scale, glm::mat4 projection)
    {
		// HACK: There might be a better way to specify no limit to the bottom right
		DrawString(fontID, text, position, glm::vec2(9000000.0f, 9000000.0f), color, scale, projection);
    }

    void glText::DrawString(int fontID, const char* text, glm::vec2 topLeft, glm::vec2 botRight, Color color, float scale, glm::mat4 projection)
    {
        if (!mTextShader.IsBuilt())
		{
			Logger::Error(LogCategory::GRAPHICS, "Unable to draw string because no text rendering shaders are loaded");
			return;
		}

		if (fontID < 0 || fontID >= mFonts.size())
		{
			Logger::Error(LogCategory::GRAPHICS, "Invalid font ID specified for DrawString: %d", fontID);
			return;
		}


		//glEnable(GL_BLEND);
		//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		// NOTE: String drawing code based on code from: https://learnopengl.com/In-Practice/Text-Rendering
		mTextShader.MakeActive();
		mTextShader.SetUniformMatrix("projection", 1, glm::value_ptr(projection));
		mTextShader.SetUniformf("textColor", { color.R, color.G, color.B, color.A });
		glActiveTexture(GL_TEXTURE0);
		glBindVertexArray(mTextVAO);

		float lineSize = (mFonts[fontID].MaxHeight + mFonts[fontID].MaxDownShift) * scale;

		float curX = topLeft.x;
		float curY = topLeft.y;

		// Iterate through all characters
		int len = (int)strlen(text);
		for (int c = 0; c < len; c++)
		{
			// if the last character was a space then we must
			// be starting a new word
			if (c > 0 && ' ' == text[c - 1])
			{
				// check the total advance of this word to see if
				// it would go beyond the right boundry
				GLfloat totalAdvance = 0;
				for (int i = c; text[i] != ' ' && text[i] != '\0'; i++)
				{
					Character ch = mFonts[fontID].CharSet[text[c]];

					totalAdvance += (ch.Advance >> 6) * scale;
				}
				
				
				if (curX + totalAdvance > botRight.x)
				{
					curX = topLeft.x;
					curY += lineSize;
				}

				if (curY + lineSize > botRight.y)
				{
					break;
				}
			}


			Character ch = mFonts[fontID].CharSet[text[c]];

			GLfloat xpos = curX + ch.Bearing.x * scale;
			GLfloat ypos = curY + (mFonts[fontID].MaxHeight - ch.Size.Height) * scale;

			// Apply a down offset if the char should go below the baseline
			ypos += (ch.DownShift * scale);

			// NOTE: The following code does not work because it assumes that positive y is the upward direction
			// 
			//GLfloat test_ypos = curY + (ch.Size.Height - ch.Bearing.Y) * scale;	
			//

			GLfloat w = ch.Size.Width * scale;
			GLfloat h = ch.Size.Height * scale;
			// Update VBO for each character
			GLfloat vertices[6][4] = {
				{ xpos,     ypos + h,   0.0, 1.0 },
				{ xpos + w, ypos,       1.0, 0.0 },
				{ xpos,     ypos,       0.0, 0.0 },

				{ xpos,     ypos + h,   0.0, 1.0 },
				{ xpos + w, ypos + h,   1.0, 1.0 },
				{ xpos + w, ypos,       1.0, 0.0 }
			};
			// Render glyph texture over quad
			glBindTexture(GL_TEXTURE_2D, ch.TextureID);
			// Update content of VBO memory
			glBindBuffer(GL_ARRAY_BUFFER, mTextVBO);
			glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
			glBindBuffer(GL_ARRAY_BUFFER, 0);
			// Render quad
			glDrawArrays(GL_TRIANGLES, 0, 6);
			// Now advance cursors for next glyph (note that advance is number of 1/64 pixels)
			curX += (ch.Advance >> 6) * scale; // Bitshift by 6 to get value in pixels (2^6 = 64)


		}
		glBindVertexArray(0);
		glBindTexture(GL_TEXTURE_2D, 0);
    }
}