#pragma once
/*
 *  Copyright (C) 2024  Brett Terpstra
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef BLT_WITH_GRAPHICS_FRAMEBUFFER_H
#define BLT_WITH_GRAPHICS_FRAMEBUFFER_H

#include "blt/gfx/gl_includes.h"
#include <blt/gfx/texture.h>
#include <vector>
#include "blt/std/types.h"

namespace blt::gfx
{

#define C(num) COLOR##num = GL_COLOR_ATTACHMENT##num,

#define COLOR_LIST \
    C(0) C(1) C(2) C(3) C(4) C(5) C(6) C(7) C(8) C(9) C(10) C(11) C(12) C(13) C(14) C(15) C(16) \
    C(17) C(18) C(19) C(20) C(21) C(22) C(23) C(24) C(25) C(26) C(27) C(28) C(29) C(30) C(31)
    
    class fbo_t;
    
    class rbo_t
    {
            friend fbo_t;
        private:
            GLuint rboID;
            GLuint storage_type;
            blt::i32 width_, height_;
            blt::i32 samples = 0;
        public:
            rbo_t(): rboID(0), storage_type(0), width_(-1), height_(-1)
            {}
            
            void create();
            
            void bind() const;
            
            void setStorage(GLuint storage_type, blt::i32 width, blt::i32 height);
            
            void setStorageMultiSampled(GLuint storage_type, blt::i32 width, blt::i32 height, blt::i32 samples);
            
            void updateStorage(blt::i32 width, blt::i32 height);
            
            void updateStorageMultiSampled(blt::i32 width, blt::i32 height, blt::i32 samples);
            
            void resize(blt::i32 width, blt::i32 height);
            
            static void unbind();
            
            void destroy();
            
            static rbo_t make_render_buffer(GLuint storage_type, blt::i32 width, blt::i32 height);
            
            static rbo_t make_render_buffer(GLuint storage_type, blt::i32 width, blt::i32 height, blt::i32 samples);
    };
    
    class fbo_t
    {
        public:
            enum class draw_t : GLuint
            {
                DRAW = GL_DRAW_FRAMEBUFFER,
                READ = GL_READ_FRAMEBUFFER,
                BOTH = GL_FRAMEBUFFER
            };
            enum class attachment_t : GLuint
            {
                COLOR_LIST
                DEPTH_STENCIL = GL_DEPTH_STENCIL_ATTACHMENT,
                DEPTH = GL_DEPTH_ATTACHMENT,
                STENCIL = GL_STENCIL_ATTACHMENT
            };
        
        private:
            GLuint fboID = 0;
            GLuint generic_bind_type = GL_FRAMEBUFFER;
            std::vector<blt::gfx::texture_gl*> texture_buffers;
            std::vector<rbo_t> render_buffers;
            blt::i32 width_ = -1, height_ = -1;
        public:
            // default used for fbo binding
            void create(draw_t bind_type = draw_t::BOTH);
            
            void bind(draw_t type = draw_t::BOTH) const;
            
            // this function takes ownership of the pointer.
            void attachTexture(blt::gfx::texture_gl* texture, attachment_t attachment, int level = 0);
            
            void updateBuffersStorage(blt::i32 width, blt::i32 height);
            
            // this function takes ownership of the render buffer
            void attachRenderBuffer(rbo_t rbo, attachment_t attachment);
            
            void blitTexture(const fbo_t& draw, blt::i32 srcX_off = 0, blt::i32 srcY_off = 0, blt::i32 destX_off = 0, blt::i32 destY_off = 0,
                             GLuint filter = GL_NEAREST, attachment_t attachment_read = attachment_t::COLOR0,
                             attachment_t attachment_write = attachment_t::COLOR0) const;
            
            void blitToScreen(blt::i32 width, blt::i32 height) const;
            
            void blitDepth(const fbo_t& draw, blt::i32 srcX_off = 0, blt::i32 srcY_off = 0, blt::i32 destX_off = 0, blt::i32 destY_off = 0,
                           GLuint filter = GL_NEAREST) const;
            
            void blitStencil(const fbo_t& draw, blt::i32 srcX_off = 0, blt::i32 srcY_off = 0, blt::i32 destX_off = 0, blt::i32 destY_off = 0,
                             GLuint filter = GL_NEAREST) const;
            
            static bool validate();
            
            static void unbind();
            
            void destroy();
            
            [[nodiscard]] i32 getHeight() const
            {
                return height_;
            }
            
            [[nodiscard]] i32 getWidth() const
            {
                return width_;
            }
        
        public:
            static fbo_t make_render_texture(blt::i32 width, blt::i32 height);
            
            static fbo_t make_multisample_render_texture(blt::i32 width, blt::i32 height, blt::i32 samples);
            
            static fbo_t make_render_target(blt::i32 width, blt::i32 height);
            
            static fbo_t make_multisample_render_target(blt::i32 width, blt::i32 height, blt::i32 samples);
    };

#undef C
#undef COLOR_LIST
}

#endif //BLT_WITH_GRAPHICS_FRAMEBUFFER_H