#include <psemek/gfx/framebuffer.hpp>

#include <psemek/util/to_string.hpp>
#include <psemek/util/exception.hpp>

namespace psemek::gfx
{

	static std::string framebuffer_status_string(GLenum status)
	{
		switch (status)
		{
		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";
		}

		return "(unknown)";
	}

	framebuffer::framebuffer()
	{
		gl::GenFramebuffers(1, &id_);
	}

	framebuffer::framebuffer(framebuffer && other)
		: id_{other.id_}
	{
		other.id_ = 0;
	}

	framebuffer & framebuffer::operator = (framebuffer && other)
	{
		if (this == &other) return *this;

		reset();
		std::swap(id_, other.id_);
		return *this;
	}

	framebuffer::~framebuffer()
	{
		reset();
	}

	framebuffer const & framebuffer::null()
	{
		static framebuffer f(nullptr);
		return f;
	}

	void framebuffer::bind() const
	{
		gl::BindFramebuffer(gl::DRAW_FRAMEBUFFER, id_);
	}

	void framebuffer::bind_read() const
	{
		gl::BindFramebuffer(gl::READ_FRAMEBUFFER, id_);
	}

	void framebuffer::reset()
	{
		if (id_ != 0)
			gl::DeleteFramebuffers(1, &id_);
		id_ = 0;
	}

#ifndef PSEMEK_GLES
	void framebuffer::color(texture_1d const & tex, int attachment)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0 + attachment, tex.target, tex.id(), 0);
	}
#endif

	void framebuffer::color(texture_2d const & tex, int attachment)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0 + attachment, tex.target, tex.id(), 0);
	}

#ifndef PSEMEK_GLES
	void framebuffer::color(texture_2d_multisample const & tex, int attachment)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0 + attachment, tex.target, tex.id(), 0);
	}

	void framebuffer::color(texture_3d const & tex, int layer, int attachment)
	{
		bind();
		gl::FramebufferTexture3D(gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0 + attachment, tex.target, tex.id(), 0, layer);
	}
#endif

	void framebuffer::color(texture_2d_array const & tex, int layer, int attachment)
	{
		bind();
		gl::FramebufferTextureLayer(gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0 + attachment, tex.id(), 0, layer);
	}

	void framebuffer::color(texture_cubemap const & tex, int attachment)
	{
		bind();
		gl::FramebufferTexture(gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0 + attachment, tex.id(), 0);
	}

	void framebuffer::color(texture_cubemap const & tex, int face, int attachment)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0 + attachment, texture_cubemap::face_to_gl(face), tex.id(), 0);
	}

	void framebuffer::color(renderbuffer const & rb, int attachment)
	{
		bind();
		gl::FramebufferRenderbuffer(gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0 + attachment, rb.target, rb.id());
	}

	void framebuffer::depth(texture_2d const & tex)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::DEPTH_ATTACHMENT, tex.target, tex.id(), 0);
	}

#ifndef PSEMEK_GLES
	void framebuffer::depth(texture_2d_multisample const & tex)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::DEPTH_ATTACHMENT, tex.target, tex.id(), 0);
	}
#endif

	void framebuffer::depth(texture_2d_array const & tex, int layer)
	{
		bind();
		gl::FramebufferTextureLayer(gl::DRAW_FRAMEBUFFER, gl::DEPTH_ATTACHMENT, tex.id(), 0, layer);
	}

	void framebuffer::depth(texture_cubemap const & tex)
	{
		bind();
		gl::FramebufferTexture(gl::DRAW_FRAMEBUFFER, gl::DEPTH_ATTACHMENT, tex.id(), 0);
	}

	void framebuffer::depth(texture_cubemap const & tex, int face)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::DEPTH_ATTACHMENT, texture_cubemap::face_to_gl(face), tex.id(), 0);
	}

	void framebuffer::depth(renderbuffer const & rb)
	{
		bind();
		gl::FramebufferRenderbuffer(gl::DRAW_FRAMEBUFFER, gl::DEPTH_ATTACHMENT, rb.target, rb.id());
	}

	void framebuffer::depth_stencil(texture_2d const & tex)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, tex.target, tex.id(), 0);
	}

#ifndef PSEMEK_GLES
	void framebuffer::depth_stencil(texture_2d_multisample const & tex)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, tex.target, tex.id(), 0);
	}
#endif

	void framebuffer::depth_stencil(texture_2d_array const & tex, int layer)
	{
		bind();
		gl::FramebufferTextureLayer(gl::DRAW_FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, tex.id(), 0, layer);
	}

	void framebuffer::depth_stencil(texture_cubemap const & tex)
	{
		bind();
		gl::FramebufferTexture(gl::DRAW_FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, tex.id(), 0);
	}

	void framebuffer::depth_stencil(texture_cubemap const & tex, int face)
	{
		bind();
		gl::FramebufferTexture2D(gl::DRAW_FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, texture_cubemap::face_to_gl(face), tex.id(), 0);
	}

	void framebuffer::depth_stencil(renderbuffer const & rb)
	{
		bind();
		gl::FramebufferRenderbuffer(gl::DRAW_FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, rb.target, rb.id());
	}

	GLenum framebuffer::status() const
	{
		bind();
		return gl::CheckFramebufferStatus(gl::DRAW_FRAMEBUFFER);
	}

	bool framebuffer::complete() const
	{
		return status() == gl::FRAMEBUFFER_COMPLETE;
	}

	void framebuffer::assert_complete(std::string_view name) const
	{
		if (auto s = status(); s != gl::FRAMEBUFFER_COMPLETE)
			throw util::exception(util::to_string("Framebuffer ", name, name.empty() ? "" : " ", "incomplete: ", framebuffer_status_string(s)));
	}

	framebuffer::framebuffer(std::nullptr_t)
	{}

}
