#include "camera.h"

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/euler_angles.hpp>
#include <math.h>
#include <glm/gtx/matrix_decompose.hpp>

#include "object.h"
#include "utils.h"

namespace kek {

Camera::Camera() {
	this->position = glm::vec3(0.0f, 0.0f, 0.0f);
	this->direction = glm::vec3(0.0f, 0.0f, -1.0f);
	this->roll = 0;
}

void Camera::lookAt(glm::vec3 direction) {
	this->direction = glm::normalize(direction);
}

void Camera::lookAtPos(glm::vec3 pos) {
	this->direction = glm::normalize(pos - position);
}

void Camera::setYaw(float degrees) {
	glm::vec3 euler = eulerAngles();
	euler.x = degrees;
	applyEuler(euler);
}

void Camera::rotateYaw(float degrees) {
	glm::vec3 euler = eulerAngles();
	euler.x += degrees;
	applyEuler(euler);
}

void Camera::setPitch(float degrees) {
	glm::vec3 euler = eulerAngles();
	euler.y = degrees;
	applyEuler(euler);
}

void Camera::rotatePitch(float degrees) {
	glm::vec3 euler = eulerAngles();
	euler.y += degrees;
	applyEuler(euler);
}

void Camera::setRoll(float degrees) {
	roll = degrees;
}

void Camera::rotateRoll(float degrees) {
	roll += degrees;
}

glm::mat4 Camera::transformationMatrix() {
	float rollRad = glm::radians(roll);
	float x = sin(rollRad);
	float y = cos(rollRad);
	glm::vec3 up = glm::vec3(x, y, 0.0f);
	//glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
	glm::vec3 right = glm::normalize(glm::cross(direction, up));
	glm::vec3 cameraUp = glm::normalize(glm::cross(right, direction));

	glm::mat4 transform = glm::mat4(1.0f);
	transform[0][0] = right.x;
	transform[1][0] = right.y;
	transform[2][0] = right.z;
	transform[0][1] = cameraUp.x;
	transform[1][1] = cameraUp.y;
	transform[2][1] = cameraUp.z;
	transform[0][2] = -direction.x;
	transform[1][2] = -direction.y;
	transform[2][2] = -direction.z;
	transform[3][0] = -glm::dot(right, position);
	transform[3][1] = -glm::dot(cameraUp, position);
	transform[3][2] = glm::dot(direction, position);

	return transform;
}

void Camera::lookAtMatrix(glm::mat4 mat) {
	glm::quat q;
	float root, trace = mat[0].x + mat[1].y + mat[2].z;
	root = sqrt(trace + 1.0f);
	q.w = 0.5f * root;
	root = 0.5f / root;
	q.x = root * (mat[1].z - mat[2].y);
	q.y = root * (mat[2].x - mat[0].z);
	q.z = root * (mat[0].y - mat[1].x);
	this->direction = glm::vec3(0.0, 0.0, -1.0) * q;
}

// x = yaw, y = pitch, z = roll
glm::vec3 Camera::eulerAngles() {
	// Yaw
	float yawDegrees;
	if(direction.x == 0 && direction.z == 0) {
		yawDegrees = 0;
	}else {
		glm::vec2 xz = glm::normalize(glm::vec2(direction.x, direction.z)); // Normalized XZ coordinates
		yawDegrees = glm::degrees(atan2(-xz.y, xz.x)) + 90.0f; // -z because OpenGL
		if(yawDegrees > 180.0f) {
			yawDegrees -= 360.0f;
		}
	}

	// Pitch
	float pitchDegrees = glm::degrees(atan(direction.y / sqrt(direction.x * direction.x + direction.z * direction.z)));
	return glm::vec3(yawDegrees, pitchDegrees, roll);
}

void Camera::applyEuler(glm::vec3 euler) {
	euler.y = clamp(euler.y, -89.0f, 89.0f);

	direction.x = sin(glm::radians(euler.x)) * cos(glm::radians(euler.y)); // sin(yaw) * cos(pitch)
	direction.y = sin(glm::radians(euler.y)); // sin(pitch)
	direction.z = cos(glm::radians(euler.x)) * cos(glm::radians(euler.y)); // cos(yaw) * cos(pitch)
	roll = euler.z;
}

}