#include "scene_parse.h"

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

Block::Block(string type, string name, int line_start) {
	this->type = new string(type);
	this->name = new string(name);
	this->special_material = NULL;
	this->line_start = line_start;
}
Block::~Block() {
	delete type;
	delete name;
}

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

list<string*>* scene_parse(SceneParser* parser,
	Camera* camera,
	Image* image,
	map<string,Material*>* materials, list<Object*>* objects, list<glLib::Object::object*>* loaded_objects, map<string,Light*>* lights,
	DT_COL background_color[3])
{
	string* error = NULL;

	//printf("Loaded %d tokens.\n",parser->num_tokens);

	parser->remove_comment_lines();
	//printf("Removed comments (%d tokens remain).\n",parser.num_tokens);
	/*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);
	//printf("Split joined tokens (now %d tokens).\n",parser.num_tokens);

	parser->combine_around(":");
	//printf("Combined split tokens (now %d tokens).\n",parser.num_tokens);
	//printf("Processed %d tokens.\n",parser.num_tokens);

	printf("  Loaded %lu tokens.\n",parser->num_tokens);
	//parser->print();

	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;
	int num_tokens_processed = 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_str=="object:obj") {
			//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"||token_str=="object:obj") {
				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=="aperture"||
			token_str=="radius"||token_str=="focal"||
			token_str=="use"||token_str=="shininess"||token_str=="position"||token_str=="position2"||token_str=="position3"||
			token_str=="center"||token_str=="up"||
			token_str=="texture_diffuse"||token_str=="model"||
			token_str=="normal"||token_str=="ambient"||token_str=="diffuse"||token_str=="specular"||token_str=="emission"||token_str=="absorption"||
			token_str=="background"||
			token_str=="color"||token_str=="action:shadow"||token_str=="action:reflect"||token_str=="action:refract"||
			token_str=="index"||token_str=="film"
		) {
			//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=="aperture") { attribute_size=2; if (stack.size()!=1||*stack.back()->type!="camera") 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=="focal") { 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"&&
						 *stack.back()->type!="object:obj")
					) 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=="index") { attribute_size=2; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }

			else if (token_str=="texture_diffuse") { attribute_size=1; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }

			else if (token_str=="model") { attribute_size=1; if (stack.size()!=2||*stack.back()->type!="object:obj") 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=1; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="action:refract") { attribute_size=1; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }

			else if (token_str=="film") { 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"&&
						*stack.back()->type!="object:obj") 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=="emission") { attribute_size=3; if (stack.size()!=1||*stack.back()->type!="material") goto ERROR_CONTEXT_ATTRIBUTE; }
			else if (token_str=="absorption") { 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];
					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];
					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];
					delete current_light->primitive;
					current_light->primitive = new Plane();
				}
				else objects->push_back(new Plane());
			}
			else if (next_block_type=="object:triangle") {
				Triangle* new_triangle = new Triangle();
				if (*stack.back()->type=="light") {
					Light* current_light = (*lights)[*stack.back()->name];
					delete current_light->primitive;
					current_light->primitive = new_triangle;
				}
				else objects->push_back(new_triangle);
			}
			else if (next_block_type=="object:obj") {
				//Individual polygons are loaded as individual objects
			}

			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 {
			DT_FP 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==      "focal") {
						//TODO: focal length must be less than image_plane
						if (num_d<=0.0) goto ERROR_NUMERIC_BOUNDS;
						camera->recip_focal_length = 1.0/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_IMAGE_WIDTH)) { goto ERROR_NUMERIC_BOUNDS; } image->width=num_i; }
					else if (attribute_str== "height") { if (!(num_i>0&&num_i<=MAX_IMAGE_HEIGHT)) { goto ERROR_NUMERIC_BOUNDS; } image->height=num_i; }
					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

						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;
						mat->reflect = true;
					}
					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

						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;
						mat->refract = true;
					}
					else if (attribute_str=="samples") { if (!(num_i>0&&num_i<=MAX_IMAGE_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;
							prim->set_material((*materials)[token_str]);
						}
						else if (*stack.back()->type=="object:obj") {
							stack.back()->special_material = (*materials)[token_str];
						}
						else {
							prim = objects->back();
							prim->set_material((*materials)[token_str]);
						}
					}
					else if (attribute_str=="texture_diffuse") {
						Material* mat = (*materials)[*stack.back()->name]; //won't access violation because it was checked that it's in a material block, above
						if (mat->texture!=NULL) goto ERROR_MULTIPLE_REFERENCE;
						if (token_str.length()<2) goto ERROR_UNRECOGNIZED_TOKEN;

						string path = token_str.substr(1,token_str.length()-2);

						SDL_Surface* surf1 = IMG_Load(path.c_str());
						if (surf1==NULL) goto ERROR_LOAD;

						SDL_Surface* surf2 = SDL_CreateRGBSurface(SDL_SWSURFACE|SDL_SRCALPHA, surf1->w,surf1->h, 32, SDL_MASK_RED,SDL_MASK_GREEN,SDL_MASK_BLUE,SDL_MASK_ALPHA);

						SDL_Rect rect = {0,0,surf1->w,surf1->h};

						SDL_SetAlpha(surf1,0,surf1->format->alpha); //disable alpha blending so that copying the alpha works
						SDL_BlitSurface(surf1,&rect,surf2,&rect);
						SDL_SetAlpha(surf2,SDL_SRCALPHA,surf2->format->alpha); //reenable it

						SDL_FreeSurface(surf1);

						mat->texture = surf2;

					}
					else if (attribute_str=="model") {
						glLib::Object::object* obj;

						if (token_str.length()<2) goto ERROR_UNRECOGNIZED_TOKEN;
						string path = token_str.substr(1,token_str.length()-2);

						try { obj = new glLib::Object::object(path);
						} catch (glLib::error e) { goto ERROR_LOAD; }

						obj->build_arrays();

						if (stack.back()->special_material==NULL) {
							for (map<string,glLib::material*>::const_iterator iter=obj->mtllib.begin();iter!=obj->mtllib.end();++iter) {
								glLib::material* mat1 = iter->second;
								string name = "__"+mat1->name;

								Material* mat2 = new Material(name);
								glLib::Math::Vec::copy3(mat1->Ka,mat2->ambient);
								glLib::Math::Vec::copy3(mat1->Kd,mat2->diffuse);
								glLib::Math::Vec::copy3(mat1->Ks,mat2->specular);
								mat2->refractive_index = mat1->Ni;
								mat2->shininess = mat1->Ns;
								if (mat1->texture!=NULL) {
									mat2->texture = mat1->texture->data_surf2; ++(mat1->texture->data_surf2->refcount);
								}
								for (map<string,Light*>::const_iterator iter=lights->begin();iter!=lights->end();++iter) {
									(*mat2->to_shadow)[iter->second] = SAMPLES_SHADOW_DEFAULT;
								}

								(*materials)[name] = mat2;
							}
						}

						for (unsigned long mesh_index=0;mesh_index<obj->numof_meshes;++mesh_index) {
							glLib::Object::glLibInternal_mesh* mesh = obj->meshes[mesh_index];
							unsigned long vpf = mesh->verts_per_face;
							if (vpf!=3) {
								printf("Warning: a mesh in the object \"%s\" has %lu vertices per face, instead of the required 3!  Skipping . . .\n",token_str.c_str(),vpf);
								continue;
							}

							string name;
							if (stack.back()->special_material==NULL) {
								name = "__"+mesh->requested_material_name;
							}
							else {
								name = *( stack.back()->special_material->name );
							}
							for (unsigned long i=0; i<mesh->numof_indices; i+=3) {
								Triangle* t = new Triangle();
								if (materials->find(name)==materials->end()) goto ERROR_DEFINITION;
								t->set_material( (*materials)[name] );

								//glLibMathVec::copy3(obj->array_vertices+3*mesh->vindices[i  ],t->position );
								//glLibMathVec::copy3(obj->array_vertices+3*mesh->vindices[i+1],t->position2);
								//glLibMathVec::copy3(obj->array_vertices+3*mesh->vindices[i+2],t->position3);
								glLib::Math::Vec::copy3(mesh->array_vertices+3*(i  ),t->position );
								glLib::Math::Vec::copy3(mesh->array_vertices+3*(i+1),t->position2);
								glLib::Math::Vec::copy3(mesh->array_vertices+3*(i+2),t->position3);

								if (mesh->has_normals) {
									glLib::Math::Vec::copy3(mesh->array_normals+3*(i  ),t->normal1);
									glLib::Math::Vec::copy3(mesh->array_normals+3*(i+1),t->normal2);
									glLib::Math::Vec::copy3(mesh->array_normals+3*(i+2),t->normal3);
									t->_must_calculate_normal = false;
								}
								else {
									DT_FP v1[3],v2[3],n[3];
									glLib::Math::Vec::subtract3(t->position2,t->position,v1);
									glLib::Math::Vec::subtract3(t->position3,t->position,v2);
									glLib::Math::Vec::cross(v1,v2,n);
									glLib::Math::Vec::normalize3(n);
									glLib::Math::Vec::copy3(n,t->normal1);
									glLib::Math::Vec::copy3(n,t->normal2);
									glLib::Math::Vec::copy3(n,t->normal3);
								}
								if (mesh->has_texcoords) {
									glLib::Math::Vec::copy2(mesh->array_texcoords+2*(i  ),t->uv1);
									glLib::Math::Vec::copy2(mesh->array_texcoords+2*(i+1),t->uv2);
									glLib::Math::Vec::copy2(mesh->array_texcoords+2*(i+2),t->uv3);
								}

								objects->push_back(t);
							}
						}

						loaded_objects->push_back(obj);
					}
					else goto ERROR_UNRECOGNIZED_TOKEN; //NaN
				}

				attribute_str = "";
				attribute_size = 0;
			}
			else if (attribute_size==2) {
				if      (attribute_str== "action:shadow") {
					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;
						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==       "film") {
					Material* mat = (*materials)[*stack.back()->name]; //won't access violation because it was checked that it's in a material block, above
					//if (!mat->refract) goto ERROR_MUST_REFRACT; //other materials can be coated too.
					if (attribute_index==0) {
						if (mat->film) goto ERROR_MULTIPLE_REFERENCE;
						mat->film = true;

						attribute_index = 1;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						(*materials)[*stack.back()->name]->film_thickness = num_d;
					}
					else if (attribute_index==1) {
						attribute_index = 0;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						mat->film_refractive_index = num_d;

						attribute_str = "";
						attribute_size = 0;
					}
					
				}
				else if (attribute_str=="index") {
					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;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						mat->refractive_index = num_d;
					}
					else if (attribute_index==1) {
						attribute_index = 0;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						mat->exponent_refractive = num_d;

						attribute_str = "";
						attribute_size = 0;
					}
				}
				else if (attribute_str=="aperture") {
					if (attribute_index==0) {
						attribute_index = 1;
						if (!is_d) goto ERROR_UNRECOGNIZED_TOKEN; //NaN
						camera->aperture = 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;
						camera->aperture_samples = num_i;

						attribute_str = "";
						attribute_size = 0;
					}
				}
				else goto ERROR_UNRECOGNIZED_TOKEN;
			}
			else if (attribute_size==3) {
				DT_FP* 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=="emission") { if (attribute_index==0) { attribute_type = -1; } to_set = (*materials)[*stack.back()->name]->emission; }
				else if (attribute_str=="absorption") { if (attribute_index==0) { attribute_type = -1; } to_set = (*materials)[*stack.back()->name]->absorption; }

				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;
		}

		++num_tokens_processed;
		if (num_tokens_processed%1000==0) {
			printf("\r  Parsed %d / %lu tokens.        ",num_tokens_processed,parser->num_tokens);
		}
	}
	printf("\r  Parsed %lu / %lu tokens.\n",parser->num_tokens,parser->num_tokens);

	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 one instance of type \""+next_block_type+"\" is not allowed inside context \""+*stack.back()->type+"\"!",token1line);
		goto CLEANUP;*/

	/*ERROR_MUST_REFRACT:
		error = err("Syntax","Material film cannot be applied unless the material is refractive!",token1line);
		goto CLEANUP;*/

	ERROR_LOAD:
		error = err("Load","Error loading \""+token_str+"\": \""+SDL_GetError()+"\"!",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);
		}

	list<string*>* error_stack = NULL;
	if (error!=NULL) {
		error_stack = new list<string*>();
		error_stack->push_back(error);
	}
	return error_stack;
}