#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>

#include "fbo.h"

FBO::FBO() : fbo_id_(-1), albedo_gbuffer_id_(-1), position_gbuffer_id_(-1), normal_gbuffer_id_(-1), depth_rbo_id_(-1) {}

FBO::~FBO() {
    Cleanup();
}

void FBO::Init(int width, int height) {
    // Generate the framebuffer
    glGenFramebuffers(1, &fbo_id_);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);

    // Generate the albedo gbuffer
    glGenTextures(1, &albedo_gbuffer_id_);
    glBindTexture(GL_TEXTURE_2D, albedo_gbuffer_id_);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, albedo_gbuffer_id_, 0);

    // Generate the position gbuffer
    glGenTextures(1, &position_gbuffer_id_);
    glBindTexture(GL_TEXTURE_2D, position_gbuffer_id_);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, position_gbuffer_id_, 0);

    // Generate the normal gbuffer
    glGenTextures(1, &normal_gbuffer_id_);
    glBindTexture(GL_TEXTURE_2D, normal_gbuffer_id_);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, normal_gbuffer_id_, 0);

    GLenum drawBuffers[3] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};
    glDrawBuffers(3, drawBuffers);

    // RBO was causing issues. Not using for now
    // Generate the Depth renderbuffer
    glGenRenderbuffers(1, &depth_rbo_id_);
    glBindRenderbuffer(GL_RENDERBUFFER, depth_rbo_id_);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, width, height);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_rbo_id_);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    // Check if the framebuffer is complete
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        std::cerr << "Error: Framebuffer is not complete!" << std::endl;
    }
}

void FBO::Bind() {
    glBindFramebuffer(GL_FRAMEBUFFER, fbo_id_);
}

void FBO::Unbind() {
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void FBO::Cleanup() {
    if (albedo_gbuffer_id_ != -1) {
        glDeleteTextures(1, &albedo_gbuffer_id_);
    }
    if (position_gbuffer_id_ != -1) {
        glDeleteTextures(1, &position_gbuffer_id_);
    }
    if (normal_gbuffer_id_ != -1) {
        glDeleteTextures(1, &normal_gbuffer_id_);
    }
    if (depth_rbo_id_ != -1) {
        glDeleteTextures(1, &depth_rbo_id_);
    }
    if (fbo_id_ != -1) {
        glDeleteFramebuffers(1, &fbo_id_);
    }
}

GLuint FBO::GetAlbedoGbuffer() const {
    return albedo_gbuffer_id_;
}

GLuint FBO::GetPositionGbuffer() const {
    return position_gbuffer_id_;
}

GLuint FBO::GetNormalGbuffer() const {
    return normal_gbuffer_id_;
}