#include <fstream>
#include <iostream>
#include <string>
#include <math.h>
#include "Token.h"
#include "Lexer.h"
#include "Parser.h"
#include "Tests/TestLexer.h"
#include "ContextManager.h"
#include "ASTInspector.h"
#include "Tests/tests.h"
#include <unistd.h>

using namespace std;


/*TODO
 * Known bugs:
 *
 * >> print("I like bananas", dupa");
I like bananasnull
Context: 0: dupa"); type: null
null

dupa"); declaration? hello? It's probably temporary effect of searching for this name in variable tables

>>
 */


SenchaObject sleep(vector<ASTExpression *> arguments)
{
	SenchaObject argument = arguments[0]->evaluate();
	if(argument.type == SenchaObject::integer_number && argument.integer >= 0)
	{
		cout << "valid argument for sleep: " << argument.integer << endl;
		usleep(1000 * argument.integer);
		return SenchaObject();
	}
	else
	{
		cout << "invalid argument for sleep";
		argument.type = SenchaObject::invalid;
		return argument;
	}

}
SenchaObject print(vector<ASTExpression *> arguments)
{
		for (auto argument: arguments)
		{
			auto value = argument->evaluate();

			std::cout  << value.str();

		}
		return SenchaObject();
}

SenchaObject println(vector<ASTExpression *> arguments)
{
		for (auto argument: arguments)
		{
			auto value = argument->evaluate();

			std::cout  << value.str();

		}
		std::cout << std::endl;
		return SenchaObject();
}

SenchaObject s_sin(vector<ASTExpression *> arguments)
{
	SenchaObject result;
	SenchaObject sin_argument = arguments[0]->evaluate();
	if(sin_argument.type == SenchaObject::integer_number)
	{
		result = SenchaObject(sin(sin_argument.integer));
	}
	else if(sin_argument.type == SenchaObject::float_number)
	{
		result = SenchaObject(sin(sin_argument.number));
	}
	else
	{
		result.type = SenchaObject::invalid;
	}

	return result;
}

SenchaObject s_cos(vector<ASTExpression *> arguments)
{
	SenchaObject result;
	SenchaObject cos_argument = arguments[0]->evaluate();
	if(cos_argument.type == SenchaObject::integer_number)
	{
		result = SenchaObject(cos(cos_argument.integer));
	}
	else if(cos_argument.type == SenchaObject::float_number)
	{
		result = SenchaObject(cos(cos_argument.number));
	}
	else
	{
		result.type = SenchaObject::invalid;
	}

	return result;
}

SenchaObject s_tan(vector<ASTExpression *> arguments)
{
	SenchaObject result;
	SenchaObject tan_argument = arguments[0]->evaluate();
	if(tan_argument.type == SenchaObject::integer_number)
	{
		result = SenchaObject(tan(tan_argument.integer));
	}
	else if(tan_argument.type == SenchaObject::float_number)
	{
		result = SenchaObject(tan(tan_argument.number));
	}
	else
	{
		result.type = SenchaObject::invalid;
	}

	return result;
}

SenchaObject len(vector<ASTExpression *> arguments)
{
	SenchaObject result;
	SenchaObject argument = arguments[0]->evaluate();
	if(argument.type == SenchaObject::string_literal)
	{
		result = SenchaObject(int(argument.text.size()));
	}
	return result;
}


int how_depth_change(vector<Token> tokens)
{
	int change = 0;
	for (unsigned int i = 0; i < tokens.size(); i++)
	{
		if(tokens[i].value == "{") change++;
		else if(tokens[i].value == "}") change--;
	}
	return change;
}

string compute_indentation(int depth_level)
{
	string indent = "";
	for(int i=0; i< depth_level; i++)
	{
		indent += "    ";
	}
	return indent;
}

void interactive()
{
	Lexer lexer;

	ContextManager context_manager;
	context_manager.context("global")->register_function("print", print);
	context_manager.context("global")->register_function("println", println);
	context_manager.context("global")->register_function("sin", s_sin);
	context_manager.context("global")->register_function("cos", s_cos);
	context_manager.context("global")->register_function("tan", s_tan);
	context_manager.context("global")->register_function("sleep", sleep);
	context_manager.context("global")->register_function("len", len);

	Parser parser(&context_manager);
	ASTInspector inspector;
	vector<Token> tokens;

	string input;
	int level_of_depth = 0;
	string indentation = "";
	while(true)
	{
		cout << ">> " + indentation;
		getline(cin, input);
		if(input == "quit()") break;
		tokens = lexer.parse_line(input);

		level_of_depth += how_depth_change(tokens);
		indentation = compute_indentation(level_of_depth);

		parser.add_tokens(tokens);

		if(level_of_depth == 0) {
			parser.interpret();
			parser.program->execute_last();
			inspector.visit(parser.tree.root);
			cout << context_manager.get_top()->debug() << endl;
			//cout << parser.show_tokens();
			cout << inspector.get_report();
			inspector.forget_everything();
		}
	}
}

int main(int argc, char *argv[])
{
	if(argc <= 1)
	{
		cout << "Sencha-lang interpreter, version 0.12" << endl;
		interactive();
	}
	else
	{
		std::string argument1 = argv[1];
		if(argument1 == "--test") {

			cout << "I'm running tests" << endl;
			run_tests();
		}
		else {
			auto name = argument1;
			Lexer lexer;
			ContextManager context_manager;
			context_manager.context("global")->register_function("print", print);
			context_manager.context("global")->register_function("println", println);
			context_manager.context("global")->register_function("sin", s_sin);
			context_manager.context("global")->register_function("cos", s_cos);
			context_manager.context("global")->register_function("tan", s_tan);
			context_manager.context("global")->register_function("sleep", sleep);
			context_manager.context("global")->register_function("len", len);

			Parser parser(&context_manager);

			vector<Token> tokens;
			string line;
			ifstream input_file (name);

			if (input_file.is_open())
			{
				while ( input_file.good() )
				{
					getline (input_file,line);
					tokens = lexer.parse_line(line);
					parser.add_tokens(tokens);
				}
				input_file.close();
			}
			ASTInspector inspector;
			parser.interpret();
			inspector.visit(parser.tree.root);
			parser.program->execute();
			//cout << inspector.get_report()<< endl;
		}
	}

	return 0;
}
