#include "Commands.h" 

namespace Engine {

static Commands *CommandsInstance = NULL;

void trim_command_str (std::string &command_str) {
	std::string::size_type start = command_str.find_first_not_of (" \t");
	if (start != std::string::npos)
		command_str = command_str.substr (start);

	std::string::size_type end = command_str.find_first_of ("#;\n\r\0");

	if (end != std::string::npos)
		command_str = command_str.substr (0, end);
}

/*
 * Inherited Module functions
 */
int Commands::OnInit (int argc, char* argv[]) {
	LogDebug ("Commands Init");
	mErrorString = "";
	if (CommandsInstance) {
		LogError ("Commands module already initialized!");
		return -1;
	}

	CommandsInstance = this;

	return 0;
}
void Commands::OnDestroy () {
	LogDebug ("Commands Destroy");

	if (CommandsInstance)
		CommandsInstance = NULL;
}

/*
 * Module specific functions
 */
std::string Commands::GetCommandName (const std::string &command) {
	return command.substr (0, command.find_first_of (" ;\n\0"));
}

std::vector<std::string> Commands::ParseArgs (std::string &argument_str) {
	std::vector<std::string> args;
	std::string::size_type pos = argument_str.find_first_not_of (" ");
	std::string::size_type next_token = 0;

	while ( argument_str.length ()> pos && argument_str.find_first_of (" \"", pos) != std::string::npos) {
		next_token = argument_str.find_first_of (" \"", pos);

		if (next_token != std::string::npos && argument_str[next_token] =='"') {
			pos = next_token;
			next_token = argument_str.find_first_of ('"', pos + 1);
			if (next_token != std::string::npos) {
				args.push_back (argument_str.substr (pos + 1, next_token - pos - 1));
			}
			pos = argument_str.find_first_not_of (" ", next_token + 1);
			
		} else {
			args.push_back (argument_str.substr (pos, next_token - pos));
			pos = argument_str.find_first_not_of (" ", next_token + 1);
		}
	}

	if (pos != std::string::npos)
		args.push_back (argument_str.substr (pos, argument_str.length ()));

	return args;
}

void Commands::AddCommand (const std::string &name, command_cb callback){
	LogDebug ("Adding Command '%s' at %x", name.c_str(), (void *) callback);

	mCommandsCallbacks[name] = callback;

	return;
}

bool Commands::RunCommand (const std::string &command){
	LogDebug ("Running Command: %s", command.c_str());

	std::string cmd_name = GetCommandName (command);
	std::string args;

	if (command.length () > cmd_name.length () + 1)
		args = command.substr (cmd_name.length () + 1, command.length ());

	if (mCommandsCallbacks.find (cmd_name) != mCommandsCallbacks.end()) {
		std::vector<std::string> argv = ParseArgs (args);
		if (mCommandsCallbacks[cmd_name] (argv)) {
			mErrorString = "";
			return true;
		} else {
				return false;
		}
	} else {
		LogWarning ("Command '%s' not registered!", cmd_name.c_str ());
	}

	SetErrorString ("Command '" + cmd_name + "' does not exist!");
	return false;
}

void Commands::QueueCommand (const std::string &command){
	mCommandQueue.push (command);
}

bool Commands::QueueExecute (){
	bool result = true;

	while (mCommandQueue.size() > 0) {
		result = RunCommand (mCommandQueue.front());
		if (!result) {
			while (!mCommandQueue.empty())
				mCommandQueue.pop();
			
			return false;
		}
		mCommandQueue.pop();
	}

	return true;
}

void Commands::SetErrorString (const std::string &error_str){
	mErrorString = error_str;
	LogWarning ("Command Error: %s", error_str.c_str ());
}

std::string Commands::GetErrorString (){
	return mErrorString;
}


/*
 * Global functions
 */
bool CommandsInitialized () {
	if (!CommandsInstance) {
		LogError ("Commands System not yet initialized!");
		return false;
	}

	return true;
}

void AddCommand (const std::string &name, command_cb callback){
	if (!CommandsInitialized ())
		return;

	CommandsInstance->AddCommand (name, callback);
}

bool RunCommand (const std::string &command){
	if (!CommandsInitialized ())
		return false;

	return CommandsInstance->RunCommand (command);
}

void QueueCommand (const std::string &command){
	if (!CommandsInitialized ())
		return;

	CommandsInstance->QueueCommand (command);
}

bool CommandQueueExecute (){
	if (!CommandsInitialized ())
		return false;

	return CommandsInstance->QueueExecute ();
}

void CommandSetErrorString (const std::string &error_str){
	if (!CommandsInitialized ())
		return;

	CommandsInstance->SetErrorString (error_str);
}

std::string CommandGetErrorString (){
	if (!CommandsInitialized ())
		return false;

	return CommandsInstance->GetErrorString();
}

/*
 * Commands of the Command system
 */
bool Cmd_Exec (const std::vector<std::string> args) {
	if (!CommandsInitialized())
		return false;

	if (args.size() != 1) {
		CommandsInstance->SetErrorString("usage: exec <path_to_file>");
		return false;
	}

	std::ifstream exec_file;
	exec_file.open(args[0].c_str(), std::ios_base::in);

	if (!exec_file) {
		std::ostringstream error_msg;
		error_msg << "exec failed: could not open file '"
			<< args[0] << "'";
		CommandsInstance->SetErrorString(error_msg.str());
		return false;
	}

	int linecounter = 0;
	while (!exec_file.eof()) {
		std::string line;
		getline (exec_file, line);
		linecounter++;

		trim_command_str (line);

		if (line.size() == 0)
			continue;

		if (!CommandsInstance->RunCommand (line)) {
			std::ostringstream error_msg;
			error_msg << "exec failed running command '" << line
				<< "' in file " << args[0] << ":" << linecounter << ".";
			CommandsInstance->SetErrorString (error_msg.str());
			return false;
		}
	}

	exec_file.close();
	return true;
}

void Commands::OnRegisterCommands () {
	AddCommand ("exec", Cmd_Exec);
}

}

