#include "DefaultRendererOpenGL.hpp"
#include "BehaviourScripts/Camera.hpp"
#include "BehaviourScripts/Renderable.hpp"
#include "Debug.hpp"

#define RENDERER_MAX_SPRITES 20000
#define RENDERER_MAX_INDECIES 60000

TSE::OpenGL::DefaultRendererOpenGL::DefaultRendererOpenGL(Shader &shader)
{
    iboData = new ushort[RENDERER_MAX_INDECIES];
    InitShader(shader, true);
}

TSE::OpenGL::DefaultRendererOpenGL::~DefaultRendererOpenGL()
{
    if(vao != 0)
        glDeleteVertexArrays(1, &vao);
    delete[] iboData;
    delete ibo;
}

void TSE::OpenGL::DefaultRendererOpenGL::InitShader(Shader &s, bool init)
{
    if(!init) End();
    if(vao != 0)
    {
        glDeleteVertexArrays(1, &vao);
        vao = 0;
    }

    s.Enable();

    glGenVertexArrays(1, &vao);

    glBindVertexArray(vao);
    vbo.Bind();

    int RENDERER_SPRITE_SIZE = s.packageSize() * 3;
    int RENDERER_BUFFER_SIZE = RENDERER_SPRITE_SIZE * RENDERER_MAX_SPRITES;

    vbo.SetData(RENDERER_BUFFER_SIZE, nullptr, GL_DYNAMIC_DRAW);

    s.Enable(true);

    vbo.Unbind();
    glBindVertexArray(0);

    lastShader = &s;

    if(!init) Begin();
}

void TSE::OpenGL::DefaultRendererOpenGL::End()
{
    glUnmapBuffer(GL_ARRAY_BUFFER);
    vbo.Unbind();
}

void TSE::OpenGL::DefaultRendererOpenGL::Flush()
{
    lastShader->Flush();

    glBindVertexArray(vao);

    bool ibobound = false;
    if(!CreateIbo())
    {
        ibo->Bind();
        ibobound = true;
    }

    for (int i = 0; i < camerasToRenderWith.size(); i++)
    {
        camerasToRenderWith[i]->PreDraw(lastShader);

        lastShader->DrawCall(indexCount);

        camerasToRenderWith[i]->PostDraw();
    }

    lastShader->PostDraw();
    
    if(ibobound)
    {
        ibo->Unbind();
    }
    glBindVertexArray(0);

    indexCount = 0;
    iboOffset = 0;
}

void TSE::OpenGL::DefaultRendererOpenGL::Begin()
{
    vbo.Bind();
    bufferPointer = (float*)glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
}

void TSE::OpenGL::DefaultRendererOpenGL::Submit(const Transformable &trans, TransformationStack &stack)
{
    Submit(trans, (IShader*)lastShader, stack);
}

void TSE::OpenGL::DefaultRendererOpenGL::Submit(const Transformable &trans, IShader *shader, TransformationStack &stack)
{
        Shader* s = dynamic_cast<Shader*>(shader);
        if(lastShader == nullptr) InitShader(*s, true);
        if(!s->IsEnabled())
        {
            if(indexCount != 0)
            {
                End();
                Flush();
                Begin();
            }
            lastShader->Disable();
            InitShader(*s);
        } 
        auto* r = dynamic_cast<Renderable*>(trans.GetBehaviourScript(RENDERABLE));
        const std::vector<ushort> indecies = r->GetIndices();
        
        if(indexCount + indecies.size() > RENDERER_MAX_INDECIES)
        {
            End();
            Flush();
            Begin();
        }
        int i = 0;
        for(ushort index : indecies)
        {
            iboData[indexCount + i++] = index + iboOffset;
        }
        iboOffset += r->GetVertexCount();
        indexCount += indecies.size();
        
        lastShader->Submit(trans, bufferPointer, stack, Redraw, *this);
}

void TSE::OpenGL::DefaultRendererOpenGL::Redraw(IRenderer &rnd)
{
    rnd.End();
    rnd.Flush();
    rnd.Begin();
}

bool TSE::OpenGL::DefaultRendererOpenGL::CreateIbo()
{
    if(indexCount == 0) return true;
    if(ibo != nullptr)
    {
        ibo->WriteData(iboData, indexCount);
    }
    else
    {
        ibo = new IndexBuffer(iboData, indexCount);
    }
    return false;
}
