#pragma once

#include <psemek/gfx/gl.hpp>
#include <psemek/gfx/texture.hpp>
#include <psemek/gfx/renderbuffer.hpp>
#include <psemek/gfx/pixel.hpp>
#include <psemek/gfx/pixmap.hpp>

#include <string_view>

namespace psemek::gfx
{

	struct framebuffer
	{
		framebuffer();
		framebuffer(framebuffer &&);
		framebuffer & operator = (framebuffer &&);
		~framebuffer();

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

		static framebuffer const & null();

		GLuint id() const { return id_; }

		explicit operator bool() const { return id() != 0; }

		void bind() const;
		void bind_read() const;

		void reset();

		void color(texture_2d const & tex, int attachment = 0);
		void color(texture_2d_array const & tex, int layer, int attachment = 0);
		void color(texture_cubemap const & tex, int attachment = 0);
		void color(texture_cubemap const & tex, int face, int attachment = 0);
		void color(renderbuffer const & rb, int attachment = 0);
		void depth(texture_2d const & tex);
		void depth(texture_2d_array const & tex, int layer);
		void depth(texture_cubemap const & tex);
		void depth(texture_cubemap const & tex, int face);
		void depth(renderbuffer const & rb);
		void depth_stencil(texture_2d const & tex);
		void depth_stencil(texture_2d_array const & tex, int layer);
		void depth_stencil(texture_cubemap const & tex);
		void depth_stencil(texture_cubemap const & tex, int face);
		void depth_stencil(renderbuffer const & rb);

#ifndef PSEMEK_GLES
		void color(texture_1d const & tex, int attachment = 0);
		void color(texture_2d_multisample const & tex, int attachment = 0);
		void color(texture_3d const & tex, int layer, int attachment = 0);
		void depth(texture_2d_multisample const & tex);
		void depth_stencil(texture_2d_multisample const & tex);
#endif

		GLenum status() const;
		bool complete() const;
		void assert_complete(std::string_view name = {}) const;

		template <typename Pixel>
		void read_pixels(basic_pixmap<Pixel> & p) const
		{
			using traits = pixel_traits<Pixel>;
			bind_read();
			gl::ReadPixels(0, 0, p.width(), p.height(), traits::format, traits::type, p.data());
		}

		template <typename Pixel>
		basic_pixmap<Pixel> read_pixels(std::size_t width, std::size_t height) const
		{
			basic_pixmap<Pixel> p({width, height});
			read_pixels(p);
			return p;
		}

	private:
		GLuint id_ = 0;

		explicit framebuffer(std::nullptr_t);
	};

	template <typename ... Args>
	void draw_buffers(Args ... args)
	{
		GLenum buffers[] = {args...};
		gl::DrawBuffers(sizeof...(Args), buffers);
	}

	inline void draw_buffers()
	{
		gl::DrawBuffers(0, nullptr);
	}

}
