// mudc.cpp
// ````````

// System headers
#include <Windows.h>
#include <DbgHelp.h>
#include <ctime>

// Third-party headers
#include "../common/TinyJS.h"

// First-party headers
#include "TinyJS_Functions2.h"
#include "../common/PluginAPI.h"
#include "../common/mUtil.h" // map, string, vector
#include "mConsole.h"

// Header
#define MUDC_PLUGIN_DIR "plugins\\"
#define MUDC_VERSION "1.3"
#define MUDC_HEADER "muDC " MUDC_VERSION " (mappy 2011)\n"

// Types
typedef std::string ss;

struct mudc_plugin {
	HMODULE hPlugin;
	plugin_callback onLoad, onTick, onExit;
};

// mudc class
class mudc {
private:

	// Variables
	mConsole*	con;
	CTinyJS*	js;
	bool		bQuit, bResetJS;
	long		lastJsTick;
	std::vector<mudc_plugin> plugins;


public:

	// ################################################
	// Construction / deconstruction
	// ################################################
	
	mudc() {
		
		srand(rand());

		// Initial values
		bQuit = false;
		bResetJS = false;
		
		// Create console environment
		con = new mConsole();
		con->setPrompt(":");
		con->puts(MUDC_HEADER);
		
		// Display link time
		HMODULE hModule = GetModuleHandle(0);
		time_t linktime = GetTimestampForLoadedLibrary(hModule);
		char timeBuff[64] = {0};
		ctime_s(timeBuff, 64, &linktime);
		con->printf("Build date: %s\n", timeBuff);		
		
		// Create JS + plugin environment
		js = 0;
		resetJS();

	}
	
	// Main loop
	void mainLoop() {		
		while (! bQuit) {
			manageInputEvents();
			manageJSTickEvents();
			releaseTimeslice();

			if (bResetJS) {
				bResetJS = false;
				resetJS();
			}
		}
	}

	~mudc() {
		unloadPlugins();
		delete js;
		delete con;
	}

	// ################################################
	// Plugin management
	// ################################################

	void loadPluginByName(std::string pluginName) {
		int error_type = 0;
		mudc_plugin plugin;
		plugin.hPlugin = ::LoadLibraryA(pluginName.c_str());
		if (plugin.hPlugin) {
			plugin.onLoad = 
				(plugin_callback)::GetProcAddress(plugin.hPlugin, "plugin_onLoad");
			plugin.onTick = 
				(plugin_callback)::GetProcAddress(plugin.hPlugin, "plugin_onTick");
			plugin.onExit = 
				(plugin_callback)::GetProcAddress(plugin.hPlugin, "plugin_onExit");

			if (plugin.onLoad && plugin.onTick && plugin.onExit) {
				// It seems to be a valid plugin, so let's initialise it.
				plugin.onLoad(this->js);
				plugins.push_back(plugin);
				con->printf(
					"%s Loaded plugin \"%s\"\n",
					timestamp().c_str(), pluginName.c_str()
				);
			} else {
				// The .dll doesn't have what we're looking for.
				FreeLibrary(plugin.hPlugin);
				error_type = 2;
			}
		} else {
			error_type = 1;			
		}
		
		if (error_type) {
			con->printf(
				"%s Invalid plugin \"%s\" (error #%d)\n",
				timestamp().c_str(), pluginName.c_str(), error_type
			);
		}
	}

	void loadPlugins() {
		if (plugins.size()) return; // Must unload plugins before loading them.

		WIN32_FIND_DATAA wfd; HANDLE hFiles;
		std::string searchPath, pluginDir = MUDC_PLUGIN_DIR; 

		// Get process directory (not cwd) to find the plugin directory

		char pathBuff[MAX_PATH] = {0};
		long nSize = GetModuleFileNameA(NULL, pathBuff, MAX_PATH-1);
		if (nSize) {
			pluginDir = pathBuff;
			size_t rpos = pluginDir.rfind("\\");
			if (rpos != pluginDir.npos) {
				pluginDir = pluginDir.substr(0, rpos + 1);
				pluginDir.append(MUDC_PLUGIN_DIR);
			} else {				
				pluginDir = MUDC_PLUGIN_DIR; // Weird. Fallback to cwd
			}
		}

		// First load all .dll files..

		searchPath = pluginDir + "*.dll";
		hFiles = FindFirstFileA(searchPath.c_str(), &wfd);
		if (hFiles != INVALID_HANDLE_VALUE) do {			
			loadPluginByName(pluginDir + wfd.cFileName);
		} while (FindNextFileA(hFiles, &wfd));
		
		// ..then load all .js files

		searchPath = pluginDir + "*.js";
		hFiles = FindFirstFileA(searchPath.c_str(), &wfd);
		if (hFiles != INVALID_HANDLE_VALUE) do {
			loadJSFile(pluginDir + wfd.cFileName);
		} while (FindNextFileA(hFiles, &wfd));
		
	}

