//-----------------------------------------------------------------------------
// Includes
//-----------------------------------------------------------------------------
#include <Math/Random.hpp>

#include <Render/Sphere.hpp>
#include <Render/Triangle.hpp>
#include <Render/Accelerator.hpp>
#include <Render/Reflection.hpp>
#include <Render/WhittedIntegrator.hpp>
#include <Render/Scene.hpp>
#include <Render/Camera.hpp>
#include <Render/DDSImage.hpp>
#include <Render/PPMImage.hpp>
#include <Render/Sampler.hpp>


using namespace Render;
//TODO
// - AABB intersect
// - Visibility tester
// - BVH
// - whitted
// - reflection
// - mesh loader
// - area light

int main()
{
	Math::Random::Initialize();

	const int width  = 512;
	const int height = 512;
	const int rayPerPixel = 10;

	const float focal = 1.5f;
	const float lens  = 1.f;
	const float fov   = 75.f;

	TMaterialVector materials;

	Lambertian lamb(Math::vec3(1.f,0,0.5f));
	BSDF bsdf0;
	bsdf0.Add(&lamb);
	materials.push_back(&bsdf0);

	FresnelDielectric fresnel(1.0f, 1.31f);
	SpecularReflection spec(Math::vec3(0.8f),&fresnel);
	BSDF bsdf1;
	bsdf1.Add(&spec);
	materials.push_back(&bsdf1);


	Math::mat4 sphereTransfo0 = Math::inverse(Math::translate(2.f,1.f,1.f));
	Sphere sphere0(sphereTransfo0, 0, 1.f);

	Math::mat4 sphereTransfo1 = Math::inverse(Math::translate(-3.f,3.f,-2.f));
	Sphere sphere1(sphereTransfo1, 1, 1.5f);

	const float size = 15.f;
	Math::mat4 triangleTransfo0 = Math::inverse(Math::translate(-size/2.f,0.f,-size/2.f));
	Math::vec3 p0(0.f,0.f,0.f); 	Math::vec2 uv0(0.f,0.f);
	Math::vec3 p1(size,0.f,0.f); 	Math::vec2 uv1(1.f,0.f); 
	Math::vec3 p2(0.f,0.f,size); 	Math::vec2 uv2(0.f,1.f);  
	Math::vec3 p3(size,0.f,size); 	Math::vec2 uv3(1.f,1.f);  

	Triangle tri0(triangleTransfo0, 0, p0, p2, p1, uv0, uv1, uv2);
	Triangle tri1(triangleTransfo0, 0, p2, p3, p1, uv0, uv1, uv2);

	const float sizeL = 5.f;
	Math::mat4 triangleTransfo1 = Math::inverse(Math::translate(2.f,5.f,2.f));
	Math::vec3 p0L(0.f,0.f,0.f); 	
	Math::vec3 p1L(sizeL,0.f,0.f); 	
	Math::vec3 p2L(0.f,0.f,sizeL); 	

	Triangle triL(triangleTransfo1, 0, p0L, p2L, p1L, uv0, uv1, uv2);


	Render::Aggregate aggregate;
	aggregate.Add(&sphere0);
	aggregate.Add(&sphere1);
	aggregate.Add(&tri0);
	aggregate.Add(&tri1);
//	aggregate.Add(&triL);

	WhittedIntegrator 	integrator(2);
	PPMImage 			film(width, height);
	Camera 				camera(focal, lens, fov, film);
	Sampler				sampler(width, height, rayPerPixel);

	Math::vec3 eye(5.f,10.f,-5.f);
	Math::vec3 center(0.f,0.f,0.f);
	camera.SetPosition(eye, Math::normalize(center-eye));

	TLightVector lights;

	Math::mat4 lightTransfo = Math::inverse(Math::translate(0.f,5.f,0.f));
	PointLight point(lightTransfo, Math::vec3(1.f));
	lights.push_back(&point);

//	AreaLight area(Math::vec3(10.f), 30, &sphere1);
//	lights.push_back(&area);

	Scene scene(&aggregate, lights, materials, &integrator, &film, &camera, &sampler);
	scene.Render();

	film.SaveToFile("Test.dds");

	Math::Random::Finalize();

/*
	float Width  = 128;
	float Height = 128;
	float Near   = 1.f;
	float Far    = 100.f;
	float Fov    = 90.f;

	Math::vec3 direction(0.f,0.f,-1.f);
	Math::vec3 position(0.f,0.f,0.f);

	Math::mat4 WorldToCamera  = Math::lookAt(position, position + direction, Math::vec3(0.0,1.0,0.0));
	Math::mat4 CameraToScreen = Math::perspective(Fov, static_cast<float>(Width) / static_cast<float>(Height), Near, Far);
	Math::mat4 ScreenToRaster = Math::viewport(0.f,0.f,Width,Height,Near,Far);

	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());
	
	Math::mat4 RasterToScreen = Math::inverse(ScreenToRaster);
	Math::mat4 RasterToCamera = Math::inverse(ScreenToRaster * CameraToScreen);
	Math::mat4 RasterToWorld  = Math::inverse(ScreenToRaster * (CameraToScreen * WorldToCamera));

*/


/*
	float Width = 128;
	float Height = 128;
	float Near = 0.01f;
	float Far  = 100.f;
	float Fov   = 90.f;

	Math::vec3 direction(0.f,0.f,1.f);
	Math::vec3 position(0.f,0.f,0.f);
	Math::mat4 WorldToCamera, CameraToScreen, ScreenToRaster;
	WorldToCamera  = Math::lookAt(position, position + direction, Math::vec3(0.0,1.0,0.0));
	CameraToScreen = Math::perspective(Fov, static_cast<float>(Width) / static_cast<float>(Height), Near, Far);
	ScreenToRaster = Math::viewport(0.f,0.f,Width,Height,Near,Far);

	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());
	
	Math::mat4 RasterToScreen = Math::inverse(ScreenToRaster);
	Math::mat4 RasterToCamera = Math::inverse(ScreenToRaster * CameraToScreen);
	Math::mat4 RasterToWorld  = Math::inverse(ScreenToRaster * (CameraToScreen * WorldToCamera));

	Ray _ray;
//	Math::vec2 Position(128,128);
	Math::vec2 Position(0,0);
	{
		Math::vec4 start = RasterToScreen * Math::vec4(Position.x,Position.y,Near,1.);
		Math::vec4 end   = RasterToScreen * Math::vec4(Position.x,Position.y,Far,1.);
		_ray.Position  = start.xyz;
		_ray.Direction = Math::normalize(end-start).xyz;
		printf("_ray.Position  : %s\n", Math::ToString(_ray.Position).c_str());
//		printf("_ray.Direction : %s\n", Math::ToString(_ray.Direction).c_str());
		printf("_ray.Direction : %s\n", Math::ToString(end.xyz).c_str());
	}

	{
		Math::vec4 start = RasterToCamera * Math::vec4(Position.x,Position.y,Near,1.);
		Math::vec4 end   = RasterToCamera * Math::vec4(Position.x,Position.y,Far,1.);
		_ray.Position  = start.xyz;
		_ray.Direction = Math::normalize(end-start).xyz;
		printf("_ray.Position  : %s\n", Math::ToString(_ray.Position).c_str());
//		printf("_ray.Direction : %s\n", Math::ToString(_ray.Direction).c_str());
		printf("_ray.Direction : %s\n", Math::ToString(end.xyz).c_str());
	}

	{
		Math::vec4 start = RasterToWorld * Math::vec4(Position.x,Position.y,Near,1.);
		Math::vec4 end   = RasterToWorld * Math::vec4(Position.x,Position.y,Far,1.);
		_ray.Position  = start.xyz;
		_ray.Direction = Math::normalize(end-start).xyz;
		printf("_ray.Position  : %s\n", Math::ToString(_ray.Position).c_str());
//		printf("_ray.Direction : %s\n", Math::ToString(_ray.Direction).c_str());
		printf("_ray.Direction : %s\n", Math::ToString(end.xyz).c_str());
	}
*/

/*
	Ray _ray;
	// Create ray in normalized projective space
	Math::vec4 start = RasterToWorld * Math::vec4(Position.x,Position.y,Near,1.);
	Math::vec4 end   = RasterToWorld * Math::vec4(Position.x,Position.y,Far,1.);
	
	// Divide homogeneous coordinate
	start /= start.w;
	end   /= end.w;

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

	printf("_ray.Position  : %s\n", Math::ToString(_ray.Position).c_str());
	printf("_ray.Direction : %s\n", Math::ToString(_ray.Direction).c_str());*/
	return 0;
}

