#include <iostream>
#include <string>
#include <string.h> // strstr()
#include <assert.h>
#include <math.h>
#include <angelscript.h>
#include "../../../add_on/scriptstdstring/scriptstdstring.h"
#include "../../../add_on/scripthelper/scripthelper.h"
#include "../../../add_on/scriptarray/scriptarray.h"

using namespace std;

// Function prototypes
void PrintHelp();
void ExecString(asIScriptEngine *engine, string &arg);
void AddVariable(asIScriptEngine *engine, string &arg);
void DeleteVariable(asIScriptEngine *engine, string &arg);
void AddFunction(asIScriptEngine *engine, string &arg);
void DeleteFunction(asIScriptEngine *engine, string &arg);
void ListVariables(asIScriptEngine *engine);
void ListFunctions(asIScriptEngine *engine);
void ConfigureEngine(asIScriptEngine *engine);
void print(const string &s);
void grab(int);
void grab(asUINT);
void grab(bool);
void grab(float);
void grab(double);
void grab(const string&);
void grab(void);

// Some global variables that the script can access
float          g_gravity;
asUINT         p_health;
asUINT         r_fov;
bool           r_shadow;
string         p_name = "player";

int main(int argc, char **argv)
{
	// Create the script engine
	asIScriptEngine *engine = asCreateScriptEngine();
	if( engine == 0 )
	{
		cout << "Failed to create script engine." << endl;
		return -1;
	}

	// Configure the script engine with all the functions,
	// and variables that the script should be able to use.
	ConfigureEngine(engine);

	// Print some useful information and start the input loop
	cout << "Sample console using AngelScript " << asGetLibraryVersion() << " to perform scripted tasks." << endl;
	cout << "Type 'help' for more information." << endl;

	// TODO: Allow multiple lines per command by ending each line with a backslash.

	for(;;)
	{
		string input;
		input.resize(256);
		string cmd, arg;

		cout << "> ";
		cin.getline(&input[0], 256);

		// Trim unused characters
		input.resize(strlen(input.c_str()));

		size_t pos;
		if( (pos = input.find(" ")) != string::npos )
		{
			cmd = input.substr(0, pos);
			arg = input.substr(pos+1);
		}
		else
		{
			cmd = input;
			arg = "";
		}

		// Interpret the command
		if( cmd == "exec" )
			ExecString(engine, arg);
		else if( cmd == "addfunc" )
			AddFunction(engine, arg);
		else if( cmd == "delfunc" )
			DeleteFunction(engine, arg);
		else if( cmd == "addvar" )
			AddVariable(engine, arg);
		else if( cmd == "delvar" )
			DeleteVariable(engine, arg);
		else if( cmd == "help" )
			PrintHelp();
		else if( cmd == "listfuncs" )
			ListFunctions(engine);
		else if( cmd == "listvars" )
			ListVariables(engine); 
		else if( cmd == "quit" )
			break;
		else
			cout << "Unknown command." << endl;
	}

	// Shut down the engine
	engine->ShutDownAndRelease();

	return 0;
}

void PrintHelp()
{
	cout << "Commands:" << endl;
	cout << " addfunc [decl] - adds a user function" << endl;
	cout << " addvar [decl]  - adds a user variable" << endl; 
	cout << " delfunc [name] - removes a user function" << endl;
	cout << " delvar [name]  - removes a user variable" << endl;
	cout << " exec [script]  - executes script statement and prints the result" << endl;
	cout << " help           - this command" << endl;
	cout << " listfuncs      - list functions" << endl;
	cout << " listvars       - list variables" << endl;
	cout << " quit           - end application" << endl;
}

void print(const string &s)
{
	cout << s << endl;
}

void MessageCallback(const asSMessageInfo *msg, void *param)
{
	const char *type = "ERR ";
	if( msg->type == asMSGTYPE_WARNING )
		type = "WARN";
	else if( msg->type == asMSGTYPE_INFORMATION )
		type = "INFO";

	cout << msg->section << " (" << msg->row << ", " << msg->col << ") : " << type << " : " << msg->message << endl;
}

