#pragma once
#include "token.h"
#include "scene_parser.h"
#include "parse_scene.h"
#include "../output_helpers.h"

int result;
Token* token = NULL;
unsigned long num_tokens_received = 0;
unsigned long line_num = 1;
Scene* scene;
SceneParser* parser;
list<string*>* error_trace = NULL;
const char* what_parsing_when_error = "";

//Macro variables
stack<unsigned long>* line_num_stack;
unsigned long PROCESS_CONTEXT_temp, TEST_RESULT_temp;

#ifndef DEBUG_TOKENS
#define TEST_RESULT(error_function,what_parsing,starting_line) \
switch (result) {\
	case -1: {\
		TEST_RESULT_temp = line_num;\
		line_num = starting_line;\
		what_parsing_when_error = what_parsing; error_function();\
		line_num = TEST_RESULT_temp;\
		goto FUNCTION_END;\
	}\
	case  0:\
		break;\
	case  1:\
		break;\
}\
line_num = new_line_num;
#else
#define TEST_RESULT(error_function,what_parsing,starting_line) \
switch (result) {\
	case -1: {\
		TEST_RESULT_temp = line_num;\
		line_num = starting_line;\
		output_debug("Encountering error on line "+to_string(TEST_RESULT_temp)+" (starting on "+to_string(starting_line)+"): breaking.",-1);\
		what_parsing_when_error = what_parsing; error_function();\
		line_num = TEST_RESULT_temp;\
		goto FUNCTION_END;\
	}\
	case  0:\
		break;\
	case  1:\
		break;\
}\
output_debug("Got token \""+token->get_content_printable()+"\", t=",0,false); token->type->print(); printf(", r=%d\n",result);
#endif

#define NEXT_TOKEN(allowable) result=parser->next_token(allowable,&token);
#define NEXT_BLOCK0(function) result=function();
#define NEXT_BLOCK1(function,argument) result=function(argument);
#define NEXT_BLOCK2(function,argument1,argument2) result=function(argument1,argument2);
#define PROCESS_CONTEXT(function_macro,function_error,block_name) \
	line_num_stack->push(line_num);\
	/*printf("====Starting at line %u====\n",PROCESS_CONTEXT_temp);*/\
	function_macro \
	/*printf("====Ending at line %u====\n",line_num);*/\
	PROCESS_CONTEXT_temp = line_num_stack->top();\
	line_num_stack->pop();\
	TEST_RESULT(function_error,block_name,PROCESS_CONTEXT_temp)

void err(string type, string msg, int line_num) {
	if (error_trace==NULL) error_trace = new list<string*>();
	
	string* error;
	
	error = new string();
	*error += type + " Error";
	if (line_num!=-1) *error += " (line "+to_string(line_num)+"):";
	error_trace->push_back(error);

	error = new string();
	*error += string("  While parsing ")+what_parsing_when_error+":";
	error_trace->push_back(error);

	error = new string();
	*error += "  "+msg;
	error_trace->push_back(error);
}

inline void set_error_name_required(void) {
	err("Syntax","A name is required for this context!",line_num);
}
inline void set_error_expected_int(void) {
	err("Syntax","Expected an integer argument!",line_num);
}
inline void set_error_expected_float(void) {
	err("Syntax","Expected a floating point argument!",line_num);
}
inline void set_error_expected_number(void) {
	err("Syntax","Expected a numeric argument!",line_num);
}
inline void set_error_comment_closing(void) {
	err("Syntax","Block comment beginning on this line is not closed!",line_num);
}
inline void set_error_no_appropriate_tokens(void) {
	err("Context","Could not interpret this line meaningfully in the current context!",line_num);
}
inline void set_error_internal(const char* msg) {
	err("Internal",msg,line_num);
}

int parse_comment_block(void) {
	output_debug("Parsing remainder of comment block.",1);

	PROCESS_CONTEXT(NEXT_TOKEN(STT_COMMENT_BLOCK_END),set_error_comment_closing,"comment block")

	FUNCTION_END:
		output_debug("Done parsing remainder of comment block.",-1);
		return result;
}
void parse_comment_line(void) {
	output_debug("Parsing comment line.",1);
	//printf("Before: \"");parser->print_remaining(100);printf("\"\n");
	parser->kill_line();
	++line_num;
	//printf("After: \"");parser->print_remaining(100);printf("\"");
	output_debug("Done parsing comment line.",-1);
}

