#include <GL/glew.h>
#include <glm/mat4x4.hpp>
#include <glm/gtc/type_ptr.hpp>

#include <graphics.hpp>
#include <modelpart.hpp>

#include <iostream>

std::map<std::string, int> bindStringToNum {
	{"null", BIND_NULL},
	{"head", BIND_HEAD},
	{"face", BIND_FACE},
	{"offset-eyes", OFFSET_EYES},
};

std::map<std::string, bool> triggerStringToNum {
	{"null", TRIGGER_NULL},
	{"mouth-open", TRIGGER_MOUTH_OPEN},
};

ModelPart::ModelPart() {
	//create vbo, ebo, vao
	initBuffers(&vao);

	empty = false;
}

void ModelPart::bindAndDraw() {
	glBindVertexArray(vao);
	glBindTexture(GL_TEXTURE_2D, tex[texSelection]);
	glUniformMatrix4fv(transUniform, 1, GL_FALSE, glm::value_ptr(transMatrix));
	glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}

void ModelPart::setBind(std::string bindName) {
       bind = bindStringToNum[bindName];
}

void ModelPart::setFollowTarget(std::string followTarget) {
       follow = bindStringToNum[followTarget];
}

void ModelPart::setOffsetBind(std::string bindName) {
       offsetBind = bindStringToNum[bindName];
}

void ModelPart::smoothTransform(glm::vec2 position, float rotation, float scale) {
	histPositions[histI] = position;
	histRotations[histI] = rotation;
	histScales[histI] = scale;

	histI++;
	histI = histI % SMOOTHING_FRAMES;

	glm::vec2 smoothedPosition(0,0);
	float smoothedRotation = 0;
	float smoothedScale = 0;

	for(size_t i = 0; i < SMOOTHING_FRAMES; i++) {
		smoothedPosition += histPositions[i];
		smoothedRotation += histRotations[i];
		smoothedScale += histScales[i];
	}

	smoothedPosition /= SMOOTHING_FRAMES;
	smoothedRotation /= SMOOTHING_FRAMES;
	smoothedScale /= SMOOTHING_FRAMES;
	
	setTransform(smoothedPosition, smoothedRotation, smoothedScale);
}

void ModelPart::setTransform(glm::vec2 position, float rotation, float scale) {
	transMatrix = glm::ortho(-windowAspectRatio, windowAspectRatio, -1.0f, 1.0f);
	transMatrix = glm::translate(transMatrix, glm::vec3(position.x, -position.y, 0.0f) + glm::vec3(posOffset, 0.0f));
	transMatrix = glm::rotate(transMatrix, rotation * rotFactor, glm::vec3(0.0f, 0.0f, 1.0f));
	transMatrix = glm::scale(transMatrix, glm::vec3(1,1,1) + (scale - 1 + glm::vec3(scaleOffset, 0.0f)) * scaleFactor);
	transMatrix = glm::translate(transMatrix, glm::vec3(-origin, 0.0f));
}

void ModelPart::processFaceData(struct FaceData faceData) {
	// calculate position
	glm::vec2 bindPosition = faceData.positions[bind];
	glm::vec2 followPosition = faceData.positions[follow];
	glm::vec2 followDirection = followPosition - bindPosition;
	glm::vec2 offset = faceData.positions[offsetBind] * offsetFactor * faceData.scale;
	glm::vec2 newPosition = bindPosition + offset + (followDirection * factor);

	smoothTransform(newPosition, faceData.headRotation, faceData.scale);

	// change textures
	selectTexture(0);	// if none are triggered, use the first one
	for (size_t i = 0; i < texCount; i++) {
		if (faceData.triggers[texTriggers[i]]) {
			selectTexture(i);
			break;	// if several textures are triggered, the first one defined in model.toml will get priority
		}
	}
}

void ModelPart::addTexture(unsigned char* texBuffer, size_t texLength, size_t slot, std::string triggerName) {
	initTexture(&tex[slot], texBuffer, texLength);
	texTriggers[slot] = triggerStringToNum[triggerName];
	texCount++;
}

void ModelPart::selectTexture(size_t slot) {
	texSelection = slot;
}
