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

#include <ox/std/assert.hpp>
#include <ox/std/bstring.hpp>
#include <ox/std/trace.hpp>

#include "glutils.hpp"

namespace nostalgia::glutils {

void deleteBuffer(GLuint b) noexcept {
	glDeleteBuffers(1, &b);
}

void deleteFrameBuffer(GLuint b) noexcept {
	glDeleteFramebuffers(1, &b);
}

void deleteRenderBuffer(GLuint b) noexcept {
	glDeleteRenderbuffers(1, &b);
}

void deleteTexture(GLuint t) noexcept {
	glDeleteTextures(1, &t);
}

void deleteVertexArray(GLuint v) noexcept {
	glDeleteVertexArrays(1, &v);
}

void deleteProgram(GLuint v) noexcept {
	glDeleteProgram(v);
}

void deleteShader(GLuint v) noexcept {
	glDeleteShader(v);
}

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

[[nodiscard]]
static ox::Result<GLShader> buildShader(GLuint shaderType, const GLchar *src, const char *shaderName) noexcept {
	GLShader shader(glCreateShader(shaderType));
	glShaderSource(shader, 1, &src, nullptr);
	glCompileShader(shader);
	GLint status;
	glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
	if (status != GL_TRUE) {
		ox::Vector<char, 1000> errMsg(1000);
		glGetShaderInfoLog(shader, static_cast<GLsizei>(errMsg.size()), nullptr, errMsg.data());
		oxErrorf("shader compile error in {}: {}", shaderName, errMsg.data());
		return OxError(1, "shader compile error");
	}
	return shader;
}

ox::Result<GLProgram> buildShaderProgram(const GLchar *vert, const GLchar *frag, [[maybe_unused]] const GLchar *geo) noexcept {
	GLProgram prgm(glCreateProgram());
	oxRequire(vs, buildShader(GL_VERTEX_SHADER, vert, "vshad"));
	glAttachShader(prgm, vs);
	if (geo && ox_strlen(geo) != 0) {
		oxRequire(gs, buildShader(GL_GEOMETRY_SHADER, geo, "gshad"));
		glAttachShader(prgm, gs);
	}
	oxRequire(fs, buildShader(GL_FRAGMENT_SHADER, frag, "fshad"));
	glAttachShader(prgm, fs);
	glLinkProgram(prgm);
	return prgm;
}

ox::Result<GLProgram> buildShaderProgram(const ox::String &vert, const ox::String &frag, const ox::String &geo) noexcept {
	return buildShaderProgram(vert.c_str(), frag.c_str(), geo.c_str());
}

GLVertexArray generateVertexArrayObject() noexcept {
	GLVertexArray vao;
	glGenVertexArrays(1, &vao.id);
	return vao;
}

GLBuffer generateBuffer() noexcept {
	GLBuffer buff;
	glGenBuffers(1, &buff.id);
	return buff;
}

FrameBuffer generateFrameBuffer(int width, int height) noexcept {
	width = ox::max(1, width);
	height = ox::max(1, height);
	FrameBuffer fb;
	fb.width = width;
	fb.height = height;
	glGenFramebuffers(1, &fb.fbo.id);
	glBindFramebuffer(GL_FRAMEBUFFER, fb);
	// color texture
	glGenTextures(1, &fb.color.id);
	glBindTexture(GL_TEXTURE_2D, fb.color);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.color, 0);
	// depth texture
	glGenRenderbuffers(1, &fb.depth.id);
	glBindRenderbuffer(GL_RENDERBUFFER, fb.depth);
	glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.depth);
	// verify FBO
	oxAssert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE, "Frame Buffer is incomplete");
	// restore primary FB
	glBindFramebuffer(GL_FRAMEBUFFER, 0);
	return fb;
}

void sendVbo(const BufferSet &bg) noexcept {
	const auto bufferSize = static_cast<GLsizeiptr>(sizeof(decltype(bg.vertices)::value_type) * bg.vertices.size());
	glBindBuffer(GL_ARRAY_BUFFER, bg.vbo);
	glBufferData(GL_ARRAY_BUFFER, bufferSize, bg.vertices.data(), GL_DYNAMIC_DRAW);
}

void sendEbo(const BufferSet &bg) noexcept {
	const auto bufferSize = static_cast<GLsizeiptr>(sizeof(decltype(bg.elements)::value_type) * bg.elements.size());
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bg.ebo);
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferSize, bg.elements.data(), GL_STATIC_DRAW);
}

}
