#include "pch.h"
#include "log.h"
#include "rtt.h"
#include "util.h"

RTT::RTT()
{
    fboID = 0;
    rboID = 0;
    w = 0;
    h = 0;
    bound = false;
}

RTT::RTT(RTT&& other)
{
    fboID = other.fboID;
    rboID = other.rboID;
    w = other.w;
    h = other.h;
    bound = other.bound;

    other.fboID = 0;
    other.rboID = 0;
    other.w = 0;
    other.h = 0;
    other.bound = false;
}

RTT::~RTT()
{
    //destroy();
    if (texID || rboID || fboID)
        LOG("RTT not destroyed");
}

void RTT::resize(int width, int height)
{
    RTT new_rtt;
    
    glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldDFboID);
    glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &oldRFboID);

    new_rtt.create(width, height, -1, int_fmt);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, new_rtt.fboID);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID);
    glBlitFramebuffer(0, 0, w, h, 0, 0, new_rtt.w, new_rtt.h, GL_COLOR_BUFFER_BIT, GL_LINEAR);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oldDFboID);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, oldRFboID);
    
    destroy();
    
    oldRFboID = 0;
    oldDFboID = 0;
    fboID = new_rtt.fboID;
    rboID = new_rtt.rboID;
    texID = new_rtt.texID;
    int_fmt = new_rtt.int_fmt;
    w = new_rtt.w;
    h = new_rtt.h;
}

void RTT::destroy()
{
    if (rboID)
    {
        glDeleteRenderbuffers(1, &rboID);
    }
    if (texID)
    {
        unbindTexture();
        glDeleteTextures(1, &texID);
        //LOG("TEX rtt destroy %d", texID)
    }
    if (fboID)
    {
        unbindFramebuffer();
        glDeleteFramebuffers(1, &fboID);
        //LOG("RTT DESTROY %d", fboID);
    }
    texID = 0;
    fboID = 0;
    rboID = 0;
//    w = 0;
//    h = 0;
}

void RTT::copy(const RTT & source)
{
    glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldDFboID);
    glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &oldRFboID);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboID);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, source.fboID);
    glBlitFramebuffer(0, 0, source.w, source.h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR);

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oldDFboID);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, oldRFboID);
}

bool RTT::create(int width, int height, int tex/* = -1*/, GLint internal_format)
{
    // Destroy any previously created object
    destroy();

    w = width;
    h = height;
    int_fmt = internal_format;

    if (tex == -1)
    {
        glGenTextures(1, &texID);
        //LOG("TEX rtt create %d", texID);
    }
    else
    {
        texID = tex;
    }

    auto ifmt = GL_UNSIGNED_BYTE;
    if (internal_format == GL_RGBA32F) ifmt = GL_FLOAT;
    if (internal_format == GL_RGBA16F) ifmt = GL_HALF_FLOAT;
    
    glBindTexture(GL_TEXTURE_2D, texID);
    if (tex == -1)
        glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, GL_RGBA, ifmt, 0);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glBindTexture(GL_TEXTURE_2D, 0);

    // Create a renderbuffer object to store depth info
//     glGenRenderbuffers(1, &rboID);
//     glBindRenderbuffer(GL_RENDERBUFFER, rboID);
//     glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
//     glBindRenderbuffer(GL_RENDERBUFFER, 0);

    GLint oldFboID;
    glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldFboID);

    // Create a framebuffer object
    glGenFramebuffers(1, &fboID);
    glBindFramebuffer(GL_FRAMEBUFFER, fboID);
    //LOG("RTT CREATE %d - tex %d", fboID, texID);

    // Attach the texture to FBO color attachment point
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texID, 0);

    // Attach the renderbuffer to depth attachment point
    //glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboID);

    auto err2str = [](GLenum err) {
        switch (err)
        {
        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_UNSUPPORTED: return "GL_FRAMEBUFFER_UNSUPPORTED";
        case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: return "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE";
        default: return "UNKNOWN";
        }
    };

    // Check FBO status
    GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE)
        LOG("RTT::create failed because: %s", err2str(status));

    // Switch back to window-system-provided framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, oldFboID);
    oldRFboID = 0;
    oldDFboID = 0;

    return status == GL_FRAMEBUFFER_COMPLETE;
}

void RTT::bindFramebuffer()
{
#ifdef _DEBUG
    if (bound)
    {
        LOG("framebuffer bound twice!");
#ifdef _WIN32
        __debugbreak();
#endif
    }
#endif // _DEBUG
    bound = true;
    glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &oldDFboID);
    glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &oldRFboID);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboID);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID);
}

void RTT::unbindFramebuffer()
{
    if (!bound)
        return;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, oldDFboID);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, oldRFboID);
    oldRFboID = 0;
    oldDFboID = 0;
    bound = false;
}

void RTT::clear(glm::vec4 color)
{
    glClearColor(color.r, color.g, color.b, color.a);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

glm::ivec4 RTT::calc_bounds()
{
    auto data = std::unique_ptr<glm::u8vec4[]>(reinterpret_cast<glm::u8vec4*>(readTextureData()));
    glm::ivec2 bbmin(w, h);
    glm::ivec2 bbmax(0);
    for (int y = 0; y < h; y++)
    {
        for (int x = 0; x < w; x++)
        {
            if (data[x + y * w].a > 0)
            {
                bbmin = glm::min(bbmin, { x, y });
                bbmax = glm::max(bbmax, { x + 1, y + 1 });
            }
        }
    }
    return { bbmin, bbmax };
}

uint8_t* RTT::readTextureData(uint8_t* buffer)
{
    if (!buffer)
        buffer = createBuffer();
    GLint old;
    glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID);
    glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, old);
    return buffer;
}

float* RTT::readTextureDataFloat(float* buffer)
{
    if (!buffer)
        buffer = createBufferFloat();
    GLint old;
    glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &old);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fboID);
    glReadPixels(0, 0, w, h, GL_RGBA, GL_FLOAT, buffer);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, old);
    return buffer;
}

uint8_t* RTT::createBuffer()
{
    return new uint8_t[w * h * 4];
}

float * RTT::createBufferFloat()
{
    return new float[w * h * 4];
}

void RTT::bindTexture()
{
    glBindTexture(GL_TEXTURE_2D, texID);
}

void RTT::unbindTexture()
{
    glBindTexture(GL_TEXTURE_2D, 0);
}


