#include "RHI/OpenglDrv/GLRHICommond.h"


#include <glm/glm.hpp>
#include <glm/ext.hpp>

#include "RHI/OpenglDrv/gl.h"
#include "RHI/RHI.h"
#include "GLHandleMapper.h"

GLVAOCreateCommand::GLVAOCreateCommand(
    Mesh* mesh,
    uint32 vaoHandle,
    uint32 vboHandle,
    uint32 eboHandle
)
    : RHICommond(RHICommond::Type::CREATE_VAO)
    , vaoHandle_(vaoHandle)
    , vboHandle_(vboHandle)
    , eboHandle_(eboHandle) {
    const Mesh::Vertex* vertex = mesh->GetVertices();
    uint32 count = mesh->GetVertexCount() * sizeof(Mesh::Vertex);

    vertex_ = new unsigned char[count] {0};
    vertexCount_ = mesh->GetVertexCount();
    memcpy(vertex_, vertex, count);

    const uint32* indices = mesh->GetIndices();
    if (nullptr == indices) {
        return;
    }

    count = mesh->GetIndexCount();
    indices_ = new uint32[count]{ 0 };
    indicesCount_ = mesh->GetIndexCount();
    memcpy(indices_, indices, count * sizeof(uint32));
}

GLVAOCreateCommand::~GLVAOCreateCommand() {
    if (nullptr != vertex_) {
        delete[] vertex_;
    }

    if (nullptr != indices_) {
        delete[] indices_;
    }
}

void GLVAOCreateCommand::OnExecute() {
    glCreateVertexArrays(1, &vao_);
    glBindVertexArray(vao_);

    glCreateBuffers(1, &vbo_);
    const uint32 vertexCount = vertexCount_ * sizeof(Mesh::Vertex);
    glNamedBufferStorage(vbo_, vertexCount, nullptr, GL_DYNAMIC_STORAGE_BIT);
    glNamedBufferSubData(vbo_, 0, vertexCount, vertex_);
    GLHandleMapper::MapVBOHandle(vboHandle_, vbo_);

    glVertexArrayVertexBuffer(vao_, 0, vbo_, 0, sizeof(Mesh::Vertex));

    glEnableVertexArrayAttrib(vao_, 0);
    glVertexArrayAttribFormat(vao_, 0, 3, GL_FLOAT, GL_FALSE, offsetof(Mesh::Vertex, position_));
    glVertexArrayAttribBinding(vao_, 0, 0);

    glEnableVertexArrayAttrib(vao_, 1);
    glVertexArrayAttribFormat(vao_, 1, 4, GL_FLOAT, GL_FALSE, offsetof(Mesh::Vertex, color_));
    glVertexArrayAttribBinding(vao_, 1, 0);

    glEnableVertexArrayAttrib(vao_, 2);
    glVertexArrayAttribFormat(vao_, 2, 2, GL_FLOAT, GL_FALSE, offsetof(Mesh::Vertex, uv_));
    glVertexArrayAttribBinding(vao_, 2, 0);

    glEnableVertexArrayAttrib(vao_, 3);
    glVertexArrayAttribFormat(vao_, 3, 3, GL_FLOAT, GL_FALSE, offsetof(Mesh::Vertex, normal_));
    glVertexArrayAttribBinding(vao_, 3, 0);
    GLHandleMapper::MapVAOHandle(vaoHandle_, vao_);

    if (nullptr == indices_) {
        return;
    }

    glCreateBuffers(1, &ebo_);
    const uint32 indicesCount = indicesCount_ * sizeof(uint32);
    glNamedBufferStorage(ebo_, indicesCount, nullptr, GL_DYNAMIC_STORAGE_BIT);
    glNamedBufferSubData(ebo_, 0, indicesCount, indices_);
    GLHandleMapper::MapEBOHandle(eboHandle_, ebo_);
}

GLDrawArrayCommand::GLDrawArrayCommand(uint32 vaoHandle, uint32 vaoStart, uint32 vaoCount)
    : RHICommond(RHICommond::Type::DRAW_ARRAYS)
    , vaoHandle_(vaoHandle)
    , vaoStart_(vaoStart)
    , vaoCount_(vaoCount) {}

void GLDrawArrayCommand::OnExecute() {
    uint32 vao = GLHandleMapper::GetVAO(vaoHandle_);
    glBindVertexArray(vao);
    glDrawArrays(GL_TRIANGLES, vaoStart_, vaoCount_);
    glBindVertexArray(0);
}

GLDrawElementsCommand::GLDrawElementsCommand(uint32 vaoHandle, uint32 eboHandle, uint32 eboCount)
    : RHICommond(RHICommond::Type::DRAW_ELEMENTS)
    , vaoHandle_(vaoHandle)
    , eboHandle_(eboHandle)
    , eboCount_(eboCount) {}

void GLDrawElementsCommand::OnExecute() {
    uint32 vao = GLHandleMapper::GetVAO(vaoHandle_);
    uint32 ebo = GLHandleMapper::GetEBO(eboHandle_);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, 1);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glDrawElements(GL_TRIANGLES, eboCount_, GL_UNSIGNED_INT, 0);
    glBindVertexArray(0);
}

GLVAODeleteCommand::GLVAODeleteCommand(uint32 vaoHandle, uint32 vboHandle, uint32 eboHandle)
    : RHICommond(RHICommond::Type::DELETE_VAO)
    , vaoHandle_(vaoHandle)
    , vboHandle_(vboHandle)
    , eboHandle_(eboHandle) {}