void ConfigureEngine(asIScriptEngine *engine)
{
	int r;

	// Tell the engine to output any error messages to printf
	engine->SetMessageCallback(asFUNCTION(MessageCallback), 0, asCALL_CDECL);

	// Register the script string type
	// Look at the implementation for this function for more information
	// on how to register a custom string type, and other object types.
	RegisterStdString(engine);

	// Register the script array type
	RegisterScriptArray(engine, false);

	// Register the global variables
	r = engine->RegisterGlobalProperty("float g_gravity", &g_gravity); assert( r >= 0 );
	r = engine->RegisterGlobalProperty("uint p_health", &p_health);    assert( r >= 0 );
	r = engine->RegisterGlobalProperty("uint r_fov", &r_fov);          assert( r >= 0 );
	r = engine->RegisterGlobalProperty("bool r_shadow", &r_shadow);    assert( r >= 0 );
	r = engine->RegisterGlobalProperty("string p_name", &p_name);      assert( r >= 0 );

	// Register some useful functions
	r = engine->RegisterGlobalFunction("float sin(float)", asFUNCTION(sinf), asCALL_CDECL); assert( r >= 0 );
	r = engine->RegisterGlobalFunction("float cos(float)", asFUNCTION(cosf), asCALL_CDECL); assert( r >= 0 );
	r = engine->RegisterGlobalFunction("void print(const string &in)", asFUNCTION(print), asCALL_CDECL); assert( r >= 0 );

	// Register special function with overloads to catch any type.
	// This is used by the exec command to output the resulting value from the statement.
	r = engine->RegisterGlobalFunction("void _grab(bool)", asFUNCTIONPR(grab, (bool), void), asCALL_CDECL); assert( r >= 0 );
	r = engine->RegisterGlobalFunction("void _grab(int)", asFUNCTIONPR(grab, (int), void), asCALL_CDECL); assert( r >= 0 );
	r = engine->RegisterGlobalFunction("void _grab(uint)", asFUNCTIONPR(grab, (asUINT), void), asCALL_CDECL); assert( r >= 0 );
	r = engine->RegisterGlobalFunction("void _grab(float)", asFUNCTIONPR(grab, (float), void), asCALL_CDECL); assert( r >= 0 );
	r = engine->RegisterGlobalFunction("void _grab(double)", asFUNCTIONPR(grab, (double), void), asCALL_CDECL); assert( r >= 0 );
	r = engine->RegisterGlobalFunction("void _grab()", asFUNCTIONPR(grab, (void), void), asCALL_CDECL); assert( r >= 0 );
	r = engine->RegisterGlobalFunction("void _grab(const string &in)", asFUNCTIONPR(grab, (const string&), void), asCALL_CDECL); assert( r >= 0 );

	// Do not output anything else to printf
	engine->ClearMessageCallback();
}

void ExecString(asIScriptEngine *engine, string &arg)
{
	string script;

	// Wrap the expression in with a call to _grab, which allow us to print the resulting value
	script = "_grab(" + arg + ")";

	// TODO: Add a time out to the script, so that never ending scripts doesn't freeze the application

	int r = ExecuteString(engine, script.c_str(), engine->GetModule("console"));
	if( r < 0 )
		cout << "Invalid script statement. " << endl;
	else if( r == asEXECUTION_EXCEPTION )
		cout << "A script exception was raised." << endl;
}

void AddVariable(asIScriptEngine *engine, string &arg)
{
	asIScriptModule *mod = engine->GetModule("console", asGM_CREATE_IF_NOT_EXISTS);

	// Add a semi-colon to end the statement (if not already there)
	if( arg.length() > 0 && arg[arg.length()-1] != ';' )
		arg += ";";

	int r = mod->CompileGlobalVar("addvar", arg.c_str(), 0);
	if( r < 0 )
	{
		// TODO: Add better description of error (invalid declaration, name conflict, etc)
		cout << "Failed to add variable. " << endl;
	}
	else
		cout << "Variable added. " << endl;
}

void DeleteVariable(asIScriptEngine *engine, string &arg)
{
	asIScriptModule *mod = engine->GetModule("console");
	if( mod == 0 || mod->GetGlobalVarCount() == 0 ) 
	{
		cout << "No variables have been added. " << endl;
		return;
	}

	// trim the string to find the variable name
	size_t p1 = arg.find_first_not_of(" \n\r\t");
	if( p1 != string::npos )
		arg = arg.substr(p1, -1);
	size_t p2 = arg.find_last_not_of(" \n\r\t");
	if( p2 != string::npos )
		arg = arg.substr(0, p2+1);

	int index = mod->GetGlobalVarIndexByName(arg.c_str());
	if( index >= 0 )
	{
		mod->RemoveGlobalVar(index);
		cout << "Variable removed. " << endl;
	}
	else
		cout << "No such variable. " << endl;
}