int parse_attribute1(int* attribute) {
	output_debug("Parsing integer attribute.",1);

	START_PARSE:
		PROCESS_CONTEXT(NEXT_TOKEN(STT_WHITESPACE|STT_COMMENT_BLOCK_START),set_error_no_appropriate_tokens,"integer attribute")

	if (token->is(STT_COMMENT_BLOCK_START)) {
		PROCESS_CONTEXT(NEXT_BLOCK0(parse_comment_block),set_error_no_appropriate_tokens,"integer attribute")
		goto START_PARSE;
	}

	PROCESS_CONTEXT(NEXT_TOKEN(STT_INTEGER),set_error_expected_int,"integer attribute")
	*attribute = atoi(token->content->c_str());

	FUNCTION_END:
		output_debug("Done parsing integer attribute.",-1);
		return result;
}
int parse_attribute1(double* attribute) {
	//TODO: support parsing integers as floats.
	output_debug("Parsing floating point attribute.",1);

	START_PARSE:
		PROCESS_CONTEXT(NEXT_TOKEN(STT_WHITESPACE|STT_COMMENT_BLOCK_START),set_error_no_appropriate_tokens,"floating point attribute")

	if (token->is(STT_COMMENT_BLOCK_START)) {
		PROCESS_CONTEXT(NEXT_BLOCK0(parse_comment_block),set_error_no_appropriate_tokens,"floating point attribute")
		goto START_PARSE;
	}

	PROCESS_CONTEXT(NEXT_TOKEN(STT_DECIMAL),set_error_expected_int,"floating point attribute")
	*attribute = atof(token->content->c_str());
	
	FUNCTION_END:
		output_debug("Done parsing floating point attribute.",-1);
		return result;
}
int parse_attribute3(double attribute[3]) {
	output_debug("Parsing 3 floating point attributes.",1);

	for (char i=0;i<3;++i) {
		PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute1,&attribute[i]),set_error_expected_int,"vector of 3 floating point attributes")
	}

	FUNCTION_END:
		output_debug("Done parsing 3 floating point attributes.",-1);
		return result;
}

