#include "basicParticleShader.hpp"
#include "basicParticleShaderGLSL.hpp"
#include "BehaviourScripts/Renderable.hpp"
#include "BehaviourScripts/ParticleSystem.hpp"
#include "Color.hpp"

#define SHADER_MESH_INDEX 0
#define SHADER_POS_INDEX 1
#define SHADER_SIZE_INDEX 2
#define SHADER_ROT_INDEX 3
#define SHADER_COLOR_INDEX 4

#define SHADER_PACKAGE_SIZE sizeof(float) * (3 + 1 + 1 + 4)

TSE::OpenGL::BasicParticleShader* TSE::OpenGL::BasicParticleShader::instance = nullptr;

TSE::OpenGL::BasicParticleShader *TSE::OpenGL::BasicParticleShader::Instance()
{
    return instance;
}

void TSE::OpenGL::BasicParticleShader::Destroy()
{
    if(instance != nullptr)
    delete instance;
    instance = nullptr;
}

void TSE::OpenGL::BasicParticleShader::Init(float width, float height)
{
    std::vector<std::unique_ptr<ShaderPart>> parts;
    parts.push_back(ShaderPart::LoadFromString(vertPart, GL_VERTEX_SHADER));
    parts.push_back(ShaderPart::LoadFromString(fragPart, GL_FRAGMENT_SHADER));
    instance = new BasicParticleShader(std::move(parts));
}

TSE::OpenGL::BasicParticleShader::BasicParticleShader(std::vector<std::unique_ptr<ShaderPart>> &&parts) : Shader(parts)
{
    PackageSize = SHADER_PACKAGE_SIZE;
}

TSE::OpenGL::BasicParticleShader::~BasicParticleShader()
{
    if (meshVBO) glDeleteBuffers(1, &meshVBO);
    if (meshIBO) glDeleteBuffers(1, &meshIBO);
}

void TSE::OpenGL::BasicParticleShader::SetMesh(const void *verts, int vertCount, int stride, int floatCountPerVertex, int posOffsetBytes, GLenum primitive, const void *indices, int indexCount, GLenum indexType)
{
    GLint prevVAO = 0, prevArrayBuffer = 0, prevElementBuffer = 0;
    glGetIntegerv(GL_VERTEX_ARRAY_BINDING,         &prevVAO);
    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer);
    glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prevElementBuffer);

    if (!meshVBO) glGenBuffers(1, &meshVBO);
    glBindBuffer(GL_ARRAY_BUFFER, meshVBO);
    glBufferData(GL_ARRAY_BUFFER, vertCount * stride, verts, GL_STATIC_DRAW);

    if (indices && indexCount > 0)
    {
        if (!meshIBO) glGenBuffers(1, &meshIBO);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshIBO);
        GLsizeiptr idxSize =
            (indexType == GL_UNSIGNED_INT  ? 4 :
             indexType == GL_UNSIGNED_SHORT? 2 : 1) * indexCount;
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, idxSize, indices, GL_STATIC_DRAW);
        meshIndexCount = indexCount;
        meshIndexType  = indexType;
    }
    else
    {
        // Kein Index-Buffer
        if (meshIBO) { glDeleteBuffers(1, &meshIBO); meshIBO = 0; }
        meshIndexCount  = 0;
    }

    meshVertexCount = vertCount;
    meshStride      = stride;
    meshPosOffset   = posOffsetBytes;
    meshPosSize     = floatCountPerVertex;
    meshPrimitive   = primitive;
    meshReady       = true;

    glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, prevElementBuffer);
    glBindVertexArray(prevVAO);
}