void GLVAODeleteCommand::OnExecute() {
    if (0 != eboHandle_) {
        uint32 ebo = GLHandleMapper::GetEBO(eboHandle_);
        if (0 != ebo && glIsBuffer(ebo)) {
            GLHandleMapper::UnmapEBOHandle(eboHandle_);
            glDeleteBuffers(1, &ebo);
        }
    }

    if (0 != vboHandle_) {
        uint32 vbo = GLHandleMapper::GetVBO(vboHandle_);
        if (0 != vbo && glIsBuffer(vbo)) {
            GLHandleMapper::UnmapVBOHandle(vboHandle_);
            glDeleteBuffers(1, &vbo);
        }
    }

    if (0 != vaoHandle_) {
        uint32 vao = GLHandleMapper::GetVAO(vaoHandle_);
        if (0 != vao && glIsVertexArray(vao)) {
            GLHandleMapper::UnmapVAOHandle(vaoHandle_);
            glDeleteVertexArrays(1, &vao);
        }
    }
}

GLShaderCreateCommand::GLShaderCreateCommand(uint32 shaderHandle, uint32 type, const std::string& shader)
    : RHICommond(RHICommond::Type::CREATE_SHADER)
    , shaderHandle_(shaderHandle)
    , type_(type)
    , shader_(shader) {}

void GLShaderCreateCommand::OnExecute() {
    uint32 handle = glCreateShader(type_);
    const char* shaderSource = shader_.c_str();
    glShaderSource(handle, 1, &shaderSource, nullptr);
    glCompileShader(handle);

    char buffer[8192];
    GLsizei length = 0;
    glGetShaderInfoLog(handle, sizeof(buffer), &length, buffer);

    if (length) {
        ERRORLOG(" CompileShader error {}", buffer);
        assert(false);
        return;
    }
    GLHandleMapper::MapShaderHandle(shaderHandle_, handle);
}

GLShaderDeleteCommand::GLShaderDeleteCommand(uint32 shaderHandle)
    : RHICommond(RHICommond::Type::DELETE_SHADER)
    , shaderHandle_(shaderHandle) {}

void GLShaderDeleteCommand::OnExecute() {
    if (0 != shaderHandle_) {
        uint32 shader = GLHandleMapper::GetShader(shaderHandle_);
        if (0 != shader && glIsShader(shader)) {
            GLHandleMapper::UnmapShaderHandle(shaderHandle_);
            glDeleteShader(shader);
        }
    }
}

GLProgramCreateCommand::GLProgramCreateCommand(uint32 programHandle, std::vector<uint32> shaders)
    : RHICommond(RHICommond::Type::CREATE_PROGRAM)
    , programHandle_(programHandle)
    , shaders_(shaders) {}

void GLProgramCreateCommand::OnExecute() {
    if (shaders_.empty()) {
        return;
    }

    uint32 program = glCreateProgram();
    for (auto shader : shaders_) {
        uint32 shaderHandle = GLHandleMapper::GetShader(shader);
        if (0 != shaderHandle && glIsShader(shaderHandle)) {
            glAttachShader(program, shaderHandle);
        }
    }

    glLinkProgram(program);
    char buffer[8192];
    GLsizei length = 0;
    glGetProgramInfoLog(program, sizeof(buffer), &length, buffer);
    if (length) {
        ERRORLOG(" LinkProgram error {}", buffer);
        glDeleteProgram(program);
        assert(false);
        return;
    }
    GLHandleMapper::MapProgramHandle(programHandle_, program);
}

GLProgramUseCommand::GLProgramUseCommand(uint32 programHandle, bool use)
    : RHICommond(RHICommond::Type::DELETE_PROGRAM)
    , programHandle_(programHandle)
    , use_(use) {}

void GLProgramUseCommand::OnExecute() {
    assert(0 != programHandle_);
    uint32 program = GLHandleMapper::GetProgram(programHandle_);
    glUseProgram(program);
}

GLProgramDeleteCommand::GLProgramDeleteCommand(uint32 programHandle)
    : RHICommond(RHICommond::Type::DELETE_PROGRAM)
    , programHandle_(programHandle) {}

void GLProgramDeleteCommand::OnExecute() {
    if (0 != programHandle_) {
        uint32 program = GLHandleMapper::GetProgram(programHandle_);
        if (0 != program && glIsProgram(program)) {
            GLHandleMapper::UnmapProgramHandle(programHandle_);
            glDeleteProgram(program);
        }
    }
}

GLStartCommand::GLStartCommand()
    : RHICommond(RHICommond::Type::START_FRAME) {
    const DriverSettings& setting = RHI::Get()->GetDriverSettings();
    if (nullptr == setting.getBufferSize) {
        return;
    }
    setting.getBufferSize(&width_, &height_);
}

void GLStartCommand::OnExecute() {
    float ratio;
    glm::mat4 model, view, projection, mvp;
    height_ = height_ == 0 ? 1 : height_;

    ratio = width_ / (float)height_;
    glViewport(0, 0, width_, height_);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(49.f / 255, 77.f / 255, 121.f / 255, 1.f);
    view = glm::lookAt(glm::vec3(0, 0, 10), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
    projection = glm::perspective(glm::radians(60.f), ratio, 1.f, 1000.f);
}

GLEndCommand::GLEndCommand()
    : RHICommond(RHICommond::Type::END_FRAME) {
    //INFOLOG("actor {}", spdlog::fmt_lib::ptr(this));
}

GLEndCommand::~GLEndCommand() {
    //INFOLOG("dctor {}", spdlog::fmt_lib::ptr(this));
}


void GLEndCommand::OnExecute() {
    const DriverSettings& setting = RHI::Get()->GetDriverSettings();
    if (nullptr == setting.renderSwapBuffers) {
        return;
    }
    setting.renderSwapBuffers();
}
