#include <GL/glew.h>
#include "render.h"
#include "gui.h"
#include "core/buffer/vertexArray.h"
#include "core/buffer/vertexBuffer.h"
#include "core/texture/texture.h"
#include "shader.h"


namespace render{
namespace gui{

struct guiVert
{
    vec2f vert;
    float type;
    vec2f uv;
    color::rgba color, color2;
};

const uint guiBufferSize = 0xfff;
guiVert *guiBuffer;
uint guiCount;

render::vertexArray guiVAO, textVAO;
render::vertexBuffer guiVBO, textVBO;
render::shader guiShader, textShader;
render::texture mask, color;

void init()
{
    guiVAO.gen();
    guiVBO.gen();
    textVAO.gen();
    textVBO.gen();

    guiVBO.createEmptyBuffer(guiBufferSize*sizeof(guiVert), GL_STREAM_DRAW);
    textVBO.createEmptyBuffer(guiBufferSize*sizeof(textVert), GL_STREAM_DRAW);

    guiVAO.bind();
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);
    glEnableVertexAttribArray(3);
    glEnableVertexAttribArray(4);
    guiVBO.bind();
    glVertexAttribPointer(0, 2, GL_FLOAT, 0, sizeof(guiVert), (GLuint*)(offsetof(guiVert, vert)));
    glVertexAttribPointer(1, 1, GL_FLOAT, 0, sizeof(guiVert), (GLuint*)(offsetof(guiVert, type)));
    glVertexAttribPointer(2, 2, GL_FLOAT, 0, sizeof(guiVert), (GLuint*)(offsetof(guiVert, uv)));
    glVertexAttribPointer(3, 4, GL_FLOAT, 0, sizeof(guiVert), (GLuint*)(offsetof(guiVert, color)));
    glVertexAttribPointer(4, 4, GL_FLOAT, 0, sizeof(guiVert), (GLuint*)(offsetof(guiVert, color2)));

    textVAO.bind();
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);
    glEnableVertexAttribArray(3);
    textVBO.bind();
    glVertexAttribPointer(0, 2, GL_FLOAT, 0, sizeof(textVert), (GLuint*)(offsetof(textVert, vert)));
    glVertexAttribPointer(1, 1, GL_FLOAT, 0, sizeof(textVert), (GLuint*)(offsetof(textVert, scale)));
    glVertexAttribPointer(2, 2, GL_FLOAT, 0, sizeof(textVert), (GLuint*)(offsetof(textVert, uv)));
    glVertexAttribPointer(3, 4, GL_FLOAT, 0, sizeof(textVert), (GLuint*)(offsetof(textVert, color)));
    textVAO.unbind();
    textVBO.unbind();

    mask.load("data/textures/guiShape.png", 1);
    color.load("data/textures/guiColor.png", 1);

    mask.parameteri(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    mask.parameteri(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    color.parameteri(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    color.parameteri(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    textShader.load("data/shaders/text.xml");
    guiShader.load("data/shaders/gui.xml");
    guiShader.uniform1i("tex1", 0);
    guiShader.uniform1i("tex2", 1);

    guiBuffer = new guiVert[guiBufferSize];

    guiCount = 0;
}

void free()
{
    guiVAO.free();
    guiVBO.free();
    textVAO.free();
    textVBO.free();
    guiShader.free();
    textShader.free();
    delete [] guiBuffer;
}

void flush()
{
    guiShader.bind();
    mask.bind(0);
    color.bind(1);
    guiVBO.subData(guiCount, guiBuffer);
    guiVAO.draw(GL_TRIANGLES, 0, guiCount);
    guiCount = 0;
}

void gui(const vec2f &pos, float type, const vec2f &size, const color::rgba &background, const color::rgba &borderColor, const vec4f &border)
{
    render::setMode(4);
    if(guiCount + 54 >= guiBufferSize)
        flush();
    constexpr vec2f offset[6] = {vec2f(0, 1), vec2f(1), vec2f(1, 0),
                                 vec2f(1, 0), vec2f(0), vec2f(0, 1)};
    if(size.x == 0)
        return;
    constexpr vec2f texCoord[9][6] =
        {{vec2f(0, 0), vec2f(1, 0), vec2f(1, 1), vec2f(1, 1), vec2f(0, 1), vec2f(0, 0)},
         {vec2f(1, 0), vec2f(1, 0), vec2f(1, 1), vec2f(1, 1), vec2f(1, 1), vec2f(1, 0)},
         {vec2f(1, 0), vec2f(0, 0), vec2f(0, 1), vec2f(0, 1), vec2f(1, 1), vec2f(1, 0)},

         {vec2f(0, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(0, 0), vec2f(0, 0)},
         {vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0)},
         {vec2f(1, 0), vec2f(0, 0), vec2f(0, 0), vec2f(0, 0), vec2f(1, 0), vec2f(1, 0)},

         {vec2f(0, 1), vec2f(1, 1), vec2f(1, 0), vec2f(1, 0), vec2f(0, 0), vec2f(0, 1)},
         {vec2f(1, 1), vec2f(1, 1), vec2f(1, 0), vec2f(1, 0), vec2f(1, 0), vec2f(1, 1)},
         {vec2f(1, 1), vec2f(0, 1), vec2f(0, 0), vec2f(0, 0), vec2f(1, 0), vec2f(1, 1)}};

    vec2f posList[9] = {vec2f(-border.left, -border.top), vec2f(0, -border.top), vec2f(size.x, -border.top),
                        vec2f(-border.left, 0), vec2f(0, 0), vec2f(size.x, 0),
                        vec2f(-border.left, size.y), vec2f(0, size.y), vec2f(size.x, size.y)};

    vec2f sizeList[9] = {vec2f(border.left, border.top), vec2f(size.x, border.top), vec2f(border.right, border.top),
                         vec2f(border.left, size.y), vec2f(size.x, size.y), vec2f(border.right, size.y),
                         vec2f(border.left, border.bottom), vec2f(size.x, border.bottom), vec2f(border.right, border.bottom)};

    for(int c = 0; c < 9; c++)
        for(int i = 0; i < 6; i++)
        {
            guiBuffer[guiCount].vert = offset[i]*sizeList[c]+posList[c]+pos;
            guiBuffer[guiCount].type = type;
            guiBuffer[guiCount].uv = texCoord[c][i];
            guiBuffer[guiCount].color = background;
            guiBuffer[guiCount++].color2 = borderColor;
        }
}

void text(const textVert *data, uint count)
{
    render::flush();
    textShader.bind();
    textVBO.subData(count, data);
    textVAO.draw(GL_TRIANGLES, 0, count);
}

}}
