#ifndef OVERLAY_HPP
#define OVERLAY_HPP

/*
   """
   Portions of this software are copyright © 2025 The FreeType
   Project (https://freetype.org).  All rights reserved.
   """
*/

#include <open_engine.hpp>

#include "shmup.hpp"
#include <glm/ext/matrix_clip_space.hpp>
#include "freetype/fttypes.h"
#include <freetype2/ft2build.h>
#include FT_FREETYPE_H
#include <glad/glad.h>
#include <map>


struct Character {
    unsigned int texture_id;  // ID handle of the glyph texture
    glm::ivec2 size;       // Size of glyph
    glm::ivec2 bearing;    // Offset from baseline to left/top of glyph
    FT_Long advance;    // Offset to advance to next glyph
};

class Overlay : public OpenEngine::Layer
{
    public:
        Overlay(OpenEngine::Ref<Shmup>& shmup)
            : game_layer(shmup)
        {};

        ~Overlay() {};

        void RenderText(std::string text, float x, float y, float scale, glm::vec3 color)
        {
            // activate corresponding render state	
            text_shader->Bind();
            text_shader->SetVec3("textColor", glm::vec3(color.x, color.y, color.z));
            glActiveTexture(GL_TEXTURE0);
            glBindVertexArray(VAO);

            // iterate through all characters
            std::string::const_iterator c;
            for (c = text.begin(); c != text.end(); c++)
            {
                Character ch = characters[*c];

                float xpos = x + ch.bearing.x * scale;
                float ypos = y - (ch.size.y - ch.bearing.y) * scale;

                float w = ch.size.x * scale;
                float h = ch.size.y * scale;
                // update VBO for each character
                float vertices[6][4] = {
                    { xpos,     ypos + h,   0.0f, 0.0f },            
                    { xpos,     ypos,       0.0f, 1.0f },
                    { xpos + w, ypos,       1.0f, 1.0f },

                    { xpos,     ypos + h,   0.0f, 0.0f },
                    { xpos + w, ypos,       1.0f, 1.0f },
                    { xpos + w, ypos + h,   1.0f, 0.0f }           
                };
                // render glyph texture over quad
                glBindTexture(GL_TEXTURE_2D, ch.texture_id);
                // update content of VBO memory
                glBindBuffer(GL_ARRAY_BUFFER, VBO);
                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)
                x += (ch.advance >> 6) * scale; // bitshift by 6 to get value in pixels (2^6 = 64)
            }
            glBindVertexArray(0);
            glBindTexture(GL_TEXTURE_2D, 0);
        }

        glm::vec2 GetTextSize(const std::string& text, float scale)
        {
            float width = 0.0f;
            float height = 0.0f;

            for (char c : text)
            {
                Character ch = characters[c];
                width += (ch.advance >> 6) * scale;  // Accumulate total width

                // Track the maximum height
                float char_height = ch.size.y * scale;
                if (char_height > height)
                    height = char_height;
            }

            return glm::vec2(width, height);
        }

        void RenderTextCentered(std::string text, float center_x, float center_y, float scale, glm::vec3 color)
        {
            glm::vec2 text_size = GetTextSize(text, scale);

            // Calculate top-left position to make text centered
            float x = center_x - (text_size.x / 2.0f);
            float y = center_y - (text_size.y / 2.0f);

            RenderText(text, x, y, scale, color);
        }

    private:
        void OnAttach() override
        {
            OE_INFO("Loading Freetype...");

            int error;

            FT_Library ft;
            error = FT_Init_FreeType(&ft);
            if (error != 0) {
                OE_ERROR("FREETYPE: Could not init FreeType Library");
                return;
            }

            FT_Face face;
            error = FT_New_Face(ft, "/usr/share/fonts/TTF/JetBrainsMono-Regular.ttf", 0, &face);
            if (error != 0) {
                OE_ERROR("FREETYPE: Failed to load font");
                return;
            }

            FT_Set_Pixel_Sizes(face, 0, 48);  

            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

            for (unsigned char c = 0; c < 128; c++)
            {
                // load character glyph 
                if (FT_Load_Char(face, c, FT_LOAD_RENDER))
                {
                    OE_ERROR("FREETYTPE: Failed to load Glyph: {}", c);
                    continue;
                }

                // generate texture
                unsigned int 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, 
                    glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
                    glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
                    face->glyph->advance.x
                };
                characters.insert(std::pair<char, Character>(c, character));
            }

            glPixelStorei(GL_UNPACK_ALIGNMENT, 1);   

            FT_Done_Face(face);
            FT_Done_FreeType(ft);

            text_shader = OpenEngine::Shader::Create("assets/shaders/text.glsl");

            glGenVertexArrays(1, &VAO);
            glGenBuffers(1, &VBO);
            glBindVertexArray(VAO);
            glBindBuffer(GL_ARRAY_BUFFER, VBO);
            glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
            glEnableVertexAttribArray(0);
            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
            glBindBuffer(GL_ARRAY_BUFFER, 0);
            glBindVertexArray(0);
        };

        void OnUpdate() override
        {
            text_shader->Bind();
            int width = OpenEngine::Application::Get().GetWindow().GetWidth();
            int height = OpenEngine::Application::Get().GetWindow().GetHeight();

            projection = glm::ortho(0.0f, static_cast<float>(width),
                    0.0f, static_cast<float>(height));

            text_shader->SetMat4("u_ViewProjection", projection);

            float x = height - 40;
            float y = 0;

            switch (game_layer->GetGameState()) {
                case GameState::GameOver:
                    {
                        auto& window = OpenEngine::Application::Get().GetWindow();
                        int width = window.GetWidth();
                        int height = window.GetHeight();

                        float x = width / 2.0f;
                        float y = height / 2.0f;
                        RenderTextCentered("Game Over!", x, y, 1.0f, glm::vec3(0.8f, 0.2f, 0.3f));
                        break;
                    }
                case GameState::Paused:
                    RenderTextCentered("Paused", x, y, 1.0f, glm::vec3(0.8f, 0.2f, 0.3f));
                    break;
                case GameState::Playing:
                    break;
            }

            std::string s_score = "Score: " + std::to_string(game_layer->GetScore());
            std::string s_lives = std::to_string(game_layer->GetLives()) + " lives";
            RenderText(s_score, 10, OpenEngine::Application::Get().GetWindow().GetHeight() - 50, 1.0f, {0.8f, 0.2f, 0.3f});
            RenderText(
                    s_lives,
                    OpenEngine::Application::Get().GetWindow().GetWidth() - 220,
                    OpenEngine::Application::Get().GetWindow().GetHeight() - 50,
                    1.0f,
                    {0.8f, 0.2f, 0.3f});

        };

        void OnEvent(OpenEngine::Event& event) override
        {
        };

    private:
        std::map<char, Character> characters;
        OpenEngine::Ref<OpenEngine::Shader> text_shader;
        glm::mat4 projection;
        OpenEngine::Ref<Shmup> game_layer;

        unsigned int VAO, VBO;
};

#endif // OVERLAY_HPP
