#include "includes.h"
#include "parser.h"
#include "scene_elements.h"

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

string err(string type, string msg, int line_num) {
	string error = type + " error";
	if (line_num!=-1) error += " (line "+to_string(line_num)+")";
	error += ": " + msg;
	return error;
}

struct Block {
	string *type, *name;
	int line_start;
	Block(string type, string name, int line_start) {
		this->type = new string(type);
		this->name = new string(name);
		this->line_start = line_start;
	}
	~Block() {
		delete type;
		delete name;
	}
};

void remove_block(vector<Block*>* stack) {
	delete stack->back();
	stack->pop_back();
}

string scene_parse(const char* path,
	Camera* camera,
	Image* image,
	map<string,Material*>* materials, list<Object*>* objects, map<string,Light*>* lights,
	double background_color[3])
{
	string error;

	Parser parser(path);

	parser.remove_comment_lines();
	/*if (parser.remove_comment_blocks()) printf("Success!\n");
	else printf("Comment wasn't closed!");*/

	list<string> separable_tokens;
	separable_tokens.push_back("{");
	separable_tokens.push_back("}");
	separable_tokens.push_back(":");
	parser.split(separable_tokens);

	parser.combine_around(":");

	Token *token = NULL;
	string token_str = "";
	unsigned long token1line = 0;

	vector<Block*> stack;
	string next_block_type = "";
	string next_block_name = "";
	string attribute_str = "";
	char attribute_type; //-1=choose, 0=int, 1=double
	int attribute_index = 0;
	int attribute_size = 0;
	while (parser.has_next()) {
		token = parser.next();
		token_str = token->get_value();
		token1line = token->get_line_num();

		/*printf("Stack: \"");
		string s1="", s2="";
		for (vector<Block*>::const_iterator iter=stack.begin();iter!=stack.end();++iter) s1 += *((*iter)->type) + "/";
		int l = s1.length()-2; for (int i=0;i<l;++i) s2 += s1[i];
		printf("%s\", parsing token \"%s\".\n",s1.c_str(),token_str.c_str());*/

		//Context
		if (token_str=="material"||token_str=="light"||token_str=="image"||token_str=="camera"||
			token_str=="scene"||
			token_str=="object:sphere"||token_str=="object:point"||token_str=="object:plane"||token_str=="object:triangle") {
			//Token is a context
			if (attribute_str!="") goto ERROR_EXPECTED_ATTRIBUTE_VALUE;
			if (next_block_type!="") goto ERROR_EXPECTED_QUOTEBRACKET;
			if (token_str=="object:sphere"||token_str=="object:point"||token_str=="object:plane"||token_str=="object:triangle") {
				if (stack.size()!=1||(*stack.back()->type!="light"&&*stack.back()->type!="scene")) goto ERROR_CONTEXT_TYPE;
			}
			next_block_type = token_str;
		}
		//Attribute
		else if (token_str=="width"||token_str=="height"||token_str=="samples"||token_str=="image_plane"||token_str=="radius"||
			token_str=="use"||token_str=="shininess"||token_str=="position"||token_str=="position2"||token_str=="position3"||
			token_str=="center"||token_str=="up"||
			token_str=="normal"||token_str=="ambient"||token_str=="diffuse"||token_str=="specular"||token_str=="background"||
			token_str=="color"||token_str=="action:shadow"||token_str=="action:reflect"||token_str=="action:refract"
		) {
			//Token is an attribute
			if (attribute_str!="") goto ERROR_EXPECTED_ATTRIBUTE_VALUE;
			attribute_str = token_str;
			attribute_index = 0;

			if      (token_str=="width") { attribute_size=1; if (stack.size()!=1||*stack.back()->type!="image") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="height") { attribute_size=1; if (stack.size()!=1||*stack.back()->type!="image") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="samples") { attribute_size=1; if (stack.size()!=1||*stack.back()->type!="image") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="image_plane") { attribute_size=1; if (stack.size()!=1||*stack.back()->type!="camera") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="radius") { attribute_size=1; if (stack.size()!=2||*stack.back()->type!="object:sphere") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="use") {
				attribute_size = 1;
				if (stack.size()!=2||
						(*stack.back()->type!="object:sphere"&&
						 *stack.back()->type!="object:point"&&
						 *stack.back()->type!="object:plane"&&
						 *stack.back()->type!="object:triangle")) goto ERROR_CONTEXT_ATTRIBUTE;
			}
			else if (token_str=="shininess") { attribute_size=1; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }

			else if (token_str=="action:shadow") { attribute_size=2; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="action:reflect") { attribute_size=2; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="action:refract") { attribute_size=2; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }

			else if (token_str=="position") {
				attribute_size = 3;
				if (stack.size()==1) {
					if (*stack.back()->type!="camera") goto ERROR_CONTEXT_ATTRIBUTE;
				}
				else if (stack.size()==2) {
					if (*stack.back()->type!="object:sphere"&&
						*stack.back()->type!="object:point"&&
						*stack.back()->type!="object:plane"&&
						*stack.back()->type!="object:triangle") goto ERROR_CONTEXT_ATTRIBUTE;
				}
				else goto ERROR_CONTEXT_ATTRIBUTE;
			}
			else if (token_str=="position2") { attribute_size=3; if (*stack.back()->type!="object:triangle") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="position3") { attribute_size=3; if (*stack.back()->type!="object:triangle") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="center") { attribute_size=3; if (stack.size()!=1||*stack.back()->type!="camera") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="up") { attribute_size=3; if (stack.size()!=1||*stack.back()->type!="camera") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="normal") { attribute_size=3; if (*stack.back()->type!="object:plane") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="ambient") { attribute_size=3; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="diffuse") { attribute_size=3; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="specular") { attribute_size=3; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="color") { attribute_size=3; if (stack.size()!=1||*stack.back()->type!="light") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="background") { attribute_size=3; if (stack.size()!=1||*stack.back()->type!="scene") goto ERROR_CONTEXT_ATTRIBUTE; }
		}

		//Begin or end a context
		else if (token_str=="{") {
			if (attribute_str!="") goto ERROR_EXPECTED_ATTRIBUTE_VALUE;
			if (next_block_type=="") goto ERROR_EXPECTED_TOKEN;
			
			if      (next_block_type=="material") {
				if (next_block_name=="") goto ERROR_EXPECTED_NAME;
				(*materials)[next_block_name] = new Material(next_block_name);
			}
			else if (next_block_type=="light") {
				if (next_block_name=="") goto ERROR_EXPECTED_NAME;
				(*lights)[next_block_name] = new Light(next_block_name);
			}
			else if (next_block_type=="image") {} //TODO: more than one image?
			else if (next_block_type=="camera") {} //TODO: more than one camera?
			else if (next_block_type=="scene") {} //TODO: more than one scene?
			else if (next_block_type=="object:sphere") {
				if (*stack.back()->type=="light") {
					Light* current_light = (*lights)[*stack.back()->name];
					if (current_light->is_default) current_light->is_default = false;
					else goto ERROR_CONTEXT_MULTIPLE_ELEMENTS;
					delete current_light->primitive;
					current_light->primitive = new Sphere();
				}
				else objects->push_back(new Sphere());
			}
			else if (next_block_type=="object:point") {
				if (*stack.back()->type=="light") {
					Light* current_light = (*lights)[*stack.back()->name];
					if (current_light->is_default) current_light->is_default = false;
					else goto ERROR_CONTEXT_MULTIPLE_ELEMENTS;
					delete current_light->primitive;
					current_light->primitive = new Point();
				}
				else objects->push_back(new Point());
			}
			else if (next_block_type=="object:plane") {
				if (*stack.back()->type=="light") {
					Light* current_light = (*lights)[*stack.back()->name];
					if (current_light->is_default) current_light->is_default = false;
					else goto ERROR_CONTEXT_MULTIPLE_ELEMENTS;
					delete current_light->primitive;
					current_light->primitive = new Plane();
				}
				else objects->push_back(new Plane());
			}
			else if (next_block_type=="object:triangle") {
				if (*stack.back()->type=="light") {
					Light* current_light = (*lights)[*stack.back()->name];
					if (current_light->is_default) current_light->is_default = false;
					else goto ERROR_CONTEXT_MULTIPLE_ELEMENTS;
					delete current_light->primitive;
					current_light->primitive = new Triangle();
				}
				else objects->push_back(new Triangle());
			}

			stack.push_back(new Block(next_block_type,next_block_name,token1line));

			next_block_type = "";
			next_block_name = "";
		}
		else if (token_str=="}") {
			if (attribute_str!="") goto ERROR_EXPECTED_ATTRIBUTE_VALUE;
			if (stack.size()==0) goto ERROR_CONTEXT_POP_STACK;
			remove_block(&stack);
		}

		//Attribute value, name, or else an error
		else {
			double num_d; bool is_d = string_to_number(token_str,&num_d);
			int    num_i; bool is_i = string_to_number(token_str,&num_i);

			if (attribute_size==1) {
				if (is_d) {
					if      (attribute_str=="radius") {
						if (!(num_d>0)) goto ERROR_NUMERIC_BOUNDS;
						if (*stack.front()->type=="scene") ((Sphere*)( objects->back() ))->radius = num_d;
						else                               ((Sphere*)( (*lights)[*stack.front()->name]->primitive ))->radius = num_d;
					}
					else if (attribute_str=="image_plane") { camera->image_plane = num_d; }
					else if (attribute_str=="shininess") { (*materials)[*stack.back()->name]->shininess = num_d; }
					else if (!is_i) {
						if      (attribute_str=="width") goto ERROR_EXPECTED_ATTRIBUTE_INT;
						else if (attribute_str=="height") goto ERROR_EXPECTED_ATTRIBUTE_INT;
						else if (attribute_str=="samples") goto ERROR_EXPECTED_ATTRIBUTE_INT;
					}
				}
				if (is_i) {
					if      (attribute_str=="width") { if (!(num_i>0&&num_i<=MAX_WIDTH)) { goto ERROR_NUMERIC_BOUNDS; } image->width=num_i; }
					else if (attribute_str=="height") { if (!(num_i>0&&num_i<=MAX_HEIGHT)) { goto ERROR_NUMERIC_BOUNDS; } image->height=num_i; }
					else if (attribute_str=="samples") { if (!(num_i>0&&num_i<=MAX_SAMPLES)) { goto ERROR_NUMERIC_BOUNDS; } image->samples=num_i; }
				}
				if (!is_i&&!is_d) {
					if (attribute_str=="use") {
						if (materials->find(token_str)==materials->end()) goto ERROR_DEFINITION;
						Object* prim;
						if (*stack.back()->type=="light") prim = (*lights)[*stack.back()->name]->primitive;
						else                              prim = objects->back();
						delete prim->material;
						prim->material = (*materials)[token_str];
						prim->_must_delete_material = false; //it will be deleted by "objects"
					}
					else goto ERROR_UNRECOGNIZED_TOKEN; //NaN
				}

				attribute_str = "";
				attribute_size = 0;
			}
			else if (attribute_size==2) {
				Material* mat = (*materials)[*stack.back()->name]; //won't access violation because it was checked that it's in a material block, above
				if      (attribute_str== "action:shadow") {
					if (attribute_index==0) {
						attribute_index = 1;
						if (lights->find(token_str)==lights->end()) goto ERROR_DEFINITION;
						Light* light = (*lights)[token_str];
						if (mat->to_shadow->find(light)!=mat->to_shadow->end()) goto ERROR_MULTIPLE_REFERENCE;
						(*mat->to_shadow)[light] = -1;
					}
					else if (attribute_index==1) {
						attribute_index = 0;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						if (!is_i) goto ERROR_EXPECTED_ATTRIBUTE_INT;
						if (!(num_i>0)) goto ERROR_NUMERIC_BOUNDS;
						for (map<Light*,int>::iterator iter=mat->to_shadow->begin();iter!=mat->to_shadow->end();++iter) {
							if (iter->second==-1) {
								iter->second = num_i;
							}
						}
						attribute_str = "";
						attribute_size = 0;
					}
				}
				else if (attribute_str=="action:reflect") {
					Material* mat = (*materials)[*stack.back()->name]; //won't access violation because it was checked that it's in a material block, above
					if (attribute_index==0) {
						attribute_index = 1;
						mat->reflect = true;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						if (!(num_d>=0.0&&num_d<=1.0)) goto ERROR_NUMERIC_BOUNDS;
						mat->coefficient_reflection = num_d;
					}
					else if (attribute_index==1) {
						attribute_index = 0;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						if (!is_i) goto ERROR_EXPECTED_ATTRIBUTE_INT;
						if (!(num_i>0)) goto ERROR_NUMERIC_BOUNDS;
						mat->samples_reflection = num_i;

						attribute_str = "";
						attribute_size = 0;
					}
				}
				else if (attribute_str=="action:refract") {
					Material* mat = (*materials)[*stack.back()->name]; //won't access violation because it was checked that it's in a material block, above
					if (attribute_index==0) {
						attribute_index = 1;
						mat->refract = true;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						if (!(num_d>=0.0&&num_d<=1.0)) goto ERROR_NUMERIC_BOUNDS;
						mat->coefficient_reflection = num_d;
					}
					else if (attribute_index==1) {
						attribute_index = 0;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						if (!is_i) goto ERROR_EXPECTED_ATTRIBUTE_INT;
						if (!(num_i>0)) goto ERROR_NUMERIC_BOUNDS;
						mat->samples_refraction = num_i;

						attribute_str = "";
						attribute_size = 0;
					}
				}
				else goto ERROR_UNRECOGNIZED_TOKEN;
			}
			else if (attribute_size==3) {
				double* to_set;
				if      (attribute_str=="position") {
					if (attribute_index==0) attribute_type = 1;
					if      (*stack.back()->type=="camera") to_set = camera->position;
					else if (
						*stack.back()->type=="object:sphere"||
						*stack.back()->type=="object:point"||
						*stack.back()->type=="object:plane"||
						*stack.back()->type=="object:triangle")
					{
						if (*stack.front()->type=="scene") to_set = objects->back()->position;
						else                               to_set = (*lights)[*stack.front()->name]->primitive->position;
					}
				}
				else if (attribute_str=="position2") {
					if (attribute_index==0) attribute_type = 1;
					if (*stack.front()->type=="scene") to_set = ((Triangle*)( objects->back() ))->position2;
					else                               to_set = ((Triangle*)( (*lights)[*stack.front()->name]->primitive ))->position2;
				}
				else if (attribute_str=="position3") {
					if (attribute_index==0) attribute_type = 1;
					if (*stack.front()->type=="scene") to_set = ((Triangle*)( objects->back() ))->position3;
					else                               to_set = ((Triangle*)( (*lights)[*stack.front()->name]->primitive ))->position3;
				}
				else if (attribute_str=="center") { if (attribute_index==0) { attribute_type = 1; } to_set = camera->center; }
				else if (attribute_str=="up") { if (attribute_index==0) { attribute_type = 1; }; to_set = camera->up; }
				else if (attribute_str=="normal") {
					if (attribute_index==0) attribute_type = 1;
					if (*stack.front()->type=="scene") to_set = ((Plane*)(objects->back()))->normal;
					else                               to_set = ((Plane*)((*lights)[*stack.front()->name]->primitive))->normal;
				}

				else if (attribute_str=="ambient") { if (attribute_index==0) { attribute_type = -1; } to_set = (*materials)[*stack.back()->name]->ambient; }
				else if (attribute_str=="diffuse") { if (attribute_index==0) { attribute_type = -1; } to_set = (*materials)[*stack.back()->name]->diffuse; }
				else if (attribute_str=="specular") { if (attribute_index==0) { attribute_type = -1; } to_set = (*materials)[*stack.back()->name]->specular; }

				else if (attribute_str=="color") { if (attribute_index==0) { attribute_type = -1; } to_set = (*lights)[*stack.back()->name]->color; }

				else if (attribute_str=="background") { if (attribute_index==0) { attribute_type = -1; } to_set = background_color; }

				else goto ERROR_UNRECOGNIZED_TOKEN;

				if (attribute_index==0) {
					if (attribute_type==-1) {
						if (is_i) attribute_type = 0; //interpret as 0-255 color
						else attribute_type = 1; //interpret as 0.0-1.0 color
					}
					attribute_index = 1;
					to_set[0] = num_d;
					if (attribute_type==0) to_set[0] /= 255.0;
				}
				else if (attribute_index==1) {
					attribute_index = 2;
					to_set[1] = num_d;
					if (!is_i&&attribute_type==0) goto ERROR_EXPECTED_INT_DOUBLE_CONSISTENCY;
					if (attribute_type==0) to_set[1] /= 255.0;
				}
				else if (attribute_index==2) {
					attribute_index = 0;
					to_set[2] = num_d;
					if (!is_i&&attribute_type==0) goto ERROR_EXPECTED_INT_DOUBLE_CONSISTENCY;
					if (attribute_type==0) to_set[2] /= 255.0;
					attribute_str = "";
					attribute_size = 0;
				}
			}
			
			//Name
			//after attributes, because of things like "use "Material1"".
			else if (string_startswith("\"",token_str)&&string_endswith("\"",token_str)) {
				if (next_block_type=="") goto ERROR_EXPECTED_TOKEN_BEFORE_NAME;
				next_block_name = token_str;
			}

			else goto ERROR_UNRECOGNIZED_TOKEN;
		}
	}

	if (stack.size()!=0) goto ERROR_CONTEXT_CLOSING;
	if (next_block_type!="") goto ERROR_INCOMPLETE_TYPE;
	//the above implies "if (next_block_name!="")"
	//"if (attribute_str!="")" can never occur, because it is caught on pop
	//of stack, failure of which is caught above: "if (stack.size()!=0)"

	//SUCCESS!!!
	goto CLEANUP;

	ERROR_EXPECTED_TOKEN:
		error = err("Syntax","Expected a type, and possibly a name, before \"{\"!",token1line);
		goto CLEANUP;
	ERROR_EXPECTED_NAME:
		error = err("Syntax","Type \""+next_block_type+"\" requires a name!",token1line);
		goto CLEANUP;
	ERROR_EXPECTED_TOKEN_BEFORE_NAME:
		error = err("Syntax","Expected a type before name \"{\"!",token1line);
		goto CLEANUP;
	ERROR_EXPECTED_QUOTEBRACKET:
		error = err("Syntax","Expected a quoted name or bracket after type \""+next_block_type+"\"!",token1line);
		goto CLEANUP;
	ERROR_EXPECTED_ATTRIBUTE_VALUE:
		error = err("Syntax","Expected a value for attribute \""+attribute_str+"\"!",token1line);
		goto CLEANUP;
	ERROR_EXPECTED_ATTRIBUTE_INT:
		error = err("Syntax","Expected an integer value for attribute \""+attribute_str+"\"!",token1line);
		goto CLEANUP;
	ERROR_EXPECTED_INT_DOUBLE_CONSISTENCY:
		error = err("Syntax","Interpreting attribute \""+attribute_str+"\" as int based on first element.  Did not expect double!",token1line);
		goto CLEANUP;

	ERROR_CONTEXT_TYPE: {
		string context_name;
		if (stack.size()!=0) context_name = "context \""+*stack.back()->type+"\"";
		else context_name = "the global context";
		error = err("Context","Type \""+token_str+"\" not allowed inside "+context_name+"!",token1line);
		goto CLEANUP; }
	ERROR_CONTEXT_ATTRIBUTE: {
		string context_name;
		if (stack.size()!=0) context_name = "context \""+*stack.back()->type+"\"";
		else context_name = "the global context";
		error = err("Context","Attribute \""+token_str+"\" not allowed inside "+context_name+"!",token1line);
		goto CLEANUP;
	}
	ERROR_CONTEXT_CLOSING:
		error = err("Syntax","Context built by type \""+*stack.back()->type+"\" lacks a closing brace!",stack.back()->line_start);
		goto CLEANUP;
	ERROR_CONTEXT_POP_STACK:
		error = err("Syntax","\"}\" does not correspond to any block!",token1line);
		goto CLEANUP;
	ERROR_CONTEXT_MULTIPLE_ELEMENTS:
		error = err("Context","More than instance of type \""+next_block_type+"\" is not allowed inside context \""+*stack.back()->type+"\"!",token1line);
		goto CLEANUP;

	ERROR_NUMERIC_BOUNDS:
		error = err("Numeric","Unacceptable bounds for parameter \""+attribute_str+"\"!",token1line);
		goto CLEANUP;
	ERROR_UNRECOGNIZED_TOKEN:
		error = err("Type","Unrecognized token \""+token_str+"\"!",token1line);
		goto CLEANUP;
	ERROR_INCOMPLETE_TYPE:
		error = err("Syntax","The type \""+next_block_type+"\" lacks a context!",token1line);
		goto CLEANUP;
	ERROR_DEFINITION:
		error = err("Syntax","Name \""+token_str+"\" is not defined!",token1line);
		goto CLEANUP;
	ERROR_MULTIPLE_REFERENCE:
		error = err("Syntax","Name \""+token_str+"\" has already been referenced by this object!",token1line);
		goto CLEANUP;

	CLEANUP:
		while (stack.size()!=0) {
			remove_block(&stack);
		}

	return error;
}