/* Copyright (c) 2019-2024, Sascha Willems
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 the "License";
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "camera.h"

namespace vkb
{
void Camera::update_view_matrix()
{
	glm::mat4 rotation_matrix = glm::mat4(1.0f);
	glm::mat4 transformation_matrix;

	rotation_matrix = glm::rotate(rotation_matrix, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f));
	rotation_matrix = glm::rotate(rotation_matrix, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f));
	rotation_matrix = glm::rotate(rotation_matrix, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f));

	transformation_matrix = glm::translate(glm::mat4(1.0f), position);

	if (type == CameraType::FirstPerson)
	{
		matrices.view = rotation_matrix * transformation_matrix;
	}
	else
	{
		matrices.view = transformation_matrix * rotation_matrix;
	}

	updated = true;
}

bool Camera::moving()
{
	return keys.left || keys.right || keys.up || keys.down;
}

float Camera::get_near_clip()
{
	return znear;
}

float Camera::get_far_clip()
{
	return zfar;
}

void Camera::set_perspective(float fov, float aspect, float znear, float zfar)
{
	this->fov            = fov;
	this->znear          = znear;
	this->zfar           = zfar;
	matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
	updated              = true;
}

void Camera::update_aspect_ratio(float aspect)
{
	matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar);
	updated              = true;
}

void Camera::set_position(const glm::vec3 &position)
{
	this->position = position;
	update_view_matrix();
}

void Camera::set_rotation(const glm::vec3 &rotation)
{
	this->rotation = rotation;
	update_view_matrix();
}

void Camera::rotate(const glm::vec3 &delta)
{
	this->rotation += delta;
	update_view_matrix();
}

void Camera::set_translation(const glm::vec3 &translation)
{
	this->position = translation;
	update_view_matrix();
}

void Camera::translate(const glm::vec3 &delta)
{
	this->position += delta;
	update_view_matrix();
}

void Camera::update(float deltaTime)
{
	updated = false;
	if (type == CameraType::FirstPerson)
	{
		if (moving())
		{
			glm::vec3 front;
			front.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
			front.y = sin(glm::radians(rotation.x));
			front.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
			front   = glm::normalize(front);

			float move_speed = deltaTime * translation_speed;

			if (keys.up)
			{
				position += front * move_speed;
			}
			if (keys.down)
			{
				position -= front * move_speed;
			}
			if (keys.left)
			{
				position -= glm::normalize(glm::cross(front, glm::vec3(0.0f, 1.0f, 0.0f))) * move_speed;
			}
			if (keys.right)
			{
				position += glm::normalize(glm::cross(front, glm::vec3(0.0f, 1.0f, 0.0f))) * move_speed;
			}

			update_view_matrix();
		}
	}
}

bool Camera::update_gamepad(glm::vec2 axis_left, glm::vec2 axis_right, float delta_time)
{
	bool changed = false;

	if (type == CameraType::FirstPerson)
	{
		// Use the common console thumbstick layout
		// Left = view, right = move

		const float dead_zone = 0.0015f;
		const float range     = 1.0f - dead_zone;

		glm::vec3 front;
		front.x = -cos(glm::radians(rotation.x)) * sin(glm::radians(rotation.y));
		front.y = sin(glm::radians(rotation.x));
		front.z = cos(glm::radians(rotation.x)) * cos(glm::radians(rotation.y));
		front   = glm::normalize(front);

		float move_speed         = delta_time * translation_speed * 2.0f;
		float new_rotation_speed = delta_time * rotation_speed * 50.0f;

		// Move
		if (fabsf(axis_left.y) > dead_zone)
		{
			float pos = (fabsf(axis_left.y) - dead_zone) / range;
			position -= front * pos * ((axis_left.y < 0.0f) ? -1.0f : 1.0f) * move_speed;
			changed = true;
		}
		if (fabsf(axis_left.x) > dead_zone)
		{
			float pos = (fabsf(axis_left.x) - dead_zone) / range;
			position += glm::normalize(glm::cross(front, glm::vec3(0.0f, 1.0f, 0.0f))) * pos * ((axis_left.x < 0.0f) ? -1.0f : 1.0f) * move_speed;
			changed = true;
		}

		// Rotate
		if (fabsf(axis_right.x) > dead_zone)
		{
			float pos = (fabsf(axis_right.x) - dead_zone) / range;
			rotation.y += pos * ((axis_right.x < 0.0f) ? -1.0f : 1.0f) * new_rotation_speed;
			changed = true;
		}
		if (fabsf(axis_right.y) > dead_zone)
		{
			float pos = (fabsf(axis_right.y) - dead_zone) / range;
			rotation.x -= pos * ((axis_right.y < 0.0f) ? -1.0f : 1.0f) * new_rotation_speed;
			changed = true;
		}
	}
	else
	{
		// todo: move code from example base class for look-at
	}

	if (changed)
	{
		update_view_matrix();
	}

	return changed;
}
}        // namespace vkb
