#include "renderer/buffer.hpp"
#include <pch.hpp>

#include <core.hpp>
#include <opengl/opengl_vertex_array.hpp>

#include <cstdint>
#include <glad/glad.h>

namespace OpenEngine {

    static GLenum ShaderDataTypeToOpenGLBaseType(ShaderDataType type)
    {
        switch (type)
        {
            case ShaderDataType::Float: return GL_FLOAT;
            case ShaderDataType::Float2: return GL_FLOAT;
            case ShaderDataType::Float3: return GL_FLOAT;
            case ShaderDataType::Float4: return GL_FLOAT;
            case ShaderDataType::Mat3: return GL_FLOAT;
            case ShaderDataType::Mat4: return GL_FLOAT;
            case ShaderDataType::Int: return GL_INT;
            case ShaderDataType::Int2: return GL_INT;
            case ShaderDataType::Int3: return GL_INT;
            case ShaderDataType::Int4: return GL_INT;
            case ShaderDataType::Bool: return GL_BOOL;
        }

        OE_CORE_ASSERT(false, "Unknown ShaderDataType!");
        return 0;
    }

    OpenGLVertexArray::OpenGLVertexArray()
    {
        OE_PROFILE_FUNCTION();

        glCreateVertexArrays(1, &id);
    }

    OpenGLVertexArray::~OpenGLVertexArray()
    {
        OE_PROFILE_FUNCTION();

        glDeleteVertexArrays(1, &id);
    }

    void OpenGLVertexArray::Bind() const
    {
        glBindVertexArray(id);
    }
    void OpenGLVertexArray::UnBind() const
    {
        glBindVertexArray(0);
    }

    void OpenGLVertexArray::AddVertexBuffer(const Ref<VertexBuffer>& vertex_buffer)
    {
        OE_PROFILE_FUNCTION();

        glBindVertexArray(id);
        vertex_buffer->Bind();

        OE_CORE_ASSERT(vertex_buffer->GetLayout().GetElements().size(), "Vertex Buffer has no layout!");

        const auto& layout = vertex_buffer->GetLayout();
        for (const auto& element : layout) {
            glEnableVertexAttribArray(index);

            if (element.type == ShaderDataType::Int) {
                glVertexAttribIPointer(index,
                        element.GetComponentCount(),
                        ShaderDataTypeToOpenGLBaseType(element.type),
                        layout.GetStride(),
                        (const void*)(intptr_t)element.offset);
            } else {
                glVertexAttribPointer(index,
                        element.GetComponentCount(),
                        ShaderDataTypeToOpenGLBaseType(element.type),
                        element.normalized? GL_TRUE : GL_FALSE,
                        layout.GetStride(),
                        (const void*)(intptr_t)element.offset);
            }
            index++;
        }

        vertex_buffers.emplace_back(vertex_buffer);
    }

    void OpenGLVertexArray::SetIndexBuffer(const Ref<IndexBuffer>& index_buffer)
    {
        OE_PROFILE_FUNCTION();

        glBindVertexArray(id);
        index_buffer->Bind();

        this->index_buffer = index_buffer;
    }
}
