#pragma once
#include "../includes.h"
#include "scene_parse.h"
#include "../scene/scene.h"

class Token {
	friend class SceneParser;
	private:
		string* _value;
		unsigned long _line_number;
	public:
		Token(const char* str, unsigned long line_number) {
			_value = new string(str);
			_line_number = line_number;
		}
		Token(string str, unsigned long line_number) {
			_value = new string(str);
			_line_number = line_number;
		}
		~Token() {
			delete _value;
		}

		string& get_value(void) {
			return *_value;
		}
		unsigned long get_line_num(void) {
			return _line_number;
		}
};

typedef list<Token*> token_storage_type;
class SceneParser {
	private:
		token_storage_type* _tokens;
		token_storage_type::const_iterator* _iter;
		Token *_next, *_current, *_previous;
		unsigned long _current_position;
		unsigned long _num_removed;
		Scene* _scene;
	public:
		//public, but don't change it outside!
		unsigned long num_tokens;
		const char* file;
	public:
		SceneParser(Scene* scene, const char* path) {
			_scene = scene;

			_tokens = new token_storage_type();
			_iter = new token_storage_type::const_iterator();

			file = file_get_file(path);
			if (file==NULL) throw glLib::error("Could not open file!");

			unsigned long i = 0, line_number = 1;
			string current_token = "";
			bool in_white_space = true;
			while (true) {
				char c = file[i++];
				if (c==' '||c=='\t'||c=='\n'||c=='\0') {
					if (!in_white_space) {
						in_white_space = true;
						_tokens->push_back(new Token(current_token,line_number));
						current_token = "";
					}
					if (c=='\n') ++line_number;
					if (c=='\0') break;
				}
				else {
					in_white_space = false;
					current_token += c;
				}
			}

			reset_iter();
			num_tokens = _tokens->size();

			_next = **_iter;
			_previous = NULL;

			_num_removed = 0;
		}
		~SceneParser() {
			free((void*)(file));

			for (token_storage_type::iterator iter=_tokens->begin();iter!=_tokens->end();++iter) {
				delete (*iter);
			}
			while (_tokens->size()>0) {
				_tokens->pop_back();
			}
			delete _tokens;
			delete _iter;
		}

		list<string*>* parse(void) {
			return scene_parse(this,_scene->camera,_scene->image,_scene->materials,_scene->objects,_scene->loaded_objects,
				_scene->lights,_scene->background_color);
		}

		bool has_previous(void) { return _current_position>-1; }
		bool has_next(void) { return _current_position<num_tokens; }
		Token* next(void) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			if (!has_next()) throw glLib::error("Cannot get next; no more tokens!");
			#endif

			_current = (**_iter);
			++(*_iter);
			++_current_position;

