
#include <Spectre/Math/Math.h>
#include <Spectre/Scene/Camera2D.h>

Camera2D::Camera2D() :
m_position		(0.0f, 0.0f),
m_rotation		(0.0f),
m_zoom			(1.0f),
m_size			(0, 0),
m_viewNeedsUpdate	(false),
m_projNeedsUpdate	(false),
m_view			(Matrix4f::Identity),
m_proj			(Matrix4f::Identity)
{
	reset(Vector2f(0, 0), Vector2f(800, 600));
}

Camera2D::Camera2D(const Vector2f& position, const Vector2f& size) :
m_position		(position),
m_rotation		(0.0f),
m_zoom			(1.0f),
m_size			(size),
m_viewNeedsUpdate	(true),
m_projNeedsUpdate	(true),
m_projViewNeedsUpdate	(true),
m_view			(Matrix4f::Identity),
m_proj			(Matrix4f::Identity),
m_projView		(Matrix4f::Identity)
{
}

void Camera2D::reset(const Vector2f& position, const Vector2f& size)
{
	m_position = size / 2.0f;
	m_size = size;

	m_projNeedsUpdate = true;
	m_viewNeedsUpdate = true;
	m_projViewNeedsUpdate = true;
}

void Camera2D::setPosition(float x, float y)
{
	m_position.x = x;
	m_position.y = y;
	m_viewNeedsUpdate = true;
	m_projViewNeedsUpdate = true;
}

void Camera2D::setPosition(const Vector2f& position)
{
	setPosition(position.x, position.y);
}

const Vector2f& Camera2D::getPosition() const
{
	return m_position;
}

void Camera2D::move(float x, float y)
{
	setPosition(m_position.x + x, m_position.y + y);
}

void Camera2D::move(const Vector2f& vec)
{
	setPosition(m_position + vec);
}

void Camera2D::moveUp(float delta)
{
	vec2f dir = m_view.getUpVector() * vec2f(1.0f, -1.0f);

	move(dir * delta);
}

void Camera2D::moveRight(float delta)
{
	vec2f dir = m_view.getRightVector() * vec2f(1.0f, -1.0f);

	move(dir * delta);
}

void Camera2D::setSize(const Vector2u& size)
{
	if (m_size != size) {
		m_size = size;
		m_viewNeedsUpdate = true;
		m_projNeedsUpdate = true;
		m_projViewNeedsUpdate = true;
	}
}

void Camera2D::setSize(unsigned int w, unsigned int h)
{
	setSize(Vector2u(w, h));
}

const Vector2u& Camera2D::getSize() const
{
	return m_size;
}

void Camera2D::setZoom(float factor)
{
	if (factor < 0.0f) {
		factor = 0.0f;
	}
	m_zoom = factor;
	m_viewNeedsUpdate = true;
	m_projViewNeedsUpdate = true;
}

void Camera2D::zoom(float factor)
{
	setZoom(m_zoom + factor);
}

void Camera2D::setRotation(float angle)
{
	m_rotation = angle;
	m_viewNeedsUpdate = true;
	m_projViewNeedsUpdate = true;
}

float Camera2D::getRotation() const
{
	return m_rotation;
}

void Camera2D::rotate(float delta)
{
	setRotation(m_rotation + delta);
}

const Matrix4f& Camera2D::getViewMatrix() const
{
	return getTransform().getMatrix();
}

const Matrix4f& Camera2D::getProjectionMatrix() const
{
	if (m_projNeedsUpdate) {
		float w = static_cast<float>(m_size.x);
		float h = static_cast<float>(m_size.y);
		m_proj = math::orthoProjection(0.0f, w, h, 0.0f);
		m_projNeedsUpdate = false;
	}
	return m_proj;
}

const Matrix4f& Camera2D::getProjectionViewMatrix() const
{
	if (m_projViewNeedsUpdate) {
		m_projView = getProjectionMatrix() * getViewMatrix();
		m_projViewNeedsUpdate = false;
	}
	return m_projView;
}

const Transform& Camera2D::getTransform() const
{
	if (m_viewNeedsUpdate) {
		Vector2f half_size = Vector2f(m_size) / 2.0f;

		m_view.reset();
		m_view.translate(half_size);
		m_view.scale(m_zoom);
		m_view.rotate(m_rotation);
		m_view.translate(-m_position);
		m_viewNeedsUpdate = false;
	}
	return m_view;
}