void AddFunction(asIScriptEngine *engine, string &arg)
{
	asIScriptModule *mod = engine->GetModule("console", asGM_CREATE_IF_NOT_EXISTS);

	asIScriptFunction *func = 0;
	int r = mod->CompileFunction("addfunc", arg.c_str(), 0, asCOMP_ADD_TO_MODULE, &func);
	if( r < 0 )
	{
		// TODO: Add better description of error (invalid declaration, name conflict, etc)
		cout << "Failed to add function. " << endl;
	}
	else
	{
		// The script engine supports function overloads, but to simplify the 
		// console we'll disallow multiple functions with the same name.
		// We know the function was added, so if GetFunctionByName() fails it is
		// because there already was another function with the same name.
		if( mod->GetFunctionByName(func->GetName()) == 0 )
		{
			mod->RemoveFunction(func);
			cout << "Another function with that name already exists." << endl;
		}
		else
			cout << "Function added. " << endl;
	}

	// We must release the function object
	if( func )
		func->Release();
}

void DeleteFunction(asIScriptEngine *engine, string &arg)
{
	asIScriptModule *mod = engine->GetModule("console");
	if( mod == 0 || mod->GetFunctionCount() == 0 ) 
	{
		cout << "No functions have been added. " << endl;
		return;
	}

	// trim the string to find the variable name
	size_t p1 = arg.find_first_not_of(" \n\r\t");
	if( p1 != string::npos )
		arg = arg.substr(p1, -1);
	size_t p2 = arg.find_last_not_of(" \n\r\t");
	if( p2 != string::npos )
		arg = arg.substr(0, p2+1);

	asIScriptFunction *func = mod->GetFunctionByName(arg.c_str());
	if( func )
	{
		mod->RemoveFunction(func);
		cout << "Function removed. " << endl;
	}
	else
		cout << "No such function. " << endl;

	// Since functions can be recursive, we'll call the garbage
	// collector to make sure the object is really freed
	engine->GarbageCollect();
}

void ListVariables(asIScriptEngine *engine)
{
	asUINT n;

	// List the application registered variables
	cout << "Application variables:" << endl;
	for( n = 0; n < (asUINT)engine->GetGlobalPropertyCount(); n++ )
	{
		const char *name;
		int typeId;
		bool isConst;
		engine->GetGlobalPropertyByIndex(n, &name, 0, &typeId, &isConst);
		string decl = isConst ? " const " : " ";
		decl += engine->GetTypeDeclaration(typeId);
		decl += " ";
		decl += name;
		cout << decl << endl;
	}

	// List the user variables in the module
	asIScriptModule *mod = engine->GetModule("console");
	if( mod )
	{
		cout << endl;
		cout << "User variables:" << endl;
		for( n = 0; n < (asUINT)mod->GetGlobalVarCount(); n++ )
		{
			cout << " " << mod->GetGlobalVarDeclaration(n) << endl;
		}
	}
}

void ListFunctions(asIScriptEngine *engine)
{
	asUINT n;
	
	// List the application registered functions
	cout << "Application functions:" << endl;
	for( n = 0; n < (asUINT)engine->GetGlobalFunctionCount(); n++ )
	{
		asIScriptFunction *func = engine->GetGlobalFunctionByIndex(n);

		// Skip the functions that start with _ as these are not meant to be called explicitly by the user
		if( func->GetName()[0] != '_' )
			cout << " " << func->GetDeclaration() << endl;
	}

	// List the user functions in the module
	asIScriptModule *mod = engine->GetModule("console");
	if( mod )
	{
		cout << endl;
		cout << "User functions:" << endl;
		for( n = 0; n < (asUINT)mod->GetFunctionCount(); n++ )
		{
			asIScriptFunction *func = mod->GetFunctionByIndex(n);
			cout << " " << func->GetDeclaration() << endl;
		}
	}
}

void grab(int v)
{
	cout << v << endl;
}

void grab(asUINT v)
{
	cout << v << endl;
}

void grab(bool v)
{
	cout << boolalpha << v << endl;
}

void grab(float v)
{
	cout << v << endl;
}

void grab(double v)
{
	cout << v << endl;
}

void grab(const string &v)
{
	cout << v << endl;
}

void grab()
{
	// There is no value
}