			return _current;
		}
		Token* previous(void) {
			#ifdef GLLIB_ERROR_MODE_REPORT_ERRORS
			if (!has_previous()) throw glLib::error("Cannot get previous; no more tokens!");
			#endif

			--(*_iter);
			_current = **_iter;
			--_current_position;

			return _current;
		}
		void reset_iter(void) {
			*_iter = _tokens->begin();
			_current_position = 0;
		}

		void remove_comment_lines(string comment_name="//") {
			reset_iter();

			_num_removed = 0;

			bool ignoring = false;
			unsigned long ignoring_line;

			token_storage_type::iterator iter;
			for (iter=_tokens->begin();iter!=_tokens->end();) {
				Token* token = *iter;
				string token_str = *token->_value;
				unsigned long token_line_num = token->get_line_num();

				if (ignoring) {
					if (token->get_line_num()>ignoring_line) {
						ignoring = false;
					}
					else {
						if (!delete_token(&iter)) break;
						continue;
					}
				}

				if (string_startswith(comment_name,token_str)) {
					ignoring = true;
					ignoring_line = token_line_num;
					if (!delete_token(&iter)) break;
					continue;
				}

				++iter;
			}

			reset_iter();
		}
		bool remove_comment_blocks(string comment_start="/*", string comment_end="*/") {
			reset_iter();

			throw glLib::error("Not implemented!");
			_num_removed = 0;

			token_storage_type::iterator iter;
			for (iter=_tokens->begin();iter!=_tokens->end();) {
				printf("Got \"%s\".\n",(*iter)->_value->c_str());
				while (true) {
					Token* token = *iter;

					printf("Checking token \"%s\".\n",token->_value->c_str());

					if (string_startswith(comment_start,*token->_value)) {
						printf("  Token \"%s\" starts comment:\n",token->_value->c_str());
						*token->_value = token->_value->substr(comment_start.length());
						printf("  New token is \"%s\".\n",token->_value->c_str());
						while (true) {
							printf("    Begin loop\n");
							size_t p = token->_value->find(comment_end);
							if (p!=string::npos) {
								//Unsigned int is not quite the same, but it's pretty close.
								printf("      Found comment end in the token at location %d\n",(unsigned int)p);
								*token->_value = token->_value->substr(p+comment_end.length());
								printf("      New token is \"%s\".\n",token->_value->c_str());
							}

							//check this token again (outer loop)
							if (*token->_value!="") {
								printf("      Continuing to parse token.\n");
								break;
							}

							//token is useless (it is entirely a comment (or removing the comment start made it empty))
							//delete it and get a new one
							printf("      Token is useless.\n");
							if (!delete_token(&iter)) {
								return false;
							}
							break;
						}
					}
					else {
						break;
					}
				}

				++iter;
			}

			reset_iter();

			return true;
		}

		void split(list<string> separable_tokens) {
			reset_iter();

			for (token_storage_type::iterator iter1=_tokens->begin();iter1!=_tokens->end();++iter1) {
				unsigned long line_number = (*iter1)->_line_number;
				//printf("Testing token \"%s\".\n",(*iter1)->_value->c_str());
				bool changed;
				do {
					//print(false);printf("\n");
					changed = false;
					for (list<string>::const_iterator iter2=separable_tokens.begin();iter2!=separable_tokens.end();++iter2) {
						string main = *(*iter1)->_value;
						string test = *iter2;
						//printf("  Testing if \"%s\" is in \"%s\"",test.c_str(),main.c_str());
						if (main==test) {
							//printf(" (same, don't split).\n");
							continue;
						}
						//printf(".\n");
						if ( string_in_string(test,main) ) {
							//printf("  It is!  Splitting \"%s\" at \"%s\".\n",main.c_str(),test.c_str());

							list<string> new_strings;
							while (true) {
								size_t p = main.find(test);
								if (p!=string::npos) {
									string new_str = main.substr(0,p);
									if (new_str!="") {
										new_strings.push_back(new_str);
									}
									new_strings.push_back(test);
									main = main.substr(p+test.length());
								}
								else {
									if (main!="") new_strings.push_back(main);
									break;
								}
							}

							list<string>::const_iterator end = new_strings.end(); --end;
							for (list<string>::const_iterator iter4=new_strings.begin();iter4!=end;++iter4) {
								if (*iter4=="") continue;
								_tokens->insert(iter1,new Token(*iter4,line_number));
								++num_tokens;
							}

							*(*iter1)->_value = new_strings.back();

							for (unsigned long i=0;i<new_strings.size()-1;++i) --iter1;

							changed = true;

							break;
						}
					}
					//printf("\n");
				} while (changed);
			}

			reset_iter();
		}
		void combine_around(string combine_token) {
			reset_iter();

			bool changed;
			token_storage_type::iterator iter0, iter1, iter2;
			do {
				changed = false;
				bool is_first = true;
				for (iter1=_tokens->begin();iter1!=_tokens->end();++iter1) {
					Token* token = *iter1;
					if (*token->_value==combine_token) {
						iter0=iter1, iter2=iter1;
						--iter0;
						++iter2;

						//Concatenate iter2 to iter1
						if ( iter2!=_tokens->end() && (*iter1)->get_line_num()==(*iter2)->get_line_num() ) {
							*(*iter1)->_value += *(*iter2)->_value;
							delete_token(&iter2);
						}
						//Concatenate iter1 to iter0
						if ( !is_first && (*iter0)->get_line_num()==(*iter1)->get_line_num() ) {
							*(*iter0)->_value += *(*iter1)->_value;
							delete_token(&iter1);

							iter1 = iter0;
						}

						changed = true;
					}
					is_first = false;
				}
			} while (changed);

			reset_iter();
		}

		void print(bool with_lines=true, bool with_newline=true) {
			reset_iter();

			unsigned long line = 0;
			while (has_next()) {
				Token *t = next();
				if (with_lines) {
					if (t->get_line_num()>line) {
						printf("\n");
						++line;
					}
				}
				printf("\"%s\" ",t->get_value().c_str());
			}
			if (with_newline) printf("\n");

			reset_iter();
		}

	private:
		bool delete_token(token_storage_type::iterator* iter1) {
			--num_tokens;
			++_num_removed;

			token_storage_type::iterator iter2 = *iter1;
			++iter2;
			delete **iter1;
			//_tokens->remove( **iter1 ); //O(n)
			_tokens->erase(*iter1); //O(1)
			if (iter2==_tokens->end()) return false;
			*iter1 = iter2;
			return true;
		}

};