/*
 * Copyright 2016 - 2022 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#pragma once

#include <ox/std/defines.hpp>

#include <glad/glad.h>

#include <ox/std/error.hpp>
#include <ox/std/string.hpp>
#include <ox/std/vector.hpp>

namespace nostalgia::glutils {

constexpr auto GlslVersion = "#version 330";

struct Empty {};

struct TextureBase {

	GLsizei width = 0;
	GLsizei height = 0;

	constexpr TextureBase() noexcept = default;

	constexpr TextureBase(TextureBase &&tb) noexcept {
		width = tb.width;
		height = tb.height;
		tb.width = 0;
		tb.height = 0;
	}

	constexpr TextureBase &operator=(TextureBase &&tb) noexcept {
		width = tb.width;
		height = tb.height;
		tb.width = 0;
		tb.height = 0;
		return *this;
	}

};


template<auto del, typename Base = Empty>
struct GLObject: public Base {

	GLuint id = 0;

	constexpr GLObject() noexcept = default;

	explicit constexpr GLObject(GLuint id) noexcept {
		this->id = id;
	}

	constexpr GLObject(GLObject &&o) noexcept: Base(std::move(o)) {
		id = o.id;
		o.id = 0;
	}

	~GLObject() noexcept {
		del(id);
	}

	GLObject &operator=(GLObject &&o) noexcept {
		if (this != &o) {
			del(id);
			Base::operator=(std::move(o));
			id = o.id;
			o.id = 0;
		}
		return *this;
	}

	constexpr GLuint release() noexcept {
		auto out = id;
		id = 0;
		return out;
	}

	constexpr operator GLuint&() noexcept {
		return id;
	}

	constexpr operator const GLuint&() const noexcept {
		return id;
	}

};

void deleteBuffer(GLuint b) noexcept;
void deleteFrameBuffer(GLuint b) noexcept;
void deleteRenderBuffer(GLuint b) noexcept;
void deleteTexture(GLuint t) noexcept;
void deleteVertexArray(GLuint v) noexcept;
void deleteProgram(GLuint p) noexcept;
void deleteShader(GLuint s) noexcept;

extern template struct GLObject<deleteBuffer>;
extern template struct GLObject<deleteFrameBuffer>;
extern template struct GLObject<deleteRenderBuffer>;
extern template struct GLObject<deleteTexture, TextureBase>;
extern template struct GLObject<deleteVertexArray>;
extern template struct GLObject<deleteProgram>;
extern template struct GLObject<deleteShader>;

using GLBuffer = GLObject<deleteBuffer>;
using GLFrameBuffer = GLObject<deleteBuffer>;
using GLRenderBuffer = GLObject<deleteRenderBuffer>;
using GLShader = GLObject<deleteShader>;
using GLProgram = GLObject<deleteProgram>;
using GLTexture = GLObject<deleteTexture, TextureBase>;
using GLVertexArray = GLObject<deleteVertexArray>;

/**
 * FrameBuffer holds everything needed for a usable frame buffer to exist.
 * It differs from GLFrameBuffer in that GLFrameBuffer only manages the FBO
 * and not its dependencies.
 */
struct FrameBuffer {
	int width = 0;
	int height = 0;
	GLFrameBuffer fbo;
	GLTexture color;
	GLRenderBuffer depth;

	constexpr operator GLuint&() noexcept {
		return fbo.id;
	}

	constexpr operator const GLuint&() const noexcept {
		return fbo.id;
	}
};


ox::Result<GLProgram> buildShaderProgram(const GLchar *vert, const GLchar *frag, const GLchar *geo = nullptr) noexcept;

ox::Result<GLProgram> buildShaderProgram(const ox::String &vert, const ox::String &frag, const ox::String &geo = "") noexcept;

glutils::GLVertexArray generateVertexArrayObject() noexcept;

glutils::GLBuffer generateBuffer() noexcept;

[[nodiscard]]
FrameBuffer generateFrameBuffer(int width, int height) noexcept;

struct BufferSet {
	glutils::GLVertexArray vao;
	glutils::GLBuffer vbo;
	glutils::GLBuffer ebo;
	glutils::GLTexture tex;
	ox::Vector<float> vertices;
	ox::Vector<GLuint> elements;
};

void sendVbo(const BufferSet &bg) noexcept;

void sendEbo(const BufferSet &bg) noexcept;

}