	void tickPlugins() {
		if (! plugins.size()) return;

		for (
			std::vector<mudc_plugin>::iterator i = plugins.begin(), e = plugins.end();
			i != e; i++
		) {
			i->onTick(js);
		}
	}

	void unloadPlugins() {
		if (! plugins.size()) return; // Nothing to do

		for (
			std::vector<mudc_plugin>::iterator i = plugins.begin(), e = plugins.end();
			i != e; i++
		) {
			i->onExit(js);
			FreeLibrary(i->hPlugin);
		}
		plugins.clear();
	}

	// ################################################
	// Javascript helper functions
	// ################################################

	void resetJS() {
		if (js) {
			unloadPlugins();
			delete js;
		}
		js = new CTinyJS();
		
		registerFunctions(js);
		registerMoreFunctions(js);

		callJS("_onTick = [];");

		loadPlugins();

		// Add mudc* functions
		
		js->addNative("function mudc_console(text)",	&mudc::js_mudc_console,	this);
		js->addNative("function mudc_load(file)",		&mudc::js_mudc_load,	this);
		js->addNative("function mudc_reset()",			&mudc::js_mudc_reset,	this);
		js->addNative("function mudc_exit()",			&mudc::js_mudc_exit,	this);
	}	
	
	void loadJSFile(ss fileName) {
		std::string file_contents;
		if (file_get_contents(fileName, file_contents)) {
			callJS(file_contents);
			con->printf(
				"%s Loaded script \"%s\"\n",
				timestamp().c_str(), fileName.c_str()
			);
		} else {
			con->printf("%s Couldn't open \"%s\".\n",
				timestamp().c_str(),
				fileName.c_str()
			);
		}
	}

	void callJS(const char* script) {
		ss sScript(script);
		callJS(sScript);
	}

	void callJS(ss& script) {
		try {
			js->execute(script.c_str());
		} catch (CScriptException *ex) {
			con->printf("%s Script error: %s\n",
				timestamp().c_str(),
				ex->text.c_str()
			);
		}
	}

	// ################################################
	// Main loop functions
	// ################################################

	void manageJSTickEvents() {
		static long last_call = 0;
		long now = getUpTicks();
		if ((now - last_call) < 1000) return;

		callJsTick();
		tickPlugins();

		last_call = getUpTicks();
	}
		
	void callJsTick() {
		callJS("for(_jsti = 0; _jsti < _onTick.length; _jsti++) _onTick[_jsti]();");
	}

	void manageInputEvents() {
		if (! con->manageInput()) return;
			
		ss line; con->getLine(line);
		con->printf(":%s\n", line.c_str());
		
		if (line.length()) callJS(line); // Execute line
	}

	// ################################################
	// Javascript native functions
	// ################################################

	static void js_mudc_console(CScriptVar* v, void* data) {
		mudc* self = (mudc*)data;
		self->con->printf("%s %s\n",
			timestamp().c_str(),
			v->getParameter("text")->getString().c_str()
		);
	}
	
	static void js_mudc_reset(CScriptVar* v, void* data) {
		mudc* self = (mudc*)data;
		self->bResetJS = true; // Queue a reset
	}

	static void js_mudc_load(CScriptVar* v, void* data) {
		mudc* self = (mudc*)data;
		self->loadJSFile( v->getParameter("file")->getString() );
	}

	static void js_mudc_exit(CScriptVar* v, void* data) {
		mudc* self = (mudc*)data;
		self->bQuit = true;
	}

};

// ################################################
// Entry point
// ################################################

int main(int argc, char* argv[]) {

	mudc m = mudc();

	if (argc > 1) for (int i = 1; i < argc; i++) {
		m.loadJSFile(argv[i]);
	}

	m.mainLoop();

}