#include "graphics/opengl/framebuffer.hpp"

#include "graphics/opengl/texture.hpp"
#include "utils/assert.hpp"

#include <glad/gl.h>

#include <memory>
#include <utility>

using namespace kuiper::gl;

framebuffer::s_ptr framebuffer::make() noexcept {
    GLuint name = 0;
    glGenFramebuffers(1, &name);

    if (!name)
        return nullptr;

    return std::make_shared<framebuffer>(name);
}

framebuffer::s_ptr framebuffer::make_default(std::uint32_t width, std::uint32_t height) noexcept {
    if (!width || !height)
        return nullptr;

    auto fb = framebuffer::make();
    if (!fb)
        return nullptr;

    // Colour

    auto col_tex = texture::make();
    col_tex->upload(
        GL_RGBA8, static_cast<GLsizei>(width), static_cast<GLsizei>(height), GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    col_tex->set_param(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    col_tex->set_param(GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // Depth & stencil

    auto depth_tex = texture::make();
    depth_tex->upload(GL_DEPTH24_STENCIL8,
                      static_cast<GLsizei>(width),
                      static_cast<GLsizei>(height),
                      GL_DEPTH_STENCIL,
                      GL_UNSIGNED_INT_24_8,
                      nullptr);
    depth_tex->set_param(GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    depth_tex->set_param(GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    fb->attach_colour(std::move(col_tex));
    fb->attach_depth(std::move(depth_tex));

    fb->bind();
    const auto fb_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (fb_status != GL_FRAMEBUFFER_COMPLETE) {
        return nullptr;
    }

    return fb;
}

void framebuffer::destroy() noexcept {
    if (m_id) {
        glDeleteFramebuffers(1, &m_id);
        m_id = 0;
    }

    return;
}

void framebuffer::bind() const noexcept {
    return glBindFramebuffer(GL_FRAMEBUFFER, m_id);
}

void framebuffer::resize(std::uint32_t width, std::uint32_t height) noexcept {
    m_width  = width;
    m_height = height;

    for (const auto& col_tex : m_colour_texs) {
        if (col_tex == nullptr)
            continue;

        col_tex->resize(width, height);
    }

    if (m_depth_tex) {
        m_depth_tex->resize(width, height);
    }

    return;
}

void framebuffer::attach_colour(texture::s_ptr&& tex, std::uint32_t attachment_point) noexcept {
    KUIPER_ASSERT(attachment_point < framebuffer::max_colour_attachments);

    m_colour_texs[attachment_point] = std::move(tex);
    const auto& size                = m_colour_texs[attachment_point]->dimensions();
    m_width                         = size.x;
    m_height                        = size.y;

    bind();
    return glFramebufferTexture2D(GL_FRAMEBUFFER,
                                  GL_COLOR_ATTACHMENT0 + attachment_point,
                                  GL_TEXTURE_2D,
                                  m_colour_texs[attachment_point]->id(),
                                  0);
}

void framebuffer::attach_depth(texture::s_ptr&& tex) noexcept {
    m_depth_tex      = std::move(tex);
    const auto& size = m_depth_tex->dimensions();
    m_width          = size.x;
    m_height         = size.y;

    bind();
    return glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, m_depth_tex->id(), 0);
}
