#include "OpenGlTextPainter.h"

#include "DrawingContext.h"
#include "DrawingSurface.h"

#include "FontsManager.h"
#include "FontGlyph.h"

#include "OpenGlFontTexture.h"
#include "OpenGlShaderProgram.h"
#include "TextData.h"

#include "SceneText.h"

#include "File.h"

#ifdef _WIN32
#include <windows.h>
#endif

#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glx.h>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

OpenGlTextPainter::OpenGlTextPainter()
{

}

void OpenGlTextPainter::initializeShader()
{
    auto vert_shader_path = std::filesystem::path(__FILE__).parent_path() / "shaders/text.vert";
    auto frag_shader_path = std::filesystem::path(__FILE__).parent_path() / "shaders/text.frag";

    mShaderProgram = std::make_unique<OpenGlShaderProgram>(vert_shader_path, frag_shader_path);
}

void OpenGlTextPainter::initializeTextures(const TextData& textData, DrawingContext* context)
{
    for (auto line : textData.mLines)
    {
        for (auto c : line)
        {
            if (auto iter = mFontTextures.find(c); iter == mFontTextures.end())
            {
                auto glyph = context->getFontsManager()->getGlyph(textData.mFont.getFaceName(), textData.mFont.getSize(), c);
                auto new_texture = std::make_unique<OpenGlFontTexture>(glyph);
                mFontTextures[c] = std::move(new_texture);
            }
        }
    }
}

void OpenGlTextPainter::initializeBuffers()
{
    glGenVertexArrays(1, &mVertexArray);
    glGenBuffers(1, &mVertexBuffer);

    glBindVertexArray(mVertexArray);
    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, nullptr, GL_DYNAMIC_DRAW);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}

void OpenGlTextPainter::paint(SceneText* text, DrawingContext* context)
{
    if (!mShaderProgram)
    {
        initializeShader();
    }

    if (mVertexArray == 0)
    {
        initializeBuffers();
    }

    auto text_data = text->getTextData();

    initializeTextures(text_data, context);

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

    glUseProgram(mShaderProgram->getHandle());

    glUniform3f(glGetUniformLocation(mShaderProgram->getHandle(), "textColor"), 0.0, 0.0, 0.0);

    const auto width = float(context->getSurface()->getWidth());
    const auto height = float(context->getSurface()->getHeight());
    glm::mat4 projection = glm::ortho(0.0f, width, 0.0f, height);

    glUniformMatrix4fv(glGetUniformLocation(mShaderProgram->getHandle(), "projection"), 1, GL_FALSE, glm::value_ptr(projection));
    glActiveTexture(GL_TEXTURE0);
    glBindVertexArray(mVertexArray);

    auto transform = text->getTransform();

    float line_delta = 20;
    float line_offset = 0;
    for (auto line : text_data.mLines)
    {
        float x = transform.getLocation().getX();
        const float y = height - line_offset - transform.getLocation().getY();
        for (auto c :  line)
        {
            auto texture = mFontTextures[c].get();

            float xpos = x + texture->getGlyph()->getBearingX();
            float ypos = y - (int(texture->getGlyph()->getHeight()) - texture->getGlyph()->getBearingY());

            float w = texture->getGlyph()->getWidth();
            float h = texture->getGlyph()->getHeight();
            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 }
            };
            glBindTexture(GL_TEXTURE_2D, texture->getHandle());

            glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
            glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
            glBindBuffer(GL_ARRAY_BUFFER, 0);

            glDrawArrays(GL_TRIANGLES, 0, 6);

            auto offset = (texture->getGlyph()->getAdvanceX() >> 6); // bitshift by 6 to get value in pixels (2^6 = 64)
            x += offset;
        }
        line_offset += line_delta;
    }
    glBindVertexArray(0);
    glBindTexture(GL_TEXTURE_2D, 0);
}
