#include "pw/visual/framebuffer.hpp"

#include "pw/core/size.hpp"
#include "pw/core/debug.hpp"

#include "glad/glad.h"

#include <limits>

namespace pw {

// TODO stencil buffer?

struct framebuffer::impl {

    size _size;

    GLuint _fbo_draw { std::numeric_limits<GLuint>::max() };
    GLuint _fbo_msaa { std::numeric_limits<GLuint>::max() };

    GLuint _rbo_color { std::numeric_limits<GLuint>::max() };
    GLuint _rbo_depth { std::numeric_limits<GLuint>::max() };

    bool create(const size &s)
    {
        // TODO  mak color resolution and
        _size = s;

        int max_msaa { 0 };

        // query actual maximum MSAA
        glGetIntegerv(GL_MAX_SAMPLES,&max_msaa);

        // create a 4x MSAA renderbuffer object for colorbuffer
        int msaa = std::min(max_msaa,4);


        debug::d() << "OpenGL multisampling: " << max_msaa << " choosen:" << msaa;
        debug::d() << "Framebuffer size " << _size.width << " x " << _size.height;

        glGenRenderbuffers(1, &_rbo_color);
        glBindRenderbuffer(GL_RENDERBUFFER, _rbo_color);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa, GL_RGBA8, _size.width, _size.height);

        // create a MSAA renderbuffer object for depthbuffer

        glGenRenderbuffers(1, &_rbo_depth);
        glBindRenderbuffer(GL_RENDERBUFFER, _rbo_depth);
        glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa, GL_DEPTH_COMPONENT, _size.width, _size.height);

        // create a MSAA framebuffer object
        glGenFramebuffers(1, &_fbo_msaa);
        glBindFramebuffer(GL_FRAMEBUFFER, _fbo_msaa);

        // attach colorbuffer image to FBO
        glFramebufferRenderbuffer(GL_FRAMEBUFFER,       // 1. fbo target: GL_FRAMEBUFFER
                                  GL_COLOR_ATTACHMENT0, // 2. color attachment point
                                  GL_RENDERBUFFER,      // 3. rbo target: GL_RENDERBUFFER
                                  _rbo_color);          // 4. rbo ID

        // attach depthbuffer image to FBO
        glFramebufferRenderbuffer(GL_FRAMEBUFFER,       // 1. fbo target: GL_FRAMEBUFFER
                                  GL_DEPTH_ATTACHMENT,  // 2. depth attachment point
                                  GL_RENDERBUFFER,      // 3. rbo target: GL_RENDERBUFFER
                                  _rbo_depth);          // 4. rbo ID

        // check FBO status
        if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            return false;

        unbind();

        return true;

    }

    void bind()
    {
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo_msaa);
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo_draw);
    }

    void blit()
    {
//        glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo_msaa); // src FBO (multi-sample)
//        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo_draw);     // dst FBO (single-sample)

        glBlitFramebuffer(0, 0, _size.width, _size.height,             // src rect
                          0, 0, _size.width, _size.height,             // dst rect
                          GL_COLOR_BUFFER_BIT,             // buffer mask
                          GL_LINEAR);                      // scale filter

    }

    void unbind()
    {
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    }
};

framebuffer::framebuffer()
{
    _impl = std::make_unique<impl>();
}

framebuffer::~framebuffer()
{
}

bool framebuffer::create(const size &s)
{
    return _impl->create(s);
}

void framebuffer::bind()
{
    _impl->bind();
}

void framebuffer::blit()
{
    _impl->blit();
}

void framebuffer::unbind()
{
    _impl->unbind();
}


}
