#pragma once

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include "Mesh.h"
#include "types.h"

static bool glck(const char* call) {
	int err = glGetError();
	if (err == 0) {
		return true;
	} else {
		switch (err) {
		case GL_INVALID_VALUE:
			printf("GL_INVALID_VALUE: %s\n", call);
			break;
		case GL_INVALID_ENUM:
			printf("GL_INVALID_ENUM: %s\n", call);
			break;
		case GL_INVALID_OPERATION:
			printf("GL_INVALID_OPERATION: %s\n", call);
			break;
		default:
			printf("UNKNOWN Error: %s\n", call);
		}
		return false;
	}
}

#define GLCK(a) {a; assert(glck(#a));}

class GLMesh {
	unsigned int vertexArrayId, vPositionId, vTexCoordId, vNormalId,
			indexBufferId;
	unsigned int indexCount;

public:

	GLMesh() :
			vertexArrayId(0), vPositionId(0), vTexCoordId(0), vNormalId(0), indexBufferId(
					0), indexCount(0) {

	}

	~GLMesh() {
		GLCK(glDeleteBuffers(1, &vPositionId))
		GLCK(glDeleteBuffers(1, &vTexCoordId))
		GLCK(glDeleteBuffers(1, &vNormalId))
		GLCK(glDeleteBuffers(1, &indexBufferId))
		GLCK(glDeleteVertexArrays(1, &vertexArrayId))
	}

	void create() {
		GLCK(glGenVertexArrays(1, &vertexArrayId))
		GLCK(glGenBuffers(1, &vPositionId))
		GLCK(glGenBuffers(1, &vTexCoordId))
		GLCK(glGenBuffers(1, &vNormalId))
		GLCK(glGenBuffers(1, &indexBufferId))
	}

	void upload(const Mesh &mesh) {
		printf("upload mesh\n");
		GLCK(glBindVertexArray(vertexArrayId))

		GLCK(glBindBuffer(GL_ARRAY_BUFFER, vPositionId))
		GLCK(
				glBufferData(GL_ARRAY_BUFFER, mesh.positions.size() * sizeof(glm::vec3), mesh.positions.data(), GL_STATIC_DRAW))

		GLCK(glBindBuffer(GL_ARRAY_BUFFER, vTexCoordId))
		GLCK(
				glBufferData(GL_ARRAY_BUFFER, mesh.texcoords.size() * sizeof(glm::vec2), mesh.texcoords.data(), GL_STATIC_DRAW))

		GLCK(glBindBuffer(GL_ARRAY_BUFFER, vNormalId))
		GLCK(
				glBufferData(GL_ARRAY_BUFFER, mesh.normals.size() * sizeof(glm::vec3), mesh.normals.data(), GL_STATIC_DRAW))

		GLCK(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferId))
		GLCK(
				glBufferData(GL_ELEMENT_ARRAY_BUFFER, mesh.indices.size() * sizeof(glm::uvec3), mesh.indices.data(), GL_STATIC_DRAW))

		GLCK(glBindVertexArray(0))

		indexCount = mesh.indices.size() * 3;
	}

	void render() {
		GLCK(glBindVertexArray(vertexArrayId))
		GLCK(
				glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, (void* ) 0))
		GLCK(glBindVertexArray(0))

	}

	bool bindVertexAttrib(GLuint id, GLint loc, GLuint size) const {
		if (loc < 0) {
			return false;
		} else {
			GLCK(glBindBuffer(GL_ARRAY_BUFFER, id))
			GLCK(glEnableVertexAttribArray(loc))
			GLCK(glVertexAttribPointer(loc,                  // attribute
					size,// size
					GL_FLOAT,// type
					GL_FALSE,// normalized?
					0,// stride
					(void* ) 0// array buffer offset
					))
			return true;
		}

	}

	void update(int programId) const {
		int vPositionLoc, vNormalLoc, vTexCoordLoc;
		GLCK(vPositionLoc = glGetAttribLocation(programId, "vPosition"))
		GLCK(vTexCoordLoc = glGetAttribLocation(programId, "vTexCoord"))
		GLCK(vNormalLoc = glGetAttribLocation(programId, "vNormal"))

		GLCK(glBindVertexArray(vertexArrayId))

		bindVertexAttrib(vPositionId, vPositionLoc, 3);
		bindVertexAttrib(vNormalId, vNormalLoc, 3);
		bindVertexAttrib(vTexCoordId, vTexCoordLoc, 2);

		GLCK(glBindVertexArray(0))
	}
};

