#include "utils.h"

#include <vector>

namespace kek {

float clampCyclic(float value, float min, float max) {
	while(value > max) value -= (max - min);
	while(value < min) value += (max - min);
	return value;
}

float clamp(float value, float min, float max) {
	if(value > max) value = max;
	if(value < min) value = min;
	return value;
}

float clampCyclic(int value, int min, int max) {
	while(value > max) value -= (max - min);
	while(value < min) value += (max - min);
	return value;
}

float clamp(int value, int min, int max) {
	if(value > max) value = max;
	if(value < min) value = min;
	return value;
}

glm::vec3 faceNormal(CubeFace face) {
	switch(face) {
		case CubeFace::FRONT:
			return glm::vec3(0.0f, 0.0f, 1.0f);
		case CubeFace::BACK:
			return glm::vec3(0.0f, 0.0f, -1.0f);
		case CubeFace::UP:
			return glm::vec3(0.0f, 1.0f, 0.0f);
		case CubeFace::DOWN:
			return glm::vec3(0.0f, -1.0f, 0.0f);
		case CubeFace::LEFT:
			return glm::vec3(-1.0f, 0.0f, 0.0f);
		case CubeFace::RIGHT:
			return glm::vec3(1.0f, 0.0f, 0.0f);
	}

	return glm::vec3(0.0f, 0.0f, 0.0f);
}

float cubeTriangles[] = {
		// Front
		-0.5f, -0.5f,  0.5f, // 0
		 0.5f, -0.5f,  0.5f, // 1
		 0.5f,  0.5f,  0.5f, // 2
		 0.5f,  0.5f,  0.5f, // 2
		-0.5f,  0.5f,  0.5f, // 3
		-0.5f, -0.5f,  0.5f, // 0

		// Back
		 0.5f, -0.5f, -0.5f, // 5
		-0.5f, -0.5f, -0.5f, // 4
		-0.5f,  0.5f, -0.5f, // 7
		-0.5f,  0.5f, -0.5f, // 7
		 0.5f,  0.5f, -0.5f, // 6
		 0.5f, -0.5f, -0.5f, // 5

		// Up
		-0.5f,  0.5f,  0.5f, // 3
		 0.5f,  0.5f,  0.5f, // 2
		 0.5f,  0.5f, -0.5f, // 6
		 0.5f,  0.5f, -0.5f, // 6
		-0.5f,  0.5f, -0.5f, // 7
		-0.5f,  0.5f,  0.5f, // 3

		// Down
		 0.5f, -0.5f, -0.5f, // 5
		 0.5f, -0.5f,  0.5f, // 1
		-0.5f, -0.5f,  0.5f, // 0
		-0.5f, -0.5f,  0.5f, // 0
		-0.5f, -0.5f, -0.5f, // 4
		 0.5f, -0.5f, -0.5f, // 5

		// Left
		-0.5f, -0.5f, -0.5f, // 4
		-0.5f, -0.5f,  0.5f, // 0
		-0.5f,  0.5f,  0.5f, // 3
		-0.5f,  0.5f,  0.5f, // 3
		-0.5f,  0.5f, -0.5f, // 7
		-0.5f, -0.5f, -0.5f, // 4

		// Right
		 0.5f, -0.5f,  0.5f, // 1
		 0.5f, -0.5f, -0.5f, // 5
		 0.5f,  0.5f, -0.5f, // 6
		 0.5f,  0.5f, -0.5f, // 6
		 0.5f,  0.5f,  0.5f, // 2
		 0.5f, -0.5f,  0.5f, // 1
};

float textureCoordinates[] = {
	// Front
	0.0f, 0.0f, // 0
	1.0f, 0.0f, // 1
	1.0f, 1.0f, // 2
	1.0f, 1.0f, // 2
	0.0f, 1.0f, // 3
	0.0f, 0.0f, // 0

	// Back
	0.0f, 0.0f, // 5
	1.0f, 0.0f, // 4
	1.0f, 1.0f, // 7
	1.0f, 1.0f, // 7
	0.0f, 1.0f, // 6
	0.0f, 0.0f, // 5

	// Up
	0.0f, 0.0f, // 3
	1.0f, 0.0f, // 2
	1.0f, 1.0f, // 6
	1.0f, 1.0f, // 6
	0.0f, 1.0f, // 7
	0.0f, 0.0f, // 3

	// Down
	1.0f, 1.0f, // 5
	1.0f, 0.0f, // 1
	0.0f, 0.0f, // 0
	0.0f, 0.0f, // 0
	0.0f, 1.0f, // 4
	1.0f, 1.0f, // 5

	// Left
	0.0f, 0.0f, // 4
	1.0f, 0.0f, // 0
	1.0f, 1.0f, // 3
	1.0f, 1.0f, // 3
	0.0f, 1.0f, // 7
	0.0f, 0.0f, // 4

	// Right
	0.0f, 0.0f, // 1
	1.0f, 0.0f, // 5
	1.0f, 1.0f, // 6
	1.0f, 1.0f, // 6
	0.0f, 1.0f, // 2
	0.0f, 0.0f, // 1
};

std::vector<kek::Vertex> genCubeVertices(float w, float h, float d, bool texCoords) {
	std::vector<kek::Vertex> vertices;

	for(int i = 0; i < 6; i++) {
		CubeFace f = (CubeFace) i;
		glm::vec3 normal = faceNormal(f);
		for(int v = 0; v < 6; v++) {

			kek::Vertex vertex(
				glm::vec3(cubeTriangles[i * 18 + v * 3] * w, cubeTriangles[i * 18 + v * 3 + 1] * h, cubeTriangles[i * 18 + v * 3 + 2] * d),
				normal,
				texCoords ? glm::vec2(textureCoordinates[i * 12 + v * 2], textureCoordinates[i * 12 + v * 2 + 1]) : glm::vec2());

			vertices.push_back(vertex);
		}
	}

	return vertices;
}

Mesh *genCubeMesh(float w, float h, float d, std::shared_ptr<Texture> ambient, std::shared_ptr<Texture> diffuse, std::shared_ptr<Texture> specular) {
	std::vector<Vertex> vertices = genCubeVertices(w, h, d, true);
	std::vector<unsigned int> indices;
	for(size_t i = 0; i < vertices.size(); i++) {
		indices.push_back(i);
	}
	Material *material = new Material(ambient, diffuse, specular, 80.0f); // FIXME: hard-coded shininess
	return new Mesh(vertices, indices, material);
}

std::string toString(glm::vec3 vector) {
	return std::to_string(vector.x) + " " + std::to_string(vector.y) + " " + std::to_string(vector.z);
}

}
