#include "GLFBO.h"
#include <stdexcept>
#include <string>

GLFBO::GLFBO()
    : handle(0), width(0), height(0) {
    FBOState state;

    GLuint handleArr[1];
    glGenFramebuffers(1, handleArr);
    handle = handleArr[0];

    glBindFramebuffer(GL_FRAMEBUFFER, handle);

    int numColors = 1;
    for (int i = 0; i < numColors; ++i) {
        color.push_back(initTexture(width, height, GL_COLOR_ATTACHMENT0 + i));
    }

    state.restore();
}

GLFBO::~GLFBO() {
    GLuint handleArr[1] = {handle};
    glDeleteFramebuffers(1, handleArr);
    color.clear();
}

std::shared_ptr<GLTexture> GLFBO::initTexture(int width, int height, int attachment) {
    auto texture = std::make_shared<GLTexture>();
    texture->bind();
    texture->setShape(width, height);
    glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, texture->getHandle(), 0);
    return texture;
}

void GLFBO::checkStatus() {
    int status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
        switch (status) {
            case GL_FRAMEBUFFER_UNSUPPORTED:
                throw std::runtime_error("Framebuffer unsupported");
            case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
                throw std::runtime_error("Framebuffer incomplete attachment");
            case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
                throw std::runtime_error("Framebuffer incomplete dimensions");
            case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
                throw std::runtime_error("Framebuffer incomplete missing attachment");
            default:
                throw std::runtime_error("Failed to create framebuffer: " + std::to_string(status));
        }
    }
}

void GLFBO::bind() {
    glBindFramebuffer(GL_FRAMEBUFFER, handle);
    glViewport(0, 0, width, height);
}

void GLFBO::setShape(int w, int h) {
    if (w == width && h == height) return;

    GLint maxFBOSize;
    glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE, &maxFBOSize);
    if (w < 0 || w > maxFBOSize || h < 0 || h > maxFBOSize) {
        throw std::invalid_argument("Can't resize framebuffer. Invalid dimensions");
    }

    width = w;
    height = h;

    FBOState state;
    for (auto& clr : color) {
        clr->setShape(w, h);
    }

    glBindFramebuffer(GL_FRAMEBUFFER, handle);
    checkStatus();

    state.restore();
}

GLFBO::FBOState::FBOState() {
    GLint fbo, rbo, tex;
    glGetIntegerv(GL_FRAMEBUFFER_BINDING, &fbo);
    glGetIntegerv(GL_RENDERBUFFER_BINDING, &rbo);
    glGetIntegerv(GL_TEXTURE_BINDING_2D, &tex);
    this->fbo = fbo;
    this->rbo = rbo;
    this->tex = tex;
}

void GLFBO::FBOState::restore() {
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glBindTexture(GL_TEXTURE_2D, tex);
}