#include "primitives.h"

#include "glm/mat4x4.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "gl/glError.h"
#include "gl/shaderProgram.h"
#include "gl/vertexLayout.h"
#include "gl/renderState.h"
#include "gl/texture.h"
#include "log.h"

#include "debugPrimitive_vs.h"
#include "debugPrimitive_fs.h"

#include "debugTexture_vs.h"
#include "debugTexture_fs.h"

namespace Tangram {

namespace Primitives {

static bool s_initialized;
static std::unique_ptr<ShaderProgram> s_shader;
static std::unique_ptr<VertexLayout> s_layout;

static UniformLocation s_uColor{"u_color"};
static UniformLocation s_uProj{"u_proj"};


static std::unique_ptr<ShaderProgram> s_textureShader;
static std::unique_ptr<VertexLayout> s_textureLayout;

static UniformLocation s_uTextureProj{"u_proj"};

void init() {

    // lazy init
    if (!s_initialized) {
        s_shader = std::make_unique<ShaderProgram>();

        s_shader->setShaderSource(debugPrimitive_vs, debugPrimitive_fs);

        s_layout = std::unique_ptr<VertexLayout>(new VertexLayout({
            {"a_position", 2, GL_FLOAT, false, 0},
        }));


        s_textureShader = std::make_unique<ShaderProgram>();

        s_textureShader->setShaderSource(debugTexture_vs, debugTexture_fs);

        s_textureLayout = std::unique_ptr<VertexLayout>(new VertexLayout({
            {"a_position", 2, GL_FLOAT, false, 0},
            {"a_uv", 2, GL_FLOAT, false, 0},
        }));


        s_initialized = true;
        GL::lineWidth(1.5f);
    }
}

void deinit() {

    s_shader.reset(nullptr);
    s_layout.reset(nullptr);
    s_textureShader.reset(nullptr);
    s_textureLayout.reset(nullptr);
    s_initialized = false;

}

void drawLine(RenderState& rs, const glm::vec2& _origin, const glm::vec2& _destination) {

    init();

    glm::vec2 verts[2] = {
        glm::vec2(_origin.x, _origin.y),
        glm::vec2(_destination.x, _destination.y)
    };

    if (!s_shader->use(rs)) { return; }

    GLint boundBuffer;
    GL::getIntegerv(GL_ARRAY_BUFFER_BINDING, &boundBuffer);
    rs.vertexBuffer(0);
    rs.depthTest(GL_FALSE);

    // enable the layout for the line vertices
    s_layout->enable(rs, *s_shader, 0, &verts);

    GL::drawArrays(GL_LINES, 0, 2);

    rs.vertexBuffer(boundBuffer);
}

void drawRect(RenderState& rs, const glm::vec2& _origin, const glm::vec2& _destination) {
    drawLine(rs, _origin, {_destination.x, _origin.y});
    drawLine(rs, {_destination.x, _origin.y}, _destination);
    drawLine(rs, _destination, {_origin.x, _destination.y});
    drawLine(rs, {_origin.x,_destination.y}, _origin);
}

void drawPoly(RenderState& rs, const glm::vec2* _polygon, size_t _n) {
    init();

    if (!s_shader->use(rs)) { return; }

    GLint boundBuffer;
    GL::getIntegerv(GL_ARRAY_BUFFER_BINDING, &boundBuffer);
    rs.vertexBuffer(0);
    rs.depthTest(GL_FALSE);

    // enable the layout for the _polygon vertices
    s_layout->enable(rs, *s_shader, 0, (void*)_polygon);

    GL::drawArrays(GL_LINE_LOOP, 0, _n);

    rs.vertexBuffer(boundBuffer);
}

void drawTexture(RenderState& rs, Texture& _tex, glm::vec2 _pos, glm::vec2 _dim) {
    init();

    if (!s_textureShader->use(rs)) { return; }

    GLint boundBuffer;
    GL::getIntegerv(GL_ARRAY_BUFFER_BINDING, &boundBuffer);
    rs.vertexBuffer(0);
    rs.depthTest(GL_FALSE);

    float w = _tex.width();
    float h = _tex.height();

    if (_dim != glm::vec2(0)) {
        w = _dim.x;
        h = _dim.y;
    }
    glm::vec4 vertices[6] = {
        {_pos.x, _pos.y, 0, 1},
        {_pos.x, _pos.y + h, 0, 0},
        {_pos.x + w, _pos.y, 1, 1},

        {_pos.x + w, _pos.y, 1, 1},
        {_pos.x, _pos.y + h, 0, 0},
        {_pos.x + w, _pos.y + h, 1, 0},
    };

    _tex.bind(rs, 0);

    // enable the layout for the _polygon vertices
    s_textureLayout->enable(rs, *s_textureShader, 0, (void*)vertices);

    GL::drawArrays(GL_TRIANGLES, 0, 6);

    rs.vertexBuffer(boundBuffer);
}

void setColor(RenderState& rs, unsigned int _color) {
    init();

    float r = (_color >> 16 & 0xff) / 255.0;
    float g = (_color >> 8  & 0xff) / 255.0;
    float b = (_color       & 0xff) / 255.0;

    s_shader->setUniformf(rs, s_uColor, r, g, b);
}

void setResolution(RenderState& rs, float _width, float _height) {
    init();

    glm::mat4 proj = glm::ortho(0.f, _width, _height, 0.f, -1.f, 1.f);
    s_shader->setUniformMatrix4f(rs, s_uProj, proj);
    s_textureShader->setUniformMatrix4f(rs, s_uTextureProj, proj);
}

}

}
