#include <extra2d/graphics/backends/opengl/gl_buffer.h>
#include <extra2d/graphics/memory/vram_manager.h>
#include <extra2d/utils/logger.h>
#include <cstring>

namespace extra2d {

// ============================================================================
// GLBuffer 实现
// ============================================================================

GLBuffer::GLBuffer() = default;

GLBuffer::~GLBuffer() {
    shutdown();
}

bool GLBuffer::init(const BufferDesc& desc) {
    if (bufferID_ != 0) {
        shutdown();
    }

    type_ = desc.type;
    usage_ = desc.usage;
    size_ = desc.size;
    target_ = convertType(type_);
    glUsage_ = convertUsage(usage_);

    // 生成缓冲区
    glGenBuffers(1, &bufferID_);
    if (bufferID_ == 0) {
        E2D_LOG_ERROR("Failed to generate OpenGL buffer");
        return false;
    }

    // 绑定并分配缓冲区
    glBindBuffer(target_, bufferID_);
    glBufferData(target_, static_cast<GLsizeiptr>(size_), desc.initialData, glUsage_);
    glBindBuffer(target_, 0);

    // 追踪显存使用
    VRAMMgr::get().allocBuffer(size_);

    E2D_LOG_DEBUG("GLBuffer created: ID={}, Size={}, Type={}, Usage={}",
                  bufferID_, size_, static_cast<int>(type_), static_cast<int>(usage_));

    return true;
}

void GLBuffer::shutdown() {
    if (bufferID_ != 0) {
        if (mapped_) {
            unmap();
        }
        // 释放显存追踪
        VRAMMgr::get().freeBuffer(size_);
        glDeleteBuffers(1, &bufferID_);
        E2D_LOG_DEBUG("GLBuffer destroyed: ID={}", bufferID_);
        bufferID_ = 0;
    }
    size_ = 0;
    mapped_ = false;
    mappedPtr_ = nullptr;
}

void GLBuffer::bind() {
    if (bufferID_ != 0) {
        glBindBuffer(target_, bufferID_);
    }
}

void GLBuffer::unbind() {
    glBindBuffer(target_, 0);
}

void GLBuffer::setData(const void* data, size_t size) {
    if (bufferID_ == 0) {
        return;
    }

    bind();

    // 如果大小相同，使用 glBufferSubData 更高效
    if (size == size_) {
        glBufferSubData(target_, 0, static_cast<GLsizeiptr>(size), data);
    } else {
        // 大小不同，重新分配
        size_ = size;
        glBufferData(target_, static_cast<GLsizeiptr>(size_), data, glUsage_);
    }

    unbind();
}

void GLBuffer::updateData(const void* data, size_t offset, size_t size) {
    if (bufferID_ == 0 || data == nullptr || size == 0) {
        return;
    }

    if (offset + size > size_) {
        E2D_LOG_WARN("GLBuffer updateData out of bounds: offset={}, size={}, bufferSize={}",
                     offset, size, size_);
        return;
    }

    bind();
    glBufferSubData(target_, static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size), data);
    unbind();
}

void* GLBuffer::map() {
    if (bufferID_ == 0 || mapped_) {
        return nullptr;
    }

    bind();

    // 使用 glMapBufferRange 替代 glMapBuffer，更现代且安全
    GLbitfield access = GL_MAP_WRITE_BIT;
    if (usage_ == BufferUsage::Dynamic || usage_ == BufferUsage::Stream) {
        access |= GL_MAP_INVALIDATE_BUFFER_BIT;  // 暗示驱动可以丢弃旧数据
    }

    mappedPtr_ = glMapBufferRange(target_, 0, static_cast<GLsizeiptr>(size_), access);
    if (mappedPtr_) {
        mapped_ = true;
    } else {
        E2D_LOG_ERROR("Failed to map GLBuffer");
    }

    return mappedPtr_;
}

void GLBuffer::unmap() {
    if (!mapped_ || bufferID_ == 0) {
        return;
    }

    glUnmapBuffer(target_);
    mapped_ = false;
    mappedPtr_ = nullptr;
    unbind();
}

GLenum GLBuffer::convertUsage(BufferUsage usage) {
    switch (usage) {
        case BufferUsage::Static:
            return GL_STATIC_DRAW;
        case BufferUsage::Dynamic:
            return GL_DYNAMIC_DRAW;
        case BufferUsage::Stream:
            return GL_STREAM_DRAW;
        default:
            return GL_STATIC_DRAW;
    }
}

GLenum GLBuffer::convertType(BufferType type) {
    switch (type) {
        case BufferType::Vertex:
            return GL_ARRAY_BUFFER;
        case BufferType::Index:
            return GL_ELEMENT_ARRAY_BUFFER;
        case BufferType::Uniform:
            return GL_UNIFORM_BUFFER;
        default:
            return GL_ARRAY_BUFFER;
    }
}

} // namespace extra2d
