#include "stdafx.h"
#include "defines.h"

#include "Scene.hxx"
#include "Triangle.hxx"
#include "Image.hxx"
#include "PerspectiveCamera.hxx"
#include "Sphere.hxx"
#include "InfinitePlane.hxx"

#include "EyeLightShader.hxx"
#include "PhongShader.hxx"
#include "BumpMappedPhongShader.hxx"
#include "PointLight.hxx"
#include "TexturedEyeLightShader.hxx"

#include "SampleGenerator.hxx"
#include "RegularSampleGenerator.hxx"
#include "RandomSampleGenerator.hxx"
#include "StratifiedSampleGenerator.hxx"

void RenderFrame(Scene &aScene, int aSampleCnt, SampleGenerator *aSampler)
{
	Image img(aScene.camera->resX,aScene.camera->resY);        // Image array
	Ray   ray;                                                 // Primary ray

	for (int y=0;y<aScene.camera->resY;y++)
		for (int x=0;x<aScene.camera->resX;x++)
		{
			float u[aSampleCnt],
			      v[aSampleCnt],
			      weight[aSampleCnt];

			aSampler->GetSamples(aSampleCnt, u, v, weight);

			img[y][x] = 0;

			for (int i = 0; i < aSampleCnt; ++i)
			{
				aScene.camera->InitRay(x + u[i], y + v[i], ray); // Initialize ray
				img[y][x] += weight[i] * aScene.RayTrace(ray);
			}

			// Color values must be clamped between 0 and 1
			img[y][x] = Min(Max(img[y][x],Vec3f(0,0,0)),Vec3f(1,1,1));
		}

		img.WritePPM("result.ppm"); // Write final image
}

int main(int argc, char* argv[])
{
	int exNum = -1;
	if(argc > 1)
		exNum = atol(argv[1]);

	Scene scene(exNum == 4);

	//////////////////////////////////////////////////////////////////////////
	//Note: the primitive factory for exercise 1
	//////////////////////////////////////////////////////////////////////////
	Scene::SmoothTriangleFactory *smoothTriangleFactory = new Scene::SmoothTriangleFactory;
	smoothTriangleFactory->shader = new EyeLightShader(&scene, Vec3f(1.0,0.5,0.0));

	//////////////////////////////////////////////////////////////////////////
	//Note: A triangle factory for test purposes
	//////////////////////////////////////////////////////////////////////////
	Scene::TriangleFactory *triangleFactory = new Scene::TriangleFactory;
	triangleFactory->shader = new EyeLightShader(&scene, Vec3f(1.0,0.5,0.0));

	SampleGenerator *sampler = new RegularSampleGenerator;
	int sampleCount = 1;
	if(argc == 4)
	{
		sampleCount = atol(argv[3]);
		if(strcmp(argv[2], "random") == 0)
			sampler = new RandomSampleGenerator;
		if(strcmp(argv[2], "stratified") == 0)
			sampler = new StratifiedSampleGenerator;
	}
	switch(exNum)
	{
		case 1:
			//////////////////////////////////////////////////////////////////////////
			//TODO: Change to smoothTriangleFactory once you get the obj loader working
			//////////////////////////////////////////////////////////////////////////
			scene.ParseOBJ("cylinder16.obj", smoothTriangleFactory);
			scene.camera = 
				new PerspectiveCamera(Vec3f(0,0,-0.8f),Vec3f(0,0,1),Vec3f(0,1,0),60,640,480);
			break;
		case 2:
			scene.ParseOBJ("cone32.obj", smoothTriangleFactory);
			scene.camera = 
				new PerspectiveCamera(Vec3f(0,0,-0.8f),Vec3f(0,0,1),Vec3f(0,1,0),60,640,480);
			break;
		case 3:
			{
				Shader *shd;

				shd = new BumpMappedPhongShader(&scene, Vec3f(1,0,0), 0.1f, 0.5f, 0.5f, 40); // Red surface
				scene.Add(new Sphere(Vec3f(-2, 1.7f, 0), 2, shd));

				shd = new           PhongShader(&scene, Vec3f(0,1,1), 0.1f, 0.5f, 0.5f, 40); // Cyan surface
				scene.Add(new Sphere(Vec3f(1, -1, 1), 2.2f, shd));

				shd = new           PhongShader(&scene, Vec3f(0,0,1), 0.1f, 0.5f, 0.5f, 40); // Blue surface
				scene.Add(new Sphere(Vec3f(3, 0.8f, -2), 2, shd));

				shd = new BumpMappedPhongShader(&scene, Vec3f(1,1,0), 0.1f, 0.5f, 0.5f, 40); // Yellow surface
				scene.Add(new InfinitePlane(Vec3f(0, -1, 0), Vec3f(0, 1, 0), shd));


				scene.Add(new PointLight(&scene, Vec3f(-3,5,+4), Vec3f(7,7,7)));
				scene.Add(new PointLight(&scene, Vec3f(0,1,+4), Vec3f(7,7,7)));

				scene.camera = new PerspectiveCamera(Vec3f(0,0,10),Vec3f(0,0,-1), Vec3f(0,1,0),60,640,480);
			}
			break;
		case 4:
			{
				Scene::TexturedSmoothTriangleFactory *texTriangleFactory = new Scene::TexturedSmoothTriangleFactory;
				texTriangleFactory->shader = new TexturedEyeLightShader(&scene, Vec3f(0.5f, 0.5f, 0.5f), "barney.ppm");
				scene.ParseOBJ("barney.obj", texTriangleFactory);
				scene.camera = new PerspectiveCamera(Vec3f(0,8,25),Vec3f(0,0,-1), Vec3f(0,1,0),60,640,480);
			}
			break;
		case 5:
			{
				Scene::TexturedSmoothTriangleFactory *texTriangleFactory = new Scene::TexturedSmoothTriangleFactory;
				texTriangleFactory->shader = new TexturedEyeLightShader(&scene, Vec3f(9.f, 9.f, 9.f), "cb.ppm");
				scene.ParseOBJ("ground.obj", texTriangleFactory);
				scene.camera = new PerspectiveCamera(Vec3f(0,1.6f,25),Vec3f(0,0,-1), Vec3f(0,1,0),60,640,480);
			}
			break;
		default:
			{		
				std::cerr << "Usage: " << argv[0] << "<ex-number> [<sampler> <sample-count>]" << std::endl;
				std::cerr << "  <ex-number> = " << std::endl;
				std::cerr << "  1 - for ex.1 with cylinder" << std::endl;
				std::cerr << "  2 - for ex.1 with cone" << std::endl;
				std::cerr << "  3 - for ex.2" << std::endl;
				std::cerr << "  4 - for ex.3" << std::endl;
				std::cerr << "  5 - for ex.4" << std::endl;
				std::cerr << "<sampler> = " << std::endl;
				std::cerr << "	" << std::endl;
				std::cerr << "<sampler> = " << std::endl;
				std::cerr << "  regular (default)" << std::endl;
				std::cerr << "  random" << std::endl;
				std::cerr << "  stratified" << std::endl;
				exit(1);
			}
	}

	scene.BuildAccelStructure();
	RenderFrame(scene, sampleCount, sampler);
}