void TSE::OpenGL::BasicParticleShader::OnEnable() const
{
    if (!meshReady)
    {
        // Fallback: unit-Quad als TRIANGLE_FAN (4 Vertices, 2D Positionen)
        const float quad[8] = { -0.5f,-0.5f,  0.5f,-0.5f,  0.5f,0.5f,  -0.5f,0.5f };
        const_cast<BasicParticleShader*>(this)->SetMesh(
            quad, 4, sizeof(float)*2, 2, 0, GL_TRIANGLE_FAN
        );
    }

    GLint prevArrayBuffer = 0;
    glGetIntegerv(GL_ARRAY_BUFFER_BINDING, &prevArrayBuffer);

    glBindBuffer(GL_ARRAY_BUFFER, meshVBO);
    glEnableVertexAttribArray(SHADER_MESH_INDEX); // LOC_QUAD/pos
    glVertexAttribPointer(SHADER_MESH_INDEX, meshPosSize, GL_FLOAT, GL_FALSE, meshStride, (void*)meshPosOffset);
    glVertexAttribDivisor(SHADER_MESH_INDEX, 0);  // per-vertex (Mesh)

    glBindBuffer(GL_ARRAY_BUFFER, prevArrayBuffer);

    //   layout 1: position (vec3)
    glEnableVertexAttribArray(SHADER_POS_INDEX);
    glVertexAttribPointer(SHADER_POS_INDEX, 3, GL_FLOAT, GL_FALSE, PackageSize, (void*)0);
    glVertexAttribDivisor(SHADER_POS_INDEX, 1);

    //   layout 2: size (float)
    glEnableVertexAttribArray(SHADER_SIZE_INDEX);
    glVertexAttribPointer(SHADER_SIZE_INDEX, 1, GL_FLOAT, GL_FALSE, PackageSize, (void*)(sizeof(float)*3));
    glVertexAttribDivisor(SHADER_SIZE_INDEX, 1);

    //   layout 3: rotationRad (float)
    glEnableVertexAttribArray(SHADER_ROT_INDEX);
    glVertexAttribPointer(SHADER_ROT_INDEX, 1, GL_FLOAT, GL_FALSE, PackageSize, (void*)(sizeof(float)*4));
    glVertexAttribDivisor(SHADER_ROT_INDEX, 1);

    //   layout 4: color (vec4)
    glEnableVertexAttribArray(SHADER_COLOR_INDEX);
    glVertexAttribPointer(SHADER_COLOR_INDEX, 4, GL_FLOAT, GL_FALSE, PackageSize, (void*)(sizeof(float)*5));
    glVertexAttribDivisor(SHADER_COLOR_INDEX, 1);
}

void TSE::OpenGL::BasicParticleShader::OnDisable() const
{
    glDisableVertexAttribArray(SHADER_MESH_INDEX);
    glDisableVertexAttribArray(SHADER_POS_INDEX);
    glDisableVertexAttribArray(SHADER_SIZE_INDEX);
    glDisableVertexAttribArray(SHADER_ROT_INDEX);
    glDisableVertexAttribArray(SHADER_COLOR_INDEX);
}

void TSE::OpenGL::BasicParticleShader::OnFlush()
{
}

void TSE::OpenGL::BasicParticleShader::OnDrawCall(int indexCount)
{
    if (instanceCount <= 0) return;

    GLint prevElementBuffer = 0;
    glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING, &prevElementBuffer);

    if (meshIBO && meshIndexCount > 0)
    {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, meshIBO);
        glDrawElementsInstanced(meshPrimitive, meshIndexCount, meshIndexType, (void*)0, instanceCount);
    }
    else
    {
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
        glDrawArraysInstanced(meshPrimitive, 0, meshVertexCount, instanceCount);
    }

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)prevElementBuffer);
    instanceCount = 0;
}

void TSE::OpenGL::BasicParticleShader::OnSubmit(const Transformable &t, float *&target, TransformationStack &stack, void (*restartDrawcall)(IRenderer &), IRenderer &rnd)
{
    auto* r = dynamic_cast<Renderable*>(t.GetBehaviourScript(RENDERABLE));
    if (!r) return;

    auto* ps = dynamic_cast<ParticleSystem*>(t.GetBehaviourScript(PARTICLESYSTEM));
    if (!ps) return;

    const std::vector<TSE::Particle*>& particles = ps->GetParticles();

    for(auto particle : particles)
    {
        *target++ = particle->position.x;
        *target++ = particle->position.y;
        *target++ = particle->position.z;
        *target++ = particle->size;
        *target++ = particle->rotationRad;
        *target++ = particle->color.r;
        *target++ = particle->color.g;
        *target++ = particle->color.b;
        *target++ = particle->color.a;

        ++instanceCount;
    }
}
