// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3

#include "framebuffer.h"

#ifdef DRAGONX_HAS_GLAD
#include <glad/gl.h>
#include <cstdio>
#include "../../util/logger.h"

// GL 3.0 constants — may be missing from some GLAD configurations
#ifndef GL_RGBA16F
#define GL_RGBA16F 0x881A
#endif
#ifndef GL_HALF_FLOAT
#define GL_HALF_FLOAT 0x140B
#endif

namespace dragonx {
namespace ui {
namespace effects {

// ============================================================================
// Framebuffer Implementation (GLEW Available)
// ============================================================================

Framebuffer::Framebuffer() = default;

Framebuffer::~Framebuffer()
{
    destroy();
}

Framebuffer::Framebuffer(Framebuffer&& other) noexcept
    : fbo_(other.fbo_)
    , colorTexture_(other.colorTexture_)
    , depthRbo_(other.depthRbo_)
    , width_(other.width_)
    , height_(other.height_)
    , isComplete_(other.isComplete_)
{
    other.fbo_ = 0;
    other.colorTexture_ = 0;
    other.depthRbo_ = 0;
    other.width_ = 0;
    other.height_ = 0;
    other.isComplete_ = false;
}

Framebuffer& Framebuffer::operator=(Framebuffer&& other) noexcept
{
    if (this != &other) {
        destroy();
        
        fbo_ = other.fbo_;
        colorTexture_ = other.colorTexture_;
        depthRbo_ = other.depthRbo_;
        width_ = other.width_;
        height_ = other.height_;
        isComplete_ = other.isComplete_;
        
        other.fbo_ = 0;
        other.colorTexture_ = 0;
        other.depthRbo_ = 0;
        other.width_ = 0;
        other.height_ = 0;
        other.isComplete_ = false;
    }
    return *this;
}

bool Framebuffer::init(int width, int height, bool useFloat16)
{
    if (width <= 0 || height <= 0) {
        DEBUG_LOGF("Framebuffer::init - Invalid dimensions: %dx%d\n", width, height);
        return false;
    }
    
    // Clean up existing resources
    cleanup();
    
    width_ = width;
    height_ = height;
    useFloat16_ = useFloat16;
    
    // Generate framebuffer
    glGenFramebuffers(1, &fbo_);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
    
    // Create color texture — use RGBA16F for blur intermediates to avoid banding
    glGenTextures(1, &colorTexture_);
    glBindTexture(GL_TEXTURE_2D, colorTexture_);
    if (useFloat16) {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_HALF_FLOAT, nullptr);
    } else {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, 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);
    
    // Attach color texture to framebuffer
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture_, 0);
    
    // Create depth renderbuffer (optional, but good for completeness)
    glGenRenderbuffers(1, &depthRbo_);
    glBindRenderbuffer(GL_RENDERBUFFER, depthRbo_);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthRbo_);
    
    // Check completeness
    isComplete_ = checkComplete();
    
    // Unbind
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glBindTexture(GL_TEXTURE_2D, 0);
    glBindRenderbuffer(GL_RENDERBUFFER, 0);
    
    if (!isComplete_) {
        DEBUG_LOGF("Framebuffer::init - Framebuffer is not complete!\n");
        cleanup();
        return false;
    }
    
    return true;
}

bool Framebuffer::resize(int width, int height)
{
    if (width == width_ && height == height_) {
        return true;  // No change needed
    }
    return init(width, height, useFloat16_);
}

void Framebuffer::destroy()
{
    cleanup();
}

void Framebuffer::cleanup()
{
    if (colorTexture_) {
        glDeleteTextures(1, &colorTexture_);
        colorTexture_ = 0;
    }
    if (depthRbo_) {
        glDeleteRenderbuffers(1, &depthRbo_);
        depthRbo_ = 0;
    }
    if (fbo_) {
        glDeleteFramebuffers(1, &fbo_);
        fbo_ = 0;
    }
    width_ = 0;
    height_ = 0;
    isComplete_ = false;
}

