#include "includes.h"
#include "file.h"

#define MAX_WIDTH 8192
#define MAX_HEIGHT 8192
#define MAX_DOUBLE 1.7e308

struct SceneElement {
	string name;
	bool is_default;
};

struct Camera:SceneElement {
	double position[3];
	double center[3];
	double up[3];
	double aperture;
	double image_plane;
};
struct Image:SceneElement {
	int width;
	int height;
	int samples;
};

struct Material:SceneElement {
};

struct Object:SceneElement {
	double position[3];
	Material material;
	string name;
	char type;
	/*Object() {}
	Object(Object o) {
		for (int i=0;i<3;++i) position[i] = o.position[i];
		material = o.material;
		name = o.name;
		type = o.type;
	}*/
};
struct Sphere:Object {
	double radius;
	Sphere() { type = 1; }
	Sphere(Object o) : Object(o) { type = 1; }
	Sphere(Object* o) : Object(*o) { type = 1; }
};
struct Point:Object {
	Point() { type = 2; }
	Point(Object o) : Object(o) { type = 2; }
	Point(Object* o) : Object(*o) { type = 2; }
};

class Scene {
	public:
		Camera camera;
		Image image;
		map<string,Material> materials;
		list<Object*> objects;

		bool valid;
		string error;
	public:
		Scene(const char* file) {
			valid = true;

			init_defaults();

			FileReader* file_obj;
			try {
				file_obj = new FileReader(file);
			}
			catch (runtime_error e) {
				err("File","Could not open file \""+string(file)+"\"!",-1);
				return;
			}

			list<string> data_type_stack;

			list<string>::const_iterator iter;
			list<string> lines = file_obj->get_lines();
			int line_num = 1;
			for ( iter=lines.begin(); iter!=lines.end(); ++iter,++line_num ) {
				SuperString line = iter->c_str();
				//printf("Parsing line %3d: \"%s\"\n",line_num,line.c_str());

				line.trim();
				if (line.starts_with("//")) continue;

				if (line.starts_with("image")&&!line.starts_with("image_plane")) {
					if (data_type_stack.size()!=0) { err("Syntax","\"image\" is an invalid token in this scope.",line_num); break; }
					data_type_stack.push_front("image");
					if (!line.ends_with("{")) { err_require_bracket(line_num,1); break; }
					image.is_default = false;
				}
				else if (line.starts_with("camera")) {
					if (data_type_stack.size()!=0) { err("Syntax","\"camera\" is an invalid token in this scope.",line_num); break; }
					data_type_stack.push_front("camera");
					if (!line.ends_with("{")) { err_require_bracket(line_num,1); break; }
					camera.is_default = false;
				}
				else if (line.starts_with("material")) {
					if (data_type_stack.size()!=0) { err("Syntax","\"material\" is an invalid token in this scope.",line_num); break; }
					data_type_stack.push_front("material");
					if (!line.ends_with("{")) { err_require_bracket(line_num,1); break; }
				}
				else if (line.starts_with("light")) {
					if (data_type_stack.size()!=0) { err("Syntax","\"light\" is an invalid token in this scope.",line_num); break; }
					data_type_stack.push_front("light");
					if (!line.ends_with("{")) { err_require_bracket(line_num,1); break; }
				}
				else if (line.starts_with("scene")) {
					if (data_type_stack.size()!=0) { err("Syntax","\"scene\" is an invalid token in this scope.",line_num); break; }
					data_type_stack.push_front("scene");
					if (!line.ends_with("{")) { err_require_bracket(line_num,1); break; }
				}
				else if (line.starts_with("object")) {
					if (data_type_stack.size()!=1) { err("Syntax","\"scene\" is an invalid token in this scope.",line_num); break; }
					string parent = data_type_stack.front();
					if (parent=="scene"||parent=="light"); //TODO: light objects (light primitives)
					else { err("Syntax","\"object\" must be found inside a \"scene\" or a \"light\" scope.",line_num); break; }
					data_type_stack.push_front("object");
					objects.push_front(new Object());
				}
				else if (line.starts_with("sphere")) {
					if (data_type_stack.size()!=2) { err("Syntax","\"sphere\" is an invalid token in this scope.",line_num); break; }
					string parent = data_type_stack.front();
					if (parent=="object");
					else { err("Syntax","\"sphere\" must be found inside a \"object\" scope.",line_num); break; }
					data_type_stack.push_front("sphere");
				}
				else if (line.starts_with("point")) {
					if (data_type_stack.size()!=2) { err("Syntax","\"point\" is an invalid token in this scope.",line_num); break; }
					string parent = data_type_stack.front();
					if (parent=="object");
					else { err("Syntax","\"point\" must be found inside a \"object\" scope.",line_num); break; }
					data_type_stack.push_front("point");
				}
				else if (line.str()=="}") {
					if (data_type_stack.size()==0) { err("Syntax","closing bracket not expected!",line_num); break; }
					string parent = data_type_stack.front();
					if (parent=="sphere"||parent=="point") {
						Object* obj = objects.front();
						if (parent=="sphere") {
							Sphere* s = new Sphere(obj);
							objects.push_front(s);
						}
						else if (parent=="point") {
							Point* s = new Point(obj);
							objects.push_front(s);
						}
						objects.pop_front();
					}
					data_type_stack.pop_front();
				}
				else {
					list<string> l = line.split(" ");

					if (data_type_stack.size()==0) {
						if (line.size()!=0) {
							err("Syntax","did not expect tokens on this line!",line_num);
							break;
						}
						continue;
					}
					string parent = data_type_stack.front();
					if (parent=="image") {
						if (line.starts_with("width")) {
							if (l.size()!=2) { err("Syntax","invalid \"width\" declaration!",line_num); break; }
							if (!set_num_check_bounds(l.back(),"width",0,MAX_WIDTH,line_num,&image.width)) break;
						}
						else if (line.starts_with("height")) {
							if (l.size()!=2) { err("Syntax","invalid \"height\" declaration!",line_num); break; }
							if (!set_num_check_bounds(l.back(),"height",0,MAX_HEIGHT,line_num,&image.height)) break;
						}
						else if (line.starts_with("samples")) {
							if (l.size()!=2) { err("Syntax","invalid \"samples\" declaration!",line_num); break; }
							if (!set_num(l.back(),line_num,&image.samples)) break;
						}
						else { err("Syntax","unrecognized token!",line_num); break; }
					}
					else if (parent=="camera") {
						if (line.starts_with("position")) {
							if (l.size()!=4) { err("Syntax","invalid \"position\" declaration!",line_num); break; }
							if (!set_num(l.back(),line_num,&camera.position[2])) break;
							l.pop_back();
							if (!set_num(l.back(),line_num,&camera.position[1])) break;
							l.pop_back();
							if (!set_num(l.back(),line_num,&camera.position[0])) break;
						}
						else if (line.starts_with("center")) {
							if (l.size()!=4) { err("Syntax","invalid \"center\" declaration!",line_num); break; }
							if (!set_num(l.back(),line_num,&camera.center[2])) break;
							l.pop_back();
							if (!set_num(l.back(),line_num,&camera.center[1])) break;
							l.pop_back();
							if (!set_num(l.back(),line_num,&camera.center[0])) break;
						}
						else if (line.starts_with("up")) {
							if (l.size()!=4) { err("Syntax","invalid \"up\" declaration!",line_num); break; }
							if (!set_num(l.back(),line_num,&camera.up[2])) break;
							l.pop_back();
							if (!set_num(l.back(),line_num,&camera.up[1])) break;
							l.pop_back();
							if (!set_num(l.back(),line_num,&camera.up[0])) break;
						}
						else if (line.starts_with("image_plane")) {
							if (l.size()!=2) { err("Syntax","invalid \"image_plane\" declaration!",line_num); break; }
							if (!set_num(l.back(),line_num,&camera.image_plane)) break;
						}
						else { err("Syntax","unrecognized token!",line_num); break; }
					} else if (parent=="material") {
					} else if (parent=="light") {
					} else if (parent=="scene") {
					} else if (parent=="object") {
						if (line.starts_with("position")) {
							if (l.size()!=4) { err("Syntax","invalid \"position\" declaration!",line_num); break; }
							if (!set_num(l.back(),line_num,&objects.front()->position[2])) break;
							l.pop_back();
							if (!set_num(l.back(),line_num,&objects.front()->position[1])) break;
							l.pop_back();
							if (!set_num(l.back(),line_num,&objects.front()->position[0])) break;
						} else { err("Syntax","unrecognized token!",line_num); break; }
					} else if (parent=="sphere") {
						if (line.starts_with("radius")) {
							if (l.size()!=2) { err("Syntax","invalid \"radius\" declaration!",line_num); break; }
							if (!set_num_check_bounds(l.back(),"radius",0.0,MAX_DOUBLE,line_num,&((Sphere*)(objects.front()))->radius)) break;
						} else { err("Syntax","unrecognized token!",line_num); break; }
					} else if (parent=="point") {
						err("Syntax","unrecognized token!",line_num); break;
					} else { throw runtime_error("compiler error!"); }
				}
			}

			delete file_obj;
		}
		~Scene() {
			/*list<Object*>::iterator iter;
			for (iter=objects.begin();iter!=objects.end();++iter) {
				delete (*iter);
			}*/
			while (objects.size()>0) {
				objects.pop_front();
			}
		}
	private:
		template <typename type_num> bool set_num(string str, int line_num, type_num* to_set) {
			type_num num;
			if (!SuperString::to_number(str,&num)) {
				err("Syntax","invalid number \""+str+"\"!",line_num);
				return false;
			}
			*to_set = num;
			return true;
		}
		template <typename type_num> bool set_num_check_bounds(string str, string type, type_num lower, type_num upper, int line_num, type_num* to_set) {
			type_num num;
			if (!SuperString::to_number(str,&num)) {
				err("Syntax","invalid number \""+str+"\"!",line_num);
				return false;
			}
			if (num>lower&&num<=upper) *to_set = num;
			else {
				err("Syntax","invalid \""+type+"\" declaration!",line_num);
				return false;
			}
			return true;
		}
		void err_require_bracket(int line_num, char open_or_close) {
			err("Syntax","expected a bracket \""+string((open_or_close==1)?"{":"}")+"\"!",line_num);
		}
		void err(string type, string msg, int line_num) {
			valid = false;
			error = type + " error";
			if (line_num!=-1) error += " (line "+SuperString::to_string(line_num)+")";
			error += ": " + msg;
		}

		void init_defaults(void) {
			camera.position[0] = 0;
			camera.position[1] = 0;
			camera.position[2] = 0;
			camera.center[0] =  0;
			camera.center[1] =  0;
			camera.center[2] = -1;
			camera.up[0] = 0;
			camera.up[1] = 1;
			camera.up[2] = 0;
			camera.aperture = 0.0;
			camera.name = "Camera";
			camera.is_default = true;

			image.width = 400;
			image.height = 100;
			image.samples = 1;
			image.name = "Ray Traced Image";
			image.is_default = true;
		}
};