int parse_context_block_image(string block_name) {
	output_debug("Parsing image block.",1);
	while (true) {
		START_PARSE:
			PROCESS_CONTEXT(NEXT_TOKEN(
				STT_NEWLINE|STT_WHITESPACE|
				STT_COMMENT_LINE|STT_COMMENT_BLOCK_START|

				STT_ATTRIB_IMAGE_WIDTH|STT_ATTRIB_IMAGE_HEIGHT|STT_ATTRIB_IMAGE_SAMPLES|
				STT_BLOCK_END),
			set_error_no_appropriate_tokens,"image context")

		if      (token->is(STT_NEWLINE)) { ++line_num; goto START_PARSE; }
		else if (token->is(STT_WHITESPACE)) goto START_PARSE;
		else if (token->is(STT_COMMENT_LINE)) { parse_comment_line(); goto START_PARSE; }
		else if (token->is(STT_COMMENT_BLOCK_START)) {
			PROCESS_CONTEXT(NEXT_BLOCK0(parse_comment_block),set_error_no_appropriate_tokens,"image context")
			goto START_PARSE;
		}

		else if (token->is(STT_ATTRIB_IMAGE_WIDTH)) {
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute1,&scene->image->width),set_error_no_appropriate_tokens,"image context")
		}
		else if (token->is(STT_ATTRIB_IMAGE_HEIGHT)) {
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute1,&scene->image->height),set_error_no_appropriate_tokens,"image context")
		}
		else if (token->is(STT_ATTRIB_IMAGE_SAMPLES)) {
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute1,&scene->image->samples),set_error_no_appropriate_tokens,"image context")
		}
		else if (token->is(STT_BLOCK_END)) break;
	}
	FUNCTION_END:
		output_debug("Done parsing image block.",-1);
		return result;
}
int parse_context_block_camera(string block_name) {
	output_debug("Parsing camera block.",1);
	while (true) {
		START_PARSE:
			PROCESS_CONTEXT(NEXT_TOKEN(
				STT_NEWLINE|STT_WHITESPACE|
				STT_COMMENT_LINE|STT_COMMENT_BLOCK_START|

				STT_ATTRIB_CAMERA_POSITION|STT_ATTRIB_CAMERA_CENTER|STT_ATTRIB_CAMERA_UP|
				STT_ATTRIB_CAMERA_IMAGE_PLANE|STT_ATTRIB_CAMERA_FOCAL_LENGTH|
				STT_BLOCK_END),
			set_error_no_appropriate_tokens,"camera context")

		if      (token->is(STT_NEWLINE)) { ++line_num; goto START_PARSE; }
		else if (token->is(STT_WHITESPACE)) goto START_PARSE;
		else if (token->is(STT_COMMENT_LINE)) { parse_comment_line(); goto START_PARSE; }
		else if (token->is(STT_COMMENT_BLOCK_START)) {
			PROCESS_CONTEXT(NEXT_BLOCK0(parse_comment_block),set_error_no_appropriate_tokens,"camera context")
			goto START_PARSE;
		}

		else if (token->is(STT_ATTRIB_CAMERA_POSITION)) {
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute3,scene->camera->position),set_error_no_appropriate_tokens,"camera context")
		}
		else if (token->is(STT_ATTRIB_CAMERA_CENTER)) {
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute3,scene->camera->center),set_error_no_appropriate_tokens,"camera context")
		}
		else if (token->is(STT_ATTRIB_CAMERA_UP)) {
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute3,scene->camera->up),set_error_no_appropriate_tokens,"camera context")
		}
		else if (token->is(STT_ATTRIB_CAMERA_IMAGE_PLANE)) {
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute1,&scene->camera->image_plane),set_error_no_appropriate_tokens,"camera context")
		}
		else if (token->is(STT_ATTRIB_CAMERA_FOCAL_LENGTH)) {
			double focal = 0.0; //initialized to mask unnecessary warning
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute1,&focal),set_error_no_appropriate_tokens,"camera context")
			scene->camera->recip_focal_length = 1.0 / focal;
		}
		else if (token->is(STT_BLOCK_END)) break;
	}
	FUNCTION_END:
		output_debug("Done parsing camera block.",-1);
		return result;
}
int parse_context_block_light(string block_name) {
	output_debug("Parsing light block.",1);

	if (block_name=="") {
		set_error_name_required();
		return -1;
	}
	Light* next_light = new Light(block_name);
	(*scene->lights)[block_name] = next_light;

	while (true) {
		START_PARSE:
			PROCESS_CONTEXT(NEXT_TOKEN(
				STT_NEWLINE|STT_WHITESPACE|
				STT_COMMENT_LINE|STT_COMMENT_BLOCK_START|

				STT_ATTRIB_COLOR|
				STT_BLOCK_END),
			set_error_no_appropriate_tokens,"light context")

		if      (token->is(STT_NEWLINE)) { ++line_num; goto START_PARSE; }
		else if (token->is(STT_WHITESPACE)) goto START_PARSE;
		else if (token->is(STT_COMMENT_LINE)) { parse_comment_line(); goto START_PARSE; }
		else if (token->is(STT_COMMENT_BLOCK_START)) {
			PROCESS_CONTEXT(NEXT_BLOCK0(parse_comment_block),set_error_no_appropriate_tokens,"light context")
			goto START_PARSE;
		}

		else if (token->is(STT_ATTRIB_COLOR)) {
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute3,next_light->color),set_error_no_appropriate_tokens,"light context")
		}
		else if (token->is(STT_BLOCK_END)) break;
	}

	FUNCTION_END:
		output_debug("Done parsing light block.",-1);
		return result;
}
int parse_context_block_object(string block_name) {
	output_debug("Parsing object block.",1);

	Light* next_light = new Light(block_name);
	(*scene->lights)[block_name] = next_light;

	while (true) {
		START_PARSE:
			PROCESS_CONTEXT(NEXT_TOKEN(
				STT_NEWLINE|STT_WHITESPACE|
				STT_COMMENT_LINE|STT_COMMENT_BLOCK_START|

				STT_ATTRIB_COLOR|
				STT_BLOCK_END),
			set_error_no_appropriate_tokens,"object context")

		if      (token->is(STT_NEWLINE)) { ++line_num; goto START_PARSE; }
		else if (token->is(STT_WHITESPACE)) goto START_PARSE;
		else if (token->is(STT_COMMENT_LINE)) { parse_comment_line(); goto START_PARSE; }
		else if (token->is(STT_COMMENT_BLOCK_START)) {
			PROCESS_CONTEXT(NEXT_BLOCK0(parse_comment_block),set_error_no_appropriate_tokens,"object context")
			goto START_PARSE;
		}

		else if (token->is(STT_ATTRIB_COLOR)) {
			PROCESS_CONTEXT(NEXT_BLOCK1(parse_attribute3,next_light->color),set_error_no_appropriate_tokens,"object context")
		}
		else if (token->is(STT_BLOCK_END)) break;
	}

	FUNCTION_END:
		output_debug("Done parsing object block.",-1);
		return result;
}
int parse_context_block(Token block_type, string block_name) {
	output_debug("Parsing context block.",1);
	result = 0;
	if (block_type.is(STT_CONTEXT_IMAGE)) {
		PROCESS_CONTEXT(NEXT_BLOCK1(parse_context_block_image,block_name),set_error_no_appropriate_tokens,"generic context")
	}
	else if (block_type.is(STT_CONTEXT_CAMERA)) {
		PROCESS_CONTEXT(NEXT_BLOCK1(parse_context_block_camera,block_name),set_error_no_appropriate_tokens,"generic context")
	}
	else if (block_type.is(STT_CONTEXT_LIGHT)) {
		PROCESS_CONTEXT(NEXT_BLOCK1(parse_context_block_light,block_name),set_error_no_appropriate_tokens,"generic context")
	}
	else if (block_type.is(STT_CONTEXT_OBJECT)) {
		PROCESS_CONTEXT(NEXT_BLOCK1(parse_context_block_object,block_name),set_error_no_appropriate_tokens,"generic context")
	}
	else {
		set_error_internal("STT_CONTEXTS must be wrong.");
		result = -1;
	}
	FUNCTION_END:
		output_debug("Done parsing context block.",-1);
		return result;
}
int parse_context_block_begin(void) {
	output_debug("Parsing beginning of context block.",1);
	Token block_type(*token);
	string block_name;

	START_PARSE1:
		PROCESS_CONTEXT(NEXT_TOKEN(
			STT_NEWLINE|STT_WHITESPACE|
			STT_COMMENT_LINE|STT_COMMENT_BLOCK_START|

			STT_STRING|STT_BLOCK_START),
		set_error_no_appropriate_tokens,"beginning of context")

	if      (token->is(STT_NEWLINE)) { ++line_num; goto START_PARSE1; }
	else if (token->is(STT_WHITESPACE)) goto START_PARSE1;
	else if (token->is(STT_COMMENT_LINE)) { parse_comment_line(); goto START_PARSE1; }
	else if (token->is(STT_COMMENT_BLOCK_START)) {
		PROCESS_CONTEXT(NEXT_BLOCK0(parse_comment_block),set_error_no_appropriate_tokens,"beginning of context")
		goto START_PARSE1;
	}
	else if (token->is(STT_STRING)) {
		block_name = *token->content;
		goto START_PARSE2;
	}
	else goto START_PARSE_CONTENT;

	START_PARSE2:
		PROCESS_CONTEXT(NEXT_TOKEN(
			STT_NEWLINE|STT_WHITESPACE|
			STT_COMMENT_LINE|STT_COMMENT_BLOCK_START|

			STT_BLOCK_START),
		set_error_no_appropriate_tokens,"beginning of context")

	if      (token->is(STT_NEWLINE)) { ++line_num; goto START_PARSE2; }
	else if (token->is(STT_WHITESPACE)) goto START_PARSE2;
	else if (token->is(STT_COMMENT_LINE)) { parse_comment_line(); goto START_PARSE2; }
	else if (token->is(STT_COMMENT_BLOCK_START)) {
		PROCESS_CONTEXT(NEXT_BLOCK0(parse_comment_block),set_error_no_appropriate_tokens,"beginning of context")
		goto START_PARSE2;
	}
	else goto START_PARSE_CONTENT;

	START_PARSE_CONTENT:
		output_debug("Done parsing beginning of context block.",-1);
		PROCESS_CONTEXT(NEXT_BLOCK2(parse_context_block,block_type,block_name),set_error_no_appropriate_tokens,"beginning of context")

	FUNCTION_END:
		return result;
}

