#include "mesh.h"

#include "constants.h"

#include <GL/glew.h>

namespace kek {

Vertex::Vertex(glm::vec3 pos, glm::vec3 normal, glm::vec2 texCoords) {
	this->pos = pos;
	this->normal = normal;
	this->texCoords = texCoords;
}

Material::Material(std::shared_ptr<Texture> ambient, std::shared_ptr<Texture> diffuse, std::shared_ptr<Texture> specular, float shininess) {
	this->ambient = ambient;
	this->diffuse = diffuse;
	this->specular = specular;
	this->shininess = shininess;
}

Mesh::Mesh(std::vector<Vertex> vertices, std::vector<uint32_t> indices, Material *material) {
	this->vertices = vertices;
	this->indices = indices;
	this->material = material;

	glCreateVertexArrays(1, &vao);
	glCreateBuffers(1, &vbo);
	glCreateBuffers(1, &ebo);

	//glBindVertexArray(vao);

	glNamedBufferData(vbo, vertices.size() * sizeof(Vertex), &vertices[0], GL_STATIC_DRAW);
	glNamedBufferData(ebo, indices.size() * sizeof(unsigned int), &indices[0], GL_STATIC_DRAW);

	glVertexArrayVertexBuffer(vao, KEK_MESH_VERTEX_BUFFER_BINDING, vbo, 0, sizeof(Vertex));

	glEnableVertexArrayAttrib(vao, KEK_MESH_VERTEX_SHADER_IN_POSITION);
	glVertexArrayAttribFormat(vao, KEK_MESH_VERTEX_SHADER_IN_POSITION, 3, GL_FLOAT, GL_FALSE, 0);
	glVertexArrayAttribBinding(vao, KEK_MESH_VERTEX_SHADER_IN_POSITION, KEK_MESH_VERTEX_BUFFER_BINDING);

	glEnableVertexArrayAttrib(vao, KEK_MESH_VERTEX_SHADER_IN_NORMAL);
	glVertexArrayAttribFormat(vao, KEK_MESH_VERTEX_SHADER_IN_NORMAL, 3, GL_FLOAT, GL_FALSE, offsetof(Vertex, normal));
	glVertexArrayAttribBinding(vao, KEK_MESH_VERTEX_SHADER_IN_NORMAL, KEK_MESH_VERTEX_BUFFER_BINDING);

	glEnableVertexArrayAttrib(vao, KEK_MESH_VERTEX_SHADER_IN_TEXCOORD);
	glVertexArrayAttribFormat(vao, KEK_MESH_VERTEX_SHADER_IN_TEXCOORD, 2, GL_FLOAT, GL_FALSE, offsetof(Vertex, texCoords));
	glVertexArrayAttribBinding(vao, KEK_MESH_VERTEX_SHADER_IN_TEXCOORD, KEK_MESH_VERTEX_BUFFER_BINDING);

	glVertexArrayElementBuffer(vao, ebo);

	GLenum err = glGetError();
	if(err != GL_NO_ERROR) {
		std::cout << "OpenGL error while loading mesh: " << err << std::endl;
		exit(1);
	}
}

Mesh::~Mesh() {
	glDeleteBuffers(1, &ebo);
	glDeleteBuffers(1, &vbo);
	glDeleteVertexArrays(1, &vao);
	delete material;
}

static void bindTextureUniform(Shader *shader, std::string name, std::shared_ptr<Texture> texture, GLuint *textureIndex) {
	GLint loc = glGetUniformLocation(shader->id, name.c_str());
	if(loc != -1) {
		texture->use(*textureIndex);
		glUniform1i(loc, *textureIndex - GL_TEXTURE0);
		(*textureIndex)++;
	}
}

void Mesh::draw(Shader *shader) {
	glBindVertexArray(vao);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);

	GLuint textureIndex = GL_TEXTURE0;
	bindTextureUniform(shader, "material.ambient", material->ambient, &textureIndex);
	bindTextureUniform(shader, "material.diffuse", material->diffuse, &textureIndex);
	bindTextureUniform(shader, "material.specular", material->specular, &textureIndex);
	glUniform1f(glGetUniformLocation(shader->id, "material.shininess"), material->shininess);

	// Bind vertices + texture coordinates
	glDrawElements(GL_TRIANGLES, indices.size(), GL_UNSIGNED_INT, 0);
	glBindBuffer(GL_ARRAY_BUFFER, 0);
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	glBindVertexArray(0);
}

}
