#include "Camera.h"
#include <memory>
#include <glm/gtx/rotate_vector.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <iostream>
logic::Camera::Camera() {};

logic::Camera::Camera(
	float speed,
	float screenRatio, 
	float FOV,
	float nearZ, 
	float farZ, 
	const glm::vec3 & position, 
	const glm::vec3 & direction,
	const glm::vec3 & upAxis)
: 
m_speed(speed),
m_screenRatio(screenRatio),
m_FOV(FOV),
m_nearZ(nearZ),
m_farZ(farZ),
m_position(position), 
m_originalPosition(position),
m_direction(direction), 
m_upAxis(upAxis), 
m_originalDirection(direction),
m_originalUpAxis(upAxis),
m_verticalAngleRad(0.0f), 
m_horizontalAngleRad(0.0f) {};

glm::mat4 logic::Camera::getTransformation() const {
	return glm::lookAt(m_position, m_position+m_direction, m_upAxis);
}

glm::mat4 logic::Camera::getOrientationTransformation() const {
	return glm::lookAt(m_originalPosition, m_direction, m_upAxis);
}

glm::mat4 logic::Camera::getProjectionTransformation() const {
	return glm::perspective<float>(m_FOV, m_screenRatio, m_nearZ, m_farZ);
}

glm::mat4 logic::Camera::getOrthoTransformation() const {
	return glm::frustum(-1.0f, 1.0f, -m_screenRatio, m_screenRatio, m_nearZ, m_farZ);
}

void logic::Camera::move(const glm::vec3 & delta) {
	if (delta.z > 0.0f) {
		m_position -= glm::normalize(m_direction)*m_speed;
	}
	else if (delta.z < 0.0f) {
		m_position += glm::normalize(m_direction)*m_speed;
	}
	else if (delta.x > 0.0f) {
		glm::vec3 d = glm::normalize(glm::cross(m_direction, m_upAxis))*m_speed;
		m_position += d;
	}
	else if (delta.x < 0.0f) {
		glm::vec3 d = glm::normalize(glm::cross(m_upAxis, m_direction))*m_speed;
		m_position += d;
	}
}

void logic::Camera::orientate(int dx, int dy) {
	static float verticalAngleRadLimit = glm::radians(90.0f);

	float horizontalAngleDeltaRad = glm::radians(float(-dx) / 20.0f);
	float verticalAngleDeltaRad = glm::radians(float(dy) / 20.0f);
	
	m_verticalAngleRad += verticalAngleDeltaRad;
	m_horizontalAngleRad += horizontalAngleDeltaRad;

	if (m_verticalAngleRad > verticalAngleRadLimit) {
		verticalAngleDeltaRad -= (m_verticalAngleRad - verticalAngleRadLimit);
		m_verticalAngleRad = verticalAngleRadLimit;
	}
	else if (m_verticalAngleRad < -verticalAngleRadLimit) {
		verticalAngleDeltaRad -= (m_verticalAngleRad + verticalAngleRadLimit);
		m_verticalAngleRad = -verticalAngleRadLimit;
	}

	updateOrientation();
	
}

void logic::Camera::feedFrustrum(graphics::ShaderProgram * program) const {
	glm::mat4 perspectiveProj = glm::perspective<float>(m_FOV, m_screenRatio, m_nearZ, m_farZ);
	GLint projectionLoc = program->getLocation("PROJECTION");
	glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(perspectiveProj));
}

void logic::Camera::updateOrientation() {
	glm::vec3 zaxis = m_originalDirection;
	glm::vec3 yaxis = m_originalUpAxis;
	glm::vec3 xaxis = glm::normalize(glm::cross(zaxis, yaxis));
	zaxis = glm::normalize(glm::rotate(zaxis, m_horizontalAngleRad, yaxis));
	xaxis = glm::normalize(glm::cross(yaxis, zaxis));
	zaxis = glm::normalize(glm::rotate(zaxis, m_verticalAngleRad, xaxis));
	yaxis = glm::normalize(glm::cross(zaxis, xaxis));

	m_direction = zaxis;
	m_upAxis = yaxis;
}
void logic::Camera::feedUVN(graphics::ShaderProgram * program) const {
	glm::mat4 uvn = glm::lookAt(m_position, m_position + m_direction, m_upAxis);
	GLint projectionLoc = program->getLocation("UVN");
	glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(uvn));
}

glm::vec3 logic::Camera::getPosition() const {
	return m_position;
}

glm::vec3 logic::Camera::getDirection() const {
	return m_direction;
}

void logic::Camera::feedCameraData(graphics::ShaderProgram * program) const {
	GLint camEyeLoc = program->getLocation("CAM_EYE");
	GLint camPosLoc = program->getLocation("CAP_POS");

	glUniform3f(camEyeLoc, m_direction[0], m_direction[1], m_direction[2]);
	glUniform3f(camPosLoc, m_position[0], m_position[1], m_position[2]);
}
