#ifndef FRAMEBUFFER_H
#define FRAMEBUFFER_H

#include "rendering/Texture.h"
#include "utils/ArrayList.h"
#include "utils/Size.h"

template<int N>
class Framebuffer final {
    ArrayList<Texture, N> textures;
    GL::Framebuffer buffer;

public:
    Framebuffer() : buffer(0) {
    }

    ~Framebuffer() {
        GL::deleteFramebuffers(buffer);
    }

    Framebuffer(const Framebuffer&) = delete;
    Framebuffer(Framebuffer&&) = delete;
    Framebuffer& operator=(const Framebuffer&) = delete;
    Framebuffer& operator=(Framebuffer&&) = delete;

    template<typename... Args>
    bool init(const Size& size, Args&&... args) {
        const int n = sizeof...(args);
        TextureFormat init[n] = {args...};
        static_assert(N == n,
                      "framebuffer size and amount of arguments do not match");
        for(int i = 0; i < N; i++) {
            textures[i].init(init[i], 0);
            textures[i].setClampWrap();
            if(init[i].linear) {
                textures[i].setLinearFilter();
            }
        }

        buffer = GL::genFramebuffer();
        GL::bindFramebuffer(buffer);

        ArrayList<GL::ColorAttachment, N> attachments;
        for(Texture& t : textures) {
            t.setData(size.width, size.height);
            if(t.format.depth) {
                GL::framebufferDepthTexture2D(t.texture);
            } else {
                attachments.add(GL::framebufferColorTexture2D(
                    t.texture, attachments.getLength()));
            }
        }
        GL::drawBuffers(attachments.getLength(), attachments.begin());

        return GL::printFramebufferError();
    }

    void bindAndClear() {
        GL::bindFramebuffer(buffer);
        GL::clear();
    }

    void bindTextureTo(int index, int textureUnit) const {
        textures[index].bindTo(textureUnit);
    }

    void resize(const Size& size) {
        for(Texture& t : textures) {
            t.setData(size.width, size.height);
        }
    }
};

#endif