#include "OpenGlMeshPainter.h"

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

#include "TriMesh.h"
#include "LineMesh.h"

#include "OpenGlShaderProgram.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>

#include <iostream>

OpenGlMeshPainter::OpenGlMeshPainter()
{

}

void OpenGlMeshPainter::initializeShader()
{
    auto vert_shader_path = std::filesystem::path(__FILE__).parent_path() / "shaders/default.vert";
    auto frag_shader_path = std::filesystem::path(__FILE__).parent_path() / "shaders/default.frag";
    mShaderProgram = std::make_unique<OpenGlShaderProgram>(vert_shader_path, frag_shader_path);
}

void OpenGlMeshPainter::initializeBuffers()
{
    glGenBuffers(1, &mVertexBuffer);

    glGenBuffers(1, &mElementBuffer);

    glGenVertexArrays(1, &mVertexArray);
}

void OpenGlMeshPainter::paint(const std::vector<float>& verts, const std::vector<std::size_t>& elements, const std::vector<float>& color,  bool lines)
{
    glBindVertexArray(mVertexArray);

    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(float), verts.data(), GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mElementBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, elements.size() * sizeof(unsigned), elements.data(), GL_STATIC_DRAW);

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

    //glm::mat4 projection = glm::ortho(0.0f, width, 0.0f, height);
    //glUniformMatrix4fv(glGetUniformLocation(mShaderProgram->getHandle(), "projection"), 1, GL_FALSE, glm::value_ptr(projection));

    glUseProgram(mShaderProgram->getHandle());
    glBindVertexArray(mVertexArray);

    int vertexColorLocation = glGetUniformLocation(mShaderProgram->getHandle(), "ourColor");
    glUniform4f(vertexColorLocation, float(color[0]), float(color[1]), float(color[2]), float(color[3]));

    //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    if (lines)
    {
        glDrawElements(GL_LINES, elements.size(), GL_UNSIGNED_INT, 0);
    }
    else
    {
        glDrawElements( GL_TRIANGLES, elements.size(), GL_UNSIGNED_INT, 0);
    }
    glBindVertexArray(0);
}

void OpenGlMeshPainter::paint(SceneModel* model, DrawingContext* context)
{
    if (!mShaderProgram)
    {
        initializeShader();
    }

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

    auto surface = context->getSurface();
    const auto width = float(surface->getWidth());
    const auto height = float(surface->getHeight());

    auto transform = model->getTransform();
    auto vertices = model->getMesh()->getVerticesFlat<float>();
    for (std::size_t idx = 0; idx<vertices.size(); idx++)
    {
        if (idx % 3 == 0)
        {
            auto x = vertices[idx]*transform.getScale().mX + transform.getLocation().getX();
            vertices[idx] = 2*x/width - 1.0;
        }
        else if(idx%3 == 1)
        {
            auto y = vertices[idx]*transform.getScale().mY + transform.getLocation().getY();
            vertices[idx] = 1.0 - 2*y/height;
        }

    }

    std::vector<std::size_t> indices;
    const bool line_mesh = model->getMesh()->getType() == AbstractMesh::MeshType::LINE;
    if (line_mesh)
    {
        indices = dynamic_cast<LineMesh*>(model->getMesh())->getEdgeNodeIds();
    }
    else
    {
        indices = dynamic_cast<TriMesh*>(model->getMesh())->getFaceNodeIds();
    }

    auto model_color = model->getSolidMaterial().getFillColor().getAsVectorDouble();
    std::vector<float> color = {float(model_color[0]), float(model_color[1]), float(model_color[2]), float(model_color[3])};

    paint(vertices, indices, color, line_mesh);

    if (model->getShowOutline())
    {
        auto edge_indices = dynamic_cast<TriMesh*>(model->getMesh())->getEdgeNodeIds();
        paint(vertices, edge_indices, {0, 0, 0, 1}, true);
    }
}
