#include "gfx/gl_loader.h"
#include "gfx/gl_framebuffer.h"

namespace fn::gfx {

static void create_tex(Framebuffer& f) {
    glGenTextures(1, &f.tex);
    glBindTexture(GL_TEXTURE_2D, f.tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, f.width, f.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glBindTexture(GL_TEXTURE_2D, 0);
}

static void create_depth_rbo(Framebuffer& f) {
    glGenRenderbuffers(1, &f.depth_rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, f.depth_rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, f.width, f.height);
    glBindRenderbuffer(GL_RENDERBUFFER, 0);
}

void fb_init(Framebuffer& f) {
    f.width     = 1;
    f.height    = 1;
    f.has_depth = false;
    create_tex(f);
    glGenFramebuffers(1, &f.fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, f.fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, f.tex, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void fb_init_depth(Framebuffer& f) {
    f.width     = 1;
    f.height    = 1;
    f.has_depth = true;
    create_tex(f);
    create_depth_rbo(f);
    glGenFramebuffers(1, &f.fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, f.fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, f.tex, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, f.depth_rbo);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void fb_resize(Framebuffer& f, int w, int h) {
    if (w == f.width && h == f.height) return;
    f.width  = w;
    f.height = h;

    // Recreate color texture.
    if (f.tex) glDeleteTextures(1, &f.tex);
    f.tex = 0;
    create_tex(f);
    glBindFramebuffer(GL_FRAMEBUFFER, f.fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, f.tex, 0);

    // Resize depth renderbuffer in-place (no need to recreate).
    if (f.has_depth && f.depth_rbo) {
        glBindRenderbuffer(GL_RENDERBUFFER, f.depth_rbo);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, f.width, f.height);
        glBindRenderbuffer(GL_RENDERBUFFER, 0);
        // Re-attach in case it was lost (should be stable across storage resize, but be safe).
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, f.depth_rbo);
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void fb_destroy(Framebuffer& f) {
    if (f.fbo)       { glDeleteFramebuffers(1, &f.fbo);        f.fbo = 0; }
    if (f.tex)       { glDeleteTextures(1, &f.tex);            f.tex = 0; }
    if (f.depth_rbo) { glDeleteRenderbuffers(1, &f.depth_rbo); f.depth_rbo = 0; }
    f.width     = 0;
    f.height    = 0;
    f.has_depth = false;
}

} // namespace fn::gfx
