#include <GL/glew.h>
#include <list>
#include "particle.h"
#include "effect.h"
#include "manager.h"
#include <render/buffer.h>
#include <render/core/shader.h>
#include <render/render.h>
#include "def.h"

namespace render{
namespace fx{

    const size_t particleBufferSize = 0xFFFF;
    render::vertexArray VAO;
    render::vertexBuffer VBO;
    render::shader fxShader;

    particle *pData;
    uint32_t drawCount;

    std::list<std::shared_ptr<effect>> effectList;

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

        VBO.createEmptyBuffer(particleBufferSize*sizeof(particle), GL_STREAM_DRAW);

        VAO.bind();
        glEnableVertexAttribArray(0);
        glEnableVertexAttribArray(1);
        glEnableVertexAttribArray(2);
        glEnableVertexAttribArray(3);
        glEnableVertexAttribArray(4);
        glEnableVertexAttribArray(5);
        glEnableVertexAttribArray(6);
        VBO.bind();
        glVertexAttribPointer(0, 2, GL_FLOAT, 0, sizeof(particle), (GLuint*)(offsetof(particle, vert))); //vertex
        glVertexAttribPointer(1, 4, GL_FLOAT, 0, sizeof(particle), (GLuint*)(offsetof(particle, tex)));  //texCoord
        glVertexAttribPointer(2, 4, GL_FLOAT, 0, sizeof(particle), (GLuint*)(offsetof(particle, tex2))); //texCoord2
        glVertexAttribPointer(3, 4, GL_FLOAT, 0, sizeof(particle), (GLuint*)(offsetof(particle, color))); //color hsva
        glVertexAttribPointer(4, 2, GL_FLOAT, 0, sizeof(particle), (GLuint*)(offsetof(particle, scale))); //scale
        glVertexAttribPointer(5, 1, GL_FLOAT, 0, sizeof(particle), (GLuint*)(offsetof(particle, angle))); //angle
        glVertexAttribPointer(6, 1, GL_FLOAT, 0, sizeof(particle), (GLuint*)(offsetof(particle, blend))); //texture blend
        VAO.unbind();
        VBO.unbind();

        fxShader.load("data/shaders/particles.xml");

        initParticles();
    }

    void free()
    {
        VAO.free();
        VBO.free();
        fxShader.free();
        freeParticles();
    }

    void flush()
    {
        if(!pData)
            return;

        uint32_t addr = 0;
        do
        {
            if(drawCount <= particleBufferSize)
            {
                VBO.subData(drawCount, &pData[addr]);
                VAO.draw(GL_POINTS, 0, drawCount);
            }else
            {
                VBO.subData(particleBufferSize,&pData[addr]);
                VAO.draw(GL_POINTS, 0, particleBufferSize);
                addr += particleBufferSize;
                drawCount -= particleBufferSize;
            }
        }while(drawCount > particleBufferSize);

        pData = 0;
    }

    void draw(particle *data, uint32_t count)
    {
        render::setMode(0xFFFFFFFF);
        fxShader.bind();
        drawCount = count;
        pData = data;
        render::setMode(2);
        flush();
    }

    void update()
    {
        std::list<std::shared_ptr<effect>> removeList;
        for(std::shared_ptr<effect> &i: effectList)
        {
            if(i.unique() && !i->isRunning())
                if(!i->hasAliveParticles())
                {
                    removeList.push_back(i);
                    continue;
                }
            i->update();
        }

        for(std::shared_ptr<effect> &i: removeList)
        {
            effectList.remove(i);
            i = 0;
        }
        removeList.clear();
    }

    void drawAll()
    {
        for(std::shared_ptr<effect> &i: effectList)
            i->draw();
    }

    void drawMissing()
    {
        for(std::shared_ptr<effect> &i: effectList)
            if(i.unique())
                i->draw();
    }

    std::shared_ptr<effect> &addManagedEffect(effect *eff)
    {
        if(!eff)
            throw std::runtime_error("can't create effect with nullptr");
        effectList.push_back(std::shared_ptr<effect>(eff));
        return effectList.back();
    }
}}