bool Framebuffer::checkComplete()
{
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        const char* errorStr = "Unknown";
        switch (status) {
            case GL_FRAMEBUFFER_UNDEFINED: errorStr = "UNDEFINED"; break;
            case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: errorStr = "INCOMPLETE_ATTACHMENT"; break;
            case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: errorStr = "INCOMPLETE_MISSING_ATTACHMENT"; break;
            case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: errorStr = "INCOMPLETE_DRAW_BUFFER"; break;
            case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: errorStr = "INCOMPLETE_READ_BUFFER"; break;
            case GL_FRAMEBUFFER_UNSUPPORTED: errorStr = "UNSUPPORTED"; break;
            case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: errorStr = "INCOMPLETE_MULTISAMPLE"; break;
            case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: errorStr = "INCOMPLETE_LAYER_TARGETS"; break;
        }
        DEBUG_LOGF("Framebuffer error: %s (0x%X)\n", errorStr, status);
        return false;
    }
    return true;
}

void Framebuffer::bind()
{
    glBindFramebuffer(GL_FRAMEBUFFER, fbo_);
    glViewport(0, 0, width_, height_);
}

void Framebuffer::unbind()
{
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void Framebuffer::clear(float r, float g, float b, float a)
{
    glClearColor(r, g, b, a);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void Framebuffer::blitFrom(GLuint srcFbo, int srcX, int srcY, int srcWidth, int srcHeight,
                           int dstX, int dstY)
{
    glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_);
    
    glBlitFramebuffer(
        srcX, srcY, srcX + srcWidth, srcY + srcHeight,  // Source rect
        dstX, dstY, dstX + srcWidth, dstY + srcHeight,  // Dest rect
        GL_COLOR_BUFFER_BIT,
        GL_LINEAR
    );
    
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void Framebuffer::captureScreen(int viewportWidth, int viewportHeight)
{
    // Blit from default framebuffer (0) to this framebuffer
    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_);
    
    glBlitFramebuffer(
        0, 0, viewportWidth, viewportHeight,   // Source (screen)
        0, 0, width_, height_,                  // Dest (this FBO)
        GL_COLOR_BUFFER_BIT,
        GL_LINEAR
    );
    
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

// ============================================================================
// FramebufferPingPong Implementation
// ============================================================================

bool FramebufferPingPong::init(int width, int height, bool useFloat16)
{
    if (!buffers_[0].init(width, height, useFloat16)) return false;
    if (!buffers_[1].init(width, height, useFloat16)) return false;
    currentSource_ = 0;
    return true;
}

bool FramebufferPingPong::resize(int width, int height)
{
    if (!buffers_[0].resize(width, height)) return false;
    if (!buffers_[1].resize(width, height)) return false;
    return true;
}

void FramebufferPingPong::destroy()
{
    buffers_[0].destroy();
    buffers_[1].destroy();
    currentSource_ = 0;
}

void FramebufferPingPong::swap()
{
    currentSource_ = 1 - currentSource_;
}

} // namespace effects
} // namespace ui
} // namespace dragonx

#else // !DRAGONX_HAS_GLAD

// ============================================================================
// Stub Implementation (No GLAD - Acrylic effects disabled)
// ============================================================================

namespace dragonx {
namespace ui {
namespace effects {

Framebuffer::Framebuffer() = default;
Framebuffer::~Framebuffer() = default;
Framebuffer::Framebuffer(Framebuffer&&) noexcept = default;
Framebuffer& Framebuffer::operator=(Framebuffer&&) noexcept = default;

bool Framebuffer::init(int, int, bool) { return false; }
bool Framebuffer::resize(int, int) { return false; }
void Framebuffer::destroy() {}
void Framebuffer::cleanup() {}
bool Framebuffer::checkComplete() { return false; }
void Framebuffer::bind() {}
void Framebuffer::unbind() {}
void Framebuffer::clear(float, float, float, float) {}
void Framebuffer::blitFrom(unsigned int, int, int, int, int, int, int) {}
void Framebuffer::captureScreen(int, int) {}

bool FramebufferPingPong::init(int, int, bool) { return false; }
bool FramebufferPingPong::resize(int, int) { return false; }
void FramebufferPingPong::destroy() {}
void FramebufferPingPong::swap() {}

} // namespace effects
} // namespace ui
} // namespace dragonx

#endif // DRAGONX_HAS_GLAD
