#include "raytracer.h"
#include "octree.h"

#include "image.h"
#include "common_sdl.h"

Camera cam;
Light light(Vector3(-2.0, 10.0, -2.0), Colour(0.9, 0.9, 0.9));

Float lx, ly, lz, cf;


/*class CloudTexture: public Texture
{
	Vector3 centre;
public:
	Colour evaluate(Vector3 point)
	{
		Float sum = 0.0;
		for (int i = 1; i < 5; i++)
			sum += fabsf(perlin(point.x*i, point.y*i, point.z*i))/i;
		Float value = sinf(point.x + sum)/2 + 0.5;
		return Colour(value, value*0.5, value*0.5);
	};
};*/

void texture2D(Float u, Float v, Float &r, Float &g, Float &b)
{
	// checkers
	r = fabs((int)(u*4.2)%2 + (int)(v*4.2)%2 - 1);
	g=r; b=r;
}

class PlanarMapTexture: public Texture
{
	Vector3 centre;
public:
	PlanarMapTexture(Vector3 acentre): centre(acentre) {};
	Colour evaluate(Vector3 point)
	{
		point = point - centre;
		Float u = 1000+point.x;
		Float v = 1000+point.y;
		Float r,g,b;
		texture2D(u,v, r,g,b);
		return Colour(r, g, b);
	};
};

class CubicMapTexture: public Texture
{
	Vector3 centre;
public:
	CubicMapTexture(Vector3 acentre): centre(acentre) {};
	Colour evaluate(Vector3 point)
	{
		point = point - centre;
		Float u,v;
		if (fabs(point.x) > fabs(point.y))
		{
			if (fabs(point.x) > fabs(point.z))
			{
				if (point.x < 0)
					u = -point.y;
				else
					u = point.y;
				v = point.z;
			}
			else
			{
				if (point.z < 0)
					u = -point.x;
				else
					u = point.x;
				v = point.y;
			}
		}
		else
		{
			if (fabs(point.y) > fabs(point.z))
			{
				if (point.y < 0)
					u = -point.x;
				else
					u = point.x;
				v = point.z;
			}
			else
			{
				if (point.z < 0)
					u = -point.x;
				else
					u = point.x;
				v = point.y;
			}
		}
		u += 1000;
		v += 1000;
		Float r,g,b;
		texture2D(u,v, r,g,b);
		return Colour(r, g, b);
	};
};

class CylinderMapTexture: public Texture
{
	Vector3 centre;
public:
	CylinderMapTexture(Vector3 acentre): centre(acentre) {};
	Colour evaluate(Vector3 point)
	{
		point = point - centre;

		Float u,v;
		v = 1000+point.y;
		u = M_PI + atan2(point.z, point.x);

		Float r,g,b;
		texture2D(u,v, r,g,b);
		return Colour(r, g, b);
	};
};

class SphereMapTexture: public Texture
{
	Vector3 centre;
public:
	SphereMapTexture(Vector3 acentre): centre(acentre) {};
	Colour evaluate(Vector3 point)
	{
		point = point - centre;

		Float u,v;
		v = acos(point.y / point.mag());
		u = M_PI + atan2(point.z, point.x);

		Float r,g,b;
		texture2D(u,v, r,g,b);
		return Colour(r, g, b);
	};
};

void update_callback()
{
	if (lx != 0.0)
		light.pos.x += lx;
	if (ly != 0.0)
		light.pos.y += ly;
	if (lz != 0.0)
		light.pos.z += lz;
	if (cf != 0.0)
		cam.f += cf;
}

void key_callback(int key, int down)
{
	switch (key)
	{
		case SDLK_r:
			lx = -0.1 * down;
			break;
		case SDLK_t:
			lx = +0.1 * down;
			break;
		case SDLK_f:
			ly = -0.1 * down;
			break;
		case SDLK_g:
			ly = +0.1 * down;
			break;
		case SDLK_v:
			lz = -0.1 * down;
			break;
		case SDLK_b:
			lz = +0.1 * down;
			break;

		case SDLK_z:
			cf = -0.02 * down;
			break;
		case SDLK_x:
			cf = +0.02 * down;
			break;
	}
}

