#include <iostream>

#include "client/rendering/wrapper/Framebuffer.h"

Framebuffer::Framebuffer(const Size& size, int mode, bool texCompare) : size(size), mode(mode), textures(0),
buffer(0), error(false) {
    glGenFramebuffers(1, &buffer);
    glBindFramebuffer(GL_FRAMEBUFFER, buffer);

    data[0] = {POSITION, GL_RGB16F, GL_RGB, GL_FLOAT};
    data[1] = {NORMAL, GL_RGB16F, GL_RGB, GL_FLOAT};
    data[2] = {COLOR, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE};
    data[3] = {RED, GL_R32F, GL_RGB, GL_FLOAT};
    data[4] = {DEPTH, GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_FLOAT};

    GLuint attachments[4];
    int counter = 0;
    for(int i = 0; i < 4; i++) {
        if(mode & data[i].mask) {
            setupTexture(i, size.width, size.height, attachments, counter);
        }
    }
    if(mode & DEPTH) {
        genTexture(4, size.width, size.height);
        if(texCompare) {
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
        }
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textures[4], 0);
    }
    glDrawBuffers(counter, attachments);

    GLenum glError = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if(glError != GL_FRAMEBUFFER_COMPLETE) {
        std::cout << "frame buffer error: " << getErrorString(error) << "\n";
        error = true;
    }
}

Framebuffer::~Framebuffer() {
    glDeleteFramebuffers(1, &buffer);
    for(GLuint& texture : textures) {
        glDeleteTextures(1, &texture);
    }
}

bool Framebuffer::hasError() const {
    return error;
}

void Framebuffer::bind() const {
    glViewport(0, 0, size.width, size.height);
    glBindFramebuffer(GL_FRAMEBUFFER, buffer);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}

void Framebuffer::resize(int width, int height) const {
    for(int i = 0; i < 5; i++) {
        if(mode & data[i].mask) {
            glBindTexture(GL_TEXTURE_2D, textures[i]);
            glTexImage2D(GL_TEXTURE_2D, 0, data[i].internalFormat, width, height, 0, data[i].format, data[i].type, nullptr);
        }
    }
}

void Framebuffer::bindTexture(int textureUnit, GLuint texture) const {
    glActiveTexture(GL_TEXTURE0 + textureUnit);
    glBindTexture(GL_TEXTURE_2D, texture);
}

void Framebuffer::bindPositionTexture(int textureUnit) const {
    bindTexture(textureUnit, textures[0]);
}

void Framebuffer::bindNormalTexture(int textureUnit) const {
    bindTexture(textureUnit, textures[1]);
}

void Framebuffer::bindColorTexture(int textureUnit) const {
    bindTexture(textureUnit, textures[2]);
}

void Framebuffer::bindRedTexture(int textureUnit) const {
    bindTexture(textureUnit, textures[3]);
}

void Framebuffer::bindDepthTexture(int textureUnit) const {
    bindTexture(textureUnit, textures[4]);
}

void Framebuffer::genTexture(int index, int width, int height) {
    glGenTextures(1, &(textures[index]));
    glBindTexture(GL_TEXTURE_2D, textures[index]);
    glTexImage2D(GL_TEXTURE_2D, 0, data[index].internalFormat, width, height, 0, data[index].format, data[index].type, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

void Framebuffer::setupTexture(int index, int width, int height, GLuint* attachments, int& counter) {
    genTexture(index, width, height);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + counter, GL_TEXTURE_2D, textures[index], 0);
    attachments[counter] = GL_COLOR_ATTACHMENT0 + counter;
    counter++;
}

const char* Framebuffer::getErrorString(GLenum error) const {
    switch(error) {
        case GL_FRAMEBUFFER_UNDEFINED: return "GL_FRAMEBUFFER_UNDEFINED";
        case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: return "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT";
        case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: return "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT";
        case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: return "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER";
        case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: return "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER";
        case GL_FRAMEBUFFER_UNSUPPORTED: return "GL_FRAMEBUFFER_UNSUPPORTED";
        case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
        case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: return "GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS";
    }
    return "unknown error";
}