//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <Render/Camera.hpp>
#include <Render/Film.hpp>
#include <Math/Matrix.hpp>
#include <cmath>
#include <cassert>

namespace Render
{
	//-----------------------------------------------------------------
	// C/D-Tor
	//-----------------------------------------------------------------
	Camera::Camera(float _focal, float _lensSize, float _fov, Film& _film):
	Focal(_focal),
	LensSize(_lensSize),
	Fov(_fov),
	Near(0.01f),
	Far(100.f),
	film(&_film)
	{
		// Data check
		assert(Focal>0);
		assert(LensSize>0);

		// Default camera position  : (0,0,0)
		// Default camera direction : (0,0,-5)
		SetPosition(Math::vec3(0.f,0.f,0.f), Math::vec3(0.f,0.f,-5.f));		
	}
	//-----------------------------------------------------------------
	Camera::~Camera()
	{

	}
	//-----------------------------------------------------------------
	void Camera::SetPosition(const Math::vec3& _position, const Math::vec3& _direction)
	{
	  WorldToCamera  = Math::lookAt(_position, _position+_direction, Math::vec3(0.0,1.0,0.0));
		CameraToScreen = Math::perspective(Fov, static_cast<float>(film->Width) / static_cast<float>(film->Height), Near, Far);
		ScreenToRaster = Math::viewport(0.f,0.f,film->Width,film->Height,Near,Far);

		RasterToWorld = Math::inverse(ScreenToRaster * (CameraToScreen * WorldToCamera));

//		printf("FOV:%f", Fov);
//		printf("WorldToCamera  : %s\n", Math::ToString(WorldToCamera).c_str());
//		printf("CameraToScreen : %s\n", Math::ToString(CameraToScreen).c_str());
//		printf("ScreenToRaster : %s\n", Math::ToString(ScreenToRaster).c_str());
//		printf("RasterToWorld  : %s\n", Math::ToString(RasterToWorld).c_str());
//		printf("RasterToWorld  : %s\n", Math::ToString(RasterToWorld).c_str());	
	}
	//-----------------------------------------------------------------
	void Camera::GenerateRay(const Sample& _sample, Ray& _ray) const
	{
		// Create ray in normalized projective space
		Math::vec4 start = RasterToWorld * Math::vec4(_sample.Position.x,_sample.Position.y,Near,1.);
		Math::vec4 end   = RasterToWorld * Math::vec4(_sample.Position.x,_sample.Position.y,Far,1.);
		
		// Divide homogeneous coordinate
		start /= start.w;
		end   /= end.w;

		// Set final direction
		_ray.Origin    = start.xyz;
		_ray.Direction = Math::normalize(end-start).xyz;

//		printf("Sample x:%f y:%f \nPosition:%s\n Diretion:%s\n", _sample.Position.x, _sample.Position.y, Math::ToString(_ray.Position).c_str(), Math::ToString(_ray.Direction).c_str());
//		printf("Ray.Position : %s\n", Math::ToString(_ray.Position).c_str());
//		printf("Ray.Direction : %s\n", Math::ToString(_ray.Direction).c_str());
	}
}