int main(int argc, char **argv)
{
	Raytracer rt;

	Octree top;
	rt.setTop(&top);

	rt.addlight(&light);
	light.castShadows(false);

	Material mat0a(Colour(0.7, 0.7, 0.7));
	mat0a. setReflectivity(0.0);
	Box box(Vector3(-12.0, -1.2, -20.0), Vector3(12.0, -1.0, 0.0), &mat0a);
	rt.addshape(&box);

	Material mat0b(Colour(0.1, 0.7, 0.8));
	mat0b.setReflectivity(0.7);
	Box box2(Vector3(-12.0, -1.2, -10.0), Vector3(12.0, 10.0, -10.2), &mat0b);
	rt.addshape(&box2);

	// spheres
	Material mat1(Colour(1.0, 1.0, 1.0));
	mat1.texture = new PlanarMapTexture(Vector3(4.0, 2.0, -7.0));
	Sphere sphere1(Vector3(4.0, 2.0, -7.0), 1.0, &mat1);
	rt.addshape(&sphere1);

	Material mat2(Colour(1.0, 1.0, 1.0));
	mat2.texture = new CubicMapTexture(Vector3(1.0, 2.0, -7.0));
	Sphere sphere2(Vector3(1.0, 2.0, -7.0), 1.0, &mat2);
	rt.addshape(&sphere2);

	Material mat3(Colour(1.0, 1.0, 1.0));
	mat3.texture = new CylinderMapTexture(Vector3(-2.0, 2.0, -7.0));
	Sphere sphere3(Vector3(-2.0, 2.0, -7.0), 1.0, &mat3);
	rt.addshape(&sphere3);

	Material mat4(Colour(1.0, 1.0, 1.0));
	mat4.texture = new SphereMapTexture(Vector3(-5.0, 2.0, -7.0));
	Sphere sphere4(Vector3(-5.0, 2.0, -7.0), 1.0, &mat4);
	rt.addshape(&sphere4);

	// cubes
	Material mat5(Colour(1.0, 1.0, 1.0));
	mat5.texture = new PlanarMapTexture(Vector3(4.0, 0.0, -7.0));
	Box cube1(Vector3(4.0, 0.0, -7.0)-1.0, Vector3(4.0, 0.0, -7.0)+1.0, &mat5);
	rt.addshape(&cube1);

	Material mat6(Colour(1.0, 1.0, 1.0));
	mat6.texture = new CubicMapTexture(Vector3(1.0, 0.0, -7.0));
	Box cube2(Vector3(1.0, 0.0, -7.0)-1.0, Vector3(1.0, 0.0, -7.0)+1.0, &mat6);
	rt.addshape(&cube2);

	Material mat7(Colour(1.0, 1.0, 1.0));
	mat7.texture = new CylinderMapTexture(Vector3(-2.0, 0.0, -7.0));
	Box cube3(Vector3(-2.0, 0.0, -7.0)-1.0, Vector3(-2.0, 0.0, -7.0)+1.0, &mat7);
	rt.addshape(&cube3);

	Material mat8(Colour(1.0, 1.0, 1.0));
	mat8.texture = new SphereMapTexture(Vector3(-5.0, 0.0, -7.0));
	Box cube4(Vector3(-5.0, 0.0, -7.0)-1.0, Vector3(-5.0, 0.0, -7.0)+1.0, &mat8);
	rt.addshape(&cube4);

	mat1.setReflectivity(0);
	mat2.setReflectivity(0);
	mat3.setReflectivity(0);
	mat4.setReflectivity(0);
	mat5.setReflectivity(0);
	mat6.setReflectivity(0);
	mat7.setReflectivity(0);
	mat8.setReflectivity(0);

	top.optimize();

	cam.setEye(Vector3(-0.530505, 11.0964, 11.2208));
	cam.p = Vector3(-4.18144e-08, -0.461779, -0.886995);
	cam.u = Vector3(-1, 0, 6.3393e-11);
	cam.v = Vector3(3.19387e-08, 0.886995, -0.461779);
	rt.setCamera(&cam);

	w = 1024;
	h = 600;

	/* run interactive mode */
	loop_sdl(rt, cam, update_callback, key_callback);

	/* render image */
	if (argc == 2 && !strcmp(argv[1], "-r"))
	{
		pyrit_verbosity = 2;
		Float *fdata = (Float *) malloc(w*h*3*sizeof(Float));
		rt.ambientocclusion(300, 5.0, 0.5);
		DefaultSampler sampler(fdata, w, h);
		sampler.setOversample(2);
		sampler.setSubsample(1);
		rt.setSampler(&sampler);
		rt.render();

		struct image *img;
		new_image(&img, w, h, 3);

		Float *fd = fdata;
		for (unsigned char *cd = img->data; cd != img->data + w*h*3; cd++, fd++) {
			if (*fd > 1.0)
				*cd = 255;
			else
				*cd = (unsigned char)(*fd * 255.0);
		}
		free(fdata);
		save_png("textures.png", img);
		destroy_image(&img);
	}
}
