#pragma once
#include "includes.h"

#define SCENE_OBJECT_TYPE_POINT 1
#define SCENE_OBJECT_TYPE_SPHERE 2
#define SCENE_OBJECT_TYPE_PLANE 3
#define SCENE_OBJECT_TYPE_TRIANGLE 4

struct SceneElement {
	string* name;
	bool is_default;

	SceneElement(string str) {
		name = new string(str);
		is_default = true;
	}
	~SceneElement() {
		delete name;
	}
};

struct Camera:SceneElement {
	double position[3];
	double center[3];
	double up[3];
	double aperture;
	double image_plane;

	Camera(double position[3], double center[3], double up[3], double aperture, double image_plane, string name="") : SceneElement(name) {
		for (char i=0;i<3;++i) {
			this->position[i] = position[i];
			this->center[i] = center[i];
			this->up[i] = up[i];
		}
		this->aperture = aperture;
		this->image_plane = image_plane;
	}
	Camera() : SceneElement("Camera 1") {
		position[0]=0.0; position[1]=0.0; position[2]=0.0;
		center[0]=0.0; center[1]=0.0; center[2]=-1.0;
		up[0]=0.0; up[1]=1.0; up[2]=0.0;
		aperture = 0.0;
		image_plane = 1.0;
	}
};

struct Image:SceneElement {
	int width;
	int height;
	int samples;

	Image(int width, int height, int samples, string name="") : SceneElement(name) {
		this->width = width;
		this->height = height;
		this->samples = samples;
	}
	Image() : SceneElement("Ray Traced Image 1") {
		width = 400;
		height = 100;
		samples = 1;
	}
};

struct Light;

struct Material:SceneElement {
	double ambient[3];
	double diffuse[3];
	double specular[3];
	//double emissision[3];
	//double transmission;
	//double refractive_index;
	double shininess;
	//texture
	map<Light*,int>* to_shadow;
	bool reflect, refract;
	double coefficient_reflection, coefficient_refraction;
	int samples_reflection, samples_refraction;

	/*Material(double ambient[3], double diffuse[3], double specular[3], double shininess, string name) : SceneElement(name) {
		for (int i=0;i<3;++i) {
			this->ambient[i] = ambient[i];
			this->diffuse[i] = diffuse[i];
			this->specular[i] = specular[i];
		}
		this->shininess = shininess;
		to_shadow = new map<Light*,int>();
		reflect = refract = false;
	}*/
	Material(string name) : SceneElement(name) {
		for (int i=0;i<3;++i) {
			ambient[i] = diffuse[i] = specular[i] = 0.0;
		}
		shininess = 0.0;
		to_shadow = new map<Light*,int>();
		reflect = refract = false;
		samples_reflection = samples_refraction = 0;
	}
	~Material() {
		delete to_shadow;
	}
};

struct Object:SceneElement {
	friend string scene_parse(const char*,Camera*,Image*,map<string,Material*>*, list<Object*>*, map<string,Light*>*, double*);
	public:
		double position[3];
		Material* material;
		char type;
	private:
		bool _must_delete_material;

	public:
		Object(double position[3], Material* material, char type, string name="") : SceneElement(name) {
			for (int i=0;i<3;++i) this->position[i] = position[i];
			this->material = material;
			this->type = type;
			_must_delete_material = true;
		}
		Object(char type, string name) : SceneElement(name) {
			position[0]=0.0; position[1]=0.0; position[2]=0.0;
			material = new Material("Default Material");
			this->type = type;
			_must_delete_material = true;
		}
		~Object() {
			if (_must_delete_material) delete material;
		}
};
struct Point:Object {
	Point(double position[3], Material* material, string name="") : Object(position,material,SCENE_OBJECT_TYPE_POINT,name) {}
	Point() : Object(SCENE_OBJECT_TYPE_POINT,"Point") {}
};
struct Sphere:Object {
	double radius;
	Sphere(double position[3], Material* material, double radius, string name="") : Object(position,material,SCENE_OBJECT_TYPE_SPHERE,name) {
		this->radius = radius;
	}
	Sphere() : Object(SCENE_OBJECT_TYPE_SPHERE,"Sphere") {
		radius = 1.0;
	}
};
struct Plane:Object {
	double normal[3];
	Plane(double position[3], double normal[3], Material* material, string name="") : Object(position,material,SCENE_OBJECT_TYPE_PLANE,name) {
		for (int i=0;i<3;++i) this->normal[i] = normal[i];
	}
	Plane() : Object(SCENE_OBJECT_TYPE_PLANE,"Plane") {
		normal[0] = 0.0;
		normal[1] = 1.0;
		normal[2] = 0.0;
	}
};
struct Triangle:Object {
	double position2[3], position3[3];
	Triangle(double position[3], double position2[3], double position3[3], Material* material, double radius, string name="") : Object(position,material,SCENE_OBJECT_TYPE_TRIANGLE,name) {
		for (int i=0;i<3;++i) {
			this->position2[i] = position2[i];
			this->position3[i] = position3[i];
		}
	}
	Triangle() : Object(SCENE_OBJECT_TYPE_TRIANGLE,"Triangle") {
		position2[0] = 1.0;
		position2[1] = 0.0;
		position2[2] = 0.0;

		position3[0] = 0.0;
		position3[1] = 0.0;
		position3[2] = 1.0;
	}
};

struct Light : SceneElement {
	public:
		Object* primitive;
		double color[3];
	private:
		bool _is_default;

	public:
		Light(Object* primitive, double color[3], string name) : SceneElement(name) {
			this->primitive = primitive;
			for (int i=0;i<3;++i) this->color[i] = color[i];
			_is_default = false;
		}
		Light(string name) : SceneElement(name) {
			primitive = new Point();
			for (int i=0;i<3;++i) this->color[i] = 1.0;
			_is_default = true;
		}
		~Light() {
			delete primitive;
		}
		void get_random_point(double point[3]) {
			switch (primitive->type) {
				case    SCENE_OBJECT_TYPE_POINT: {
					glLibMathVec::copy(primitive->position,point,3);
					break;
				}
				case   SCENE_OBJECT_TYPE_SPHERE: {
					//http://www.cgafaq.info/wiki/Uniform_random_points_on_sphere
					point[2] = glLibMathRandom::get_uniform(-1.0,1.0);
					double t = glLibMathRandom::get_uniform(0.0,2.0*pi);
					double r = pow(1.0-point[2]*point[2],0.5);
					point[0] = r*cos(t);
					point[1] = r*sin(t);
					glLibMathVec::scale(((Sphere*)(primitive))->radius,point,3);
					glLibMathVec::add(primitive->position,point,point,3);
					break;
				}
				case    SCENE_OBJECT_TYPE_PLANE: {
					throw glLibError("Not implemented!");
					break;
				}
				case SCENE_OBJECT_TYPE_TRIANGLE: {
					throw glLibError("Not implemented!");
					break;
				}
				default: {
					throw glLibError("Not implemented!");
					break;
				}
			}
		}
};