int parse_file(void) {
	output_debug("Parsing file.",1);
	while (true) {
		PROCESS_CONTEXT(NEXT_TOKEN(
			STT_COMMENT_BLOCK_START|STT_COMMENT_LINE|
			STT_NEWLINE|STT_WHITESPACE|STT_EOF|

			STT_CONTEXTS),
		set_error_no_appropriate_tokens,"file")
		++num_tokens_received;

		if (token->is(STT_COMMENT_BLOCK_START)) {
			PROCESS_CONTEXT(NEXT_BLOCK0(parse_comment_block),set_error_no_appropriate_tokens,"file")
		}
		else if (token->is(STT_COMMENT_LINE)) {
			parse_comment_line();
		}
		else if (token->is(STT_NEWLINE)) ++line_num;
		else if (token->is(STT_WHITESPACE));
		else if (token->is(STT_EOF)) break;
		else if (token->is(STT_CONTEXTS)) {
			PROCESS_CONTEXT(NEXT_BLOCK0(parse_context_block_begin),set_error_no_appropriate_tokens,"file")
		}
		else {
			set_error_no_appropriate_tokens();
			break;
		}

		if (num_tokens_received%1==0) {
			//printf("\r  Parsed %d tokens.        ",num_tokens_received);
		}
	}
	FUNCTION_END:
		output_debug("Done parsing file.",1);
		//printf("\r  Parsed %lu tokens.\n",num_tokens_received);
		return result;
}
list<string*>* parse_scene(Scene* new_scene) {
	line_num_stack = new stack<unsigned long>();

	scene = new_scene;
	parser = scene->parser;

	parse_file();

	delete line_num_stack;

	return error_trace;
}