/******************************************************************************
*	File		-	text_renderer.h
*	Author		-	Joey Pollack
*	Date		-	2022/08/15 (y/m/d)
*	Mod Date	-	2022/08/15 (y/m/d)
*	Description	-	manage fonts and render text
******************************************************************************/

#include "text_renderer.h"
#include "renderer2D.h"
#include <utils/logger.h>

#include "texture.h"

#include <ft2build.h>
#include FT_FREETYPE_H 

namespace lunarium
{
    OpRes TextRenderer::Initialize()
    {
		if (FT_Init_FreeType(&mFTHandle))
		{
			return OpRes::Fail("FREETYPE: Could not init FreeType Library");
		}
        // 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();
    }

    int TextRenderer::LoadFont(const char* fontFile, const char* name, float size, int weight)
    {

        if (!mFTHandle)
        {
            Logger::Error(LogCategory::GRAPHICS, "Cannot load font: Freetype handle is invalid");
            return -1;
        }

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

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

	int TextRenderer::LoadFont(const unsigned char* fontData, int dataSize, const char* name, float size, int weight)
    {

        if (!mFTHandle)
        {
            Logger::Error(LogCategory::GRAPHICS, "Cannot load font: Freetype handle is invalid");
            return -1;
        }

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

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

	int TextRenderer::CreateFont(const char* name, 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);
		Font& f = mFonts.back();
		mFonts.back().Name = name;

		u32 max_width = 0;
		u32 max_height = 0;

		u32 running_width = 0;

		for (u8 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;
			}

            u32 width = face->glyph->bitmap.width;
            u32 height = face->glyph->bitmap.rows;
            u8* pixel_data = new u8[width * height];
            memcpy(pixel_data, face->glyph->bitmap.buffer, width * height);

            Character character = Character
				{ 
					c, pixel_data, 0, 0, Sizei { (i32)width, (i32)height },
					glm::vec2((float)face->glyph->bitmap_left, (float)face->glyph->bitmap_top),	// bearing
					(int)face->glyph->bitmap.rows - face->glyph->bitmap_top,					// downshift
					(unsigned int)face->glyph->advance.x										// advance
				};

			
			mFonts.back().CharSet.insert(std::pair<u8, Character>(c, character));

			if (width > max_width)
			{
				max_width = width;
			}

			if (height > max_height)
			{
				max_height = height;
			}
			
			// 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;
			}
        }

		// Set the character's texture offsets to the max values
		for (int i = 0; i < mFonts.back().CharSet.size(); i++)
		{
			mFonts.back().CharSet[i].TextureDataWidthOffset = max_width * i;
			mFonts.back().CharSet[i].TextureDataHeightOffset = 0;	// TEXTURE IS ONE ROW SO THIS IS ALWAYS ZERO
		}

		// Put the font texture together
		u32 single_row_width = f.CharSet.size() * max_width;
		if (single_row_width > Texture::GetMaxSize())
		{
			FreeTempPixelData(f.CharSet);
			mFonts.pop_back();
			Logger::Error(LogCategory::GRAPHICS, "Failed to create font texture: texture width (%d) is larger than max texture size: (%d)", single_row_width, Texture::GetMaxSize());
			return -1;
		}

		u8* texture_buffer = new u8[single_row_width * max_height];
		memset(texture_buffer, 0, single_row_width * max_height);

		// Copy the pixel data into the proper spot in the buffer
		u8* buffer_index = texture_buffer;
		for (int i = 0; i < max_height; i++)
		{
			for (auto iter = f.CharSet.begin(); iter != f.CharSet.end(); iter++)
			{
				if (i < iter->second.Size.Height)
				{
					memcpy(buffer_index, &iter->second.PixelDataBuffer[iter->second.Size.Width * i], iter->second.Size.Width);
				}
				buffer_index += max_width;
			}
		}

		FreeTempPixelData(f.CharSet);

		f.FontTexture = Texture::Create(texture_buffer, single_row_width, max_height, TextureFormat::RED, true);
		mDebugTexture = f.FontTexture;

		delete[] texture_buffer;

        // DEBUG STUFF:
        // Character* c = &mFonts.back().CharSet['a'];
        // mDebugTexture = Texture::Create(c->PixelDataBuffer, c->Size.Width, c->Size.Height, TextureFormat::RED, true);
		
		FT_Done_Face(face);

		return fontIdx;
	}

	void TextRenderer::DrawString(Renderer2D& r, u32 font_id, const char* str, Rectangle bounding_box, Color color, float scale)
	{
		if (font_id < 0 || font_id >= mFonts.size())
		{
			Logger::Error(LogCategory::GRAPHICS, "Invalid font ID specified for DrawString: %d", font_id);
			return;
		}

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

		float curX = bounding_box.left(); //topLeft.x;
		float curY = bounding_box.top(); //topLeft.y;

		// Iterate through all characters
		int len = (int)strlen(str);
		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 && ' ' == str[c - 1])
			{
				// check the total advance of this word to see if
				// it would go beyond the right boundry
				float totalAdvance = 0;
				for (int i = c; str[i] != ' ' && str[i] != '\0'; i++)
				{
					Character ch = mFonts[font_id].CharSet[str[c]];

					totalAdvance += (ch.Advance >> 6) * scale;
				}
				
				
				if (curX + totalAdvance > bounding_box.right())
				{
					curX = bounding_box.left();
					curY += lineSize;
				}

				if (curY + lineSize > bounding_box.bottom())
				{
					break;
				}
			}


			Character ch = mFonts[font_id].CharSet[str[c]];

			float xpos = curX + ch.Bearing.x * scale;
			float ypos = curY + (mFonts[font_id].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;	
			//

			float w = ch.Size.Width * scale;
			float h = ch.Size.Height * scale;
			// Update VBO for each character
			// float 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 }
			// };
			// 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)

			// DrawQuad
			float offset_x = ch.TextureDataWidthOffset;
			float offset_y = ch.TextureDataHeightOffset;
			Rectangle tex_region = Rectangle::MakeFromTopLeft(offset_x, offset_y, ch.Size.Width, ch.Size.Height);
			r.DrawQuad(Rectangle::MakeFromTopLeft(xpos, ypos, w, h), color, mFonts[font_id].FontTexture, 0.0f, tex_region);
		}
	}
	
	
	void TextRenderer::FreeTempPixelData(std::map<u8, Character>& char_set)
	{
		for (auto iter = char_set.begin(); iter != char_set.end(); iter++)
		{
			delete[] iter->second.PixelDataBuffer;
			iter->second.PixelDataBuffer = nullptr;
		}
	}

    /////////////////////////////////////////////////////////////////////
    //				DEBUG   DEBUG   DEBUG
    /////////////////////////////////////////////////////////////////////

    Texture* TextRenderer::GetDebugTexture()
    {
        return mDebugTexture;
    }    
}