#pragma once
#include "../includes.h"
#include "../config.h"
//#include "parse/parser.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

#define PARSE_FRIEND_DECLARATION list<string*>* scene_parse(SceneParser*,\
		Camera*,\
		Image*,\
		map<string,Material*>*, list<Object*>*, list<glLib::Object::object*>*, map<string,Light*>*,\
		DT_COL*);

class SceneElement {
	public:
		string* name;
	public:
		SceneElement(string str);
		~SceneElement();
};

class Camera : public SceneElement {
	public:
		DT_FP position[3];
		DT_FP center[3];
		DT_FP up[3];

		DT_FP aperture;
		int aperture_samples;

		DT_FP image_plane;
		DT_FP recip_focal_length;
	public:
		Camera();
};

class Image : public SceneElement {
	public:
		int width;
		int height;

		int samples;
	public:
		Image();
};

class Light;

class Material : public SceneElement {
	public:
		DT_COL ambient[3];
		DT_COL diffuse[3];
		DT_COL specular[3];
		DT_COL emission[3];

		DT_COL absorption[3];

		DT_FP refractive_index;
		DT_FP exponent_refractive;

		DT_FP shininess;

		DT_FP film_thickness;
		DT_FP film_refractive_index;

		SDL_Surface* texture;

		map<Light*,int>* to_shadow;

		bool reflect, refract, film;

		int samples_reflection, samples_refraction;

		DT_FP pow_table[NUM_POW_TABLE];

		void init(void);
	public:
		Material(string name);
		~Material();
};

class SceneParser;
class Object : public SceneElement {
	friend PARSE_FRIEND_DECLARATION
	public:
		DT_FP position[3];
		Material* material;
		char type;
	private:
		bool _has_default_material;
	public:
		virtual void set_normal(DT_FP hit_position[3], DT_FP n[3]) = 0;
		virtual void init(void) = 0;

		void set_material(Material* mat) {
			if (_has_default_material) {
				delete material;
				_has_default_material = false;
			}
			material = mat;
		}

		Object(char type, string name);
		~Object();
};
class Point : public Object {
	public:
		virtual inline void set_normal(DT_FP hit_position[3], DT_FP n[3]) {}

		void init(void) {}
		Point();
};
class Sphere : public Object {
	public:
		DT_FP radius;
	public:
		virtual inline void set_normal(DT_FP hit_position[3], DT_FP n[3]) {
			glLib::Math::Vec::subtract3(hit_position,position,n);
			glLib::Math::Vec::normalize3(n);
		}

		void init(void) {}
		Sphere();
};
class Plane : public Object {
	public:
		DT_FP normal[3];
	public:
		virtual inline void set_normal(DT_FP hit_position[3], DT_FP n[3]) {
			glLib::Math::Vec::copy3(normal,n);
		}

		void init(void) {}
		Plane();
};
class Triangle : public Object {
	friend PARSE_FRIEND_DECLARATION
	public:
		DT_FP position2[3], position3[3];
		DT_FP normal1[3], normal2[3], normal3[3];
		DT_FP uv1[2], uv2[2], uv3[2];

		DT_FP _bary_vec0[3],_bary_vec1[3],_bary_vec2[3];
		DT_FP _bary_dot00, _bary_dot01, _bary_dot02, _bary_dot11, _bary_dot12;
		DT_FP _bary_value_vec0[3], _bary_value_vec1[3];
	private:
		bool _must_calculate_normal;

	private:
		void _bary_init(void);
	public:
		void barycentric(DT_FP hit_position[3], DT_FP quan1[], DT_FP quan2[], DT_FP quan3[], DT_FP result[], int length);

		inline void set_uv(DT_FP hit_position[3], DT_FP uv[2]) {
			barycentric(hit_position,uv1,uv2,uv3,uv,2);
		}
		virtual inline void set_normal(DT_FP hit_position[3], DT_FP n[3]) {
			barycentric(hit_position,normal1,normal2,normal3,n,3);
			glLib::Math::Vec::normalize3(n);
		}

		void init(void);

		Triangle();
};

class Light : public SceneElement {
	public:
		Object* primitive;
		DT_COL color[3];
	private:
		bool _is_default;
		static DT_FP _random_sphere_points[NUM_RANDOM_SAMPLES*3];

	public:
		static void init(void);

		void get_random_point(DT_FP point[3]);

		Light(string name);
		~Light();
};