#include <GL/glew.h>
#include "sprite.h"
#include <render/render.h>
#include <render/buffer.h>
#include <render/core/shader.h>
#include <render/utils/buffer.h>
#include <color.h>

#include "sprite/image.h"

namespace render{
namespace sprite{

constexpr uint bufferSize = 0xFFFFF;
render::utils::buffer<vert> buffer(bufferSize);

render::vertexArray VAO;
render::vertexBuffer VBO;
render::shader spriteShader;

void init()
{
    VAO.gen();
    VBO.gen();

    VBO.createEmptyBuffer(bufferSize*sizeof(vert), GL_STREAM_DRAW);

    VAO.bind();
    glEnableVertexAttribArray(0);
    glEnableVertexAttribArray(1);
    glEnableVertexAttribArray(2);
    VBO.bind();
    glVertexAttribPointer(0, 2, GL_FLOAT, 0, sizeof(vert), (GLuint*)(offsetof(vert, vert))); //vertex
    glVertexAttribPointer(1, 2, GL_FLOAT, 0, sizeof(vert), (GLuint*)(offsetof(vert, uv))); //vertex
    glVertexAttribPointer(2, 4, GL_FLOAT, 0, sizeof(vert), (GLuint*)(offsetof(vert, color))); //color rgba
    VAO.unbind();
    VBO.unbind();

    spriteShader.load("data/shaders/sprite.xml");
}

void free()
{
    VAO.free();
    VBO.free();
    spriteShader.free();
}

void draw(const vert *data, uint count)
{
    render::setMode(5);
    spriteShader.bind();
    VBO.subData(count, data);
    VAO.draw(GL_TRIANGLES, 0, count);
    VAO.unbind();
}

void draw(render::utils::buffer<vert> *b)
{
    //return;
    if(!b->count)
        return;
    render::setMode(5);
    spriteShader.bind();
    VBO.subData(b->count, b->data);
    VAO.draw(GL_TRIANGLES, 0, b->count);
}

void saveBuffer(render::utils::buffer<vert> *b)
{
    *b = buffer;
}

void flush()
{
    if(!buffer.count)
        return;
    spriteShader.bind();
    VBO.subData(buffer.count, buffer.data);
    VAO.draw(GL_TRIANGLES, 0, buffer.count);
    VAO.unbind();
    buffer.count = 0;
}

void add(const vert *vert, uint count)
{
    render::setMode(5);
    if(!buffer.add(vert, count))
    {
        flush();
        buffer.add(vert, count);
    }
}

///======================

void image::draw(const vec2f &pos) const noexcept
{
    if(!_size.y) return;
    const vec2f offset[6] = {vec2f(0), vec2f(1, 0), vec2f(1), vec2f(1), vec2f(0, 1), vec2f(0)};
    const vec2f voffset[6] = {vec2f(0, 1), vec2f(1), vec2f(1, 0), vec2f(1, 0), vec2f(0), vec2f(0, 1)};
    bind();
    vert data[6];
    for(int i = 0; i < 6; i++)
    {
        data[i].vert = pos+voffset[i]*_size*scale;
        data[i].uv = offset[i];
        data[i].color = color;
    }
    add(data, 6);
}

void image::draw(const vec2f &pos, const vec2u &part, const vec2u &size) const noexcept
{
    if(!_size.y) return;
    const vec2f offset[6] = {vec2f(0, 1), vec2f(1), vec2f(1, 0), vec2f(1, 0), vec2f(0), vec2f(0, 1)};
    vert data[6];
    for(int i = 0; i < 6; i++)
    {
        data[i].vert = pos+(size*offset[i])*_size/parts*scale;
        data[i].uv = (offset[i]*size+part)/parts;
        data[i].color = color;
    }
    bind();
    add(data, 6);
}

void textureAtlas::draw(const vec2f &pos, icon &p) const noexcept
{
    constexpr vec2f offset[6] = {vec2f(0, 1), vec2f(1), vec2f(1, 0),
                                 vec2f(1, 0), vec2f(0), vec2f(0, 1)};
    if(p._parent != this)
        return;
    if(p.angle != p._angle || p.center != p._center || p.size != p._size)
        mathRotate(p);
    vert data[6];
    for(int i = 0; i < 6; i++)
    {
        data[i].vert = pos+p._rotMatrix[i]*p.scale;
        data[i].uv = (p._pos+offset[i]*p._isize)/_size;
        data[i].color = p.color;
    }
    bind();
    add(data, 6);
}

void textureAtlas::draw(const vec2f &pos, const icon &p) const noexcept
{
    if(p._parent != this)
        return;
    constexpr vec2f offset[6] = {vec2f(0, 1), vec2f(1), vec2f(1, 0),
                                 vec2f(1, 0), vec2f(0), vec2f(0, 1)};
    float msin, mcos;
    sincosf(p.angle, &msin, &mcos);

    vert data[6];
    for(int i = 0; i < 6; i++)
    {
        vec2f voffset = (offset[i]-p.center)*p.size*p.scale;
        data[i].vert.x = pos.x+voffset.x*mcos-voffset.y*msin;
        data[i].vert.y = pos.y+voffset.x*msin+voffset.y*mcos;

        data[i].uv = (p._pos+offset[i]*p._isize)/_size;
        data[i].color = p.color;
    }
    bind();
    add(data, 6);
}

}}
