#include "simgrid_simulation.h"

#include "script_api.h"
#include "settings.h"

#include "../common/python_utils.h"

//#include <stdlib.h>
//#include <stdio.h>
//#include <io.h>
//#include <fcntl.h>
//#include <process.h>
//#include <math.h>

extern "C" void init_sg_script_api(void);
PyObject *new_PySimgridSimulationInterface(SimgridSimulationInterface *simgridSim, int ownFlags);
extern "C" PyObject *_wrap_new_SimgridSimulationInterface(PyObject *self, PyObject *args);

SimgridSimulation::SimgridSimulation(GridDesigner *designer, SimgridSimulationInterface* sgInt)
{
	_designer = designer;
	_sgInt = sgInt;
	_sgguitoolsMod = NULL;
	_simOffset = 0;
	_guiInterface = NULL;

	Py_Initialize();
	PyEval_InitThreads();

	// save a pointer to the main PyThreadState object
	_mainThread = PyThreadState_Get();

	PyRun_SimpleString("import sys\n");
	PyRun_SimpleString("sys.path.append('.')\n");

	_init = false;
	_runningMod = NULL;

	init_sg_script_api();

	PyThreadState_Swap(NULL);
	PyEval_ReleaseLock();
}

SimgridSimulation::~SimgridSimulation()
{
	PyEval_AcquireLock();
	Py_XDECREF(_sgguitoolsMod);

	PyThreadState_Swap(_mainThread);
	Py_Finalize();

	if(_guiInterface) {
		delete _guiInterface;
		_guiInterface = NULL;
	}
}

bool SimgridSimulation::init()
{
	if(_init)
		return true;
	
	PyObject *sguiName = NULL;

	PyEval_AcquireLock();
	PyThreadState_Swap(_mainThread);

	// in this function all the PyErr_Print can be seen only if the console is displayed. After this function is called
	// prints and errors in python are thrown as signals.
	bool ret = false;
	while(1) {
		sguiName = PyString_FromString("sgui-tools");
		if(sguiName == NULL) {
			emitScriptError(tr("Error creating module sgui-tools.py\n"));
			PyErr_Print();
			break;
		}
		
		_sgguitoolsMod = PyImport_Import(sguiName);
		if(_sgguitoolsMod == NULL) {
			emitScriptError(tr("Error importing module sgui-tools.py\n"));
			PyErr_Print();
			break;
		}

		// borrowed reference
		PyObject *mdict = PyModule_GetDict(_sgguitoolsMod);
		if(mdict == NULL) {
			emitScriptError(tr("Error getting dictionary\n"));
			PyErr_Print();
			break;
		}

		// borrowed reference
		PyObject *redirectFnc = PyDict_GetItemString(mdict, "redirect");
		if(redirectFnc == NULL) {
			emitScriptError(tr("Cannot get redirect function\n"));
			PyErr_Print();
			break;
		}

		_guiInterface = SimgridSimulationInterface::instance();
		// set the instance of designer that the script will talk with
		_guiInterface->setInnerInstance(_sgInt);
		//_guiInterface->setInnerInstance(reinterpret_cast<SimgridSimulationInterface*>(_designer));

		//_guiInterface = SimgridSimulationInterface::instance();

		//connect(_guiInterface, SIGNAL(scriptStdout(const QString &)), this, SIGNAL(scriptOutput(const QString &)), Qt::DirectConnection);
		//connect(_guiInterface, SIGNAL(scriptStderr(const QString &)), this, SIGNAL(scriptError(const QString &)), Qt::DirectConnection);
		//connect(_guiInterface, SIGNAL(messageSet(const QString &, const QString &)), this, SIGNAL(setComponentMessage(const QString &, const QString &)), Qt::DirectConnection);
		//connect(_guiInterface, SIGNAL(redLevelSet(const QString &, int)), this, SIGNAL(setComponentRedLevel(const QString &, int)), Qt::DirectConnection);
		//connect(_guiInterface, SIGNAL(redLevelInc(const QString &)), this, SIGNAL(incComponentRedLevel(const QString &)), Qt::DirectConnection);
		//connect(_guiInterface, SIGNAL(redLevelDec(const QString &)), this, SIGNAL(decComponentRedLevel(const QString &)), Qt::DirectConnection);

/*
		if(dup2(fileno(stdout), fileno(stderr)) != 0) {
			emit scriptError(QString("Cannot redirect stderr"));
			break;
		}

		fprintf(stdout, "TESTING TESTINGS\n");
*/
		PyObject *arg = new_PySimgridSimulationInterface(_guiInterface, 0);
		if(arg == NULL) {
			emitScriptError(tr("Cannot create SimgridSimulationInterface\n"));
			PyErr_Print();
			break;
		}

		PyObject *result = PyObject_CallFunctionObjArgs(redirectFnc, arg, NULL);
		Py_XDECREF(arg);
		if(result == NULL) {
			emitScriptError(tr("Error calling redirect function\n"));
			PyErr_Print();
			break;
		}

		Py_XDECREF(result);

		ret = true;
		break;
	}

	PyThreadState_Swap(NULL);
	PyEval_ReleaseLock();

	_init = ret;

	Py_XDECREF(sguiName);

	return ret;
}

void SimgridSimulation::start(const QString &scriptPath, const QString &platPath, const QString &deploymentPath)
{
	_scriptPath = scriptPath;
	_platPath = platPath;
	_deploymentPath = deploymentPath;

	QThread::start();
}

void SimgridSimulation::run()
{
	run(_scriptPath, _platPath, _deploymentPath);
}

bool SimgridSimulation::run(const QString &scriptPath, const QString &platPath, const QString &deploymentPath)
{
	PyObject *name = NULL;
	PyObject *platName = NULL;
	PyObject *deployName = NULL;
	PyObject *mainFnc;
	PyObject *result;
	int ret = -1;
	QString scriptModule;
	bool error = true;

	static int a = 0;

	PythonThread thread;

	//_simOffset = privateSimulationTime();

	if(scriptPath.isEmpty()) {
		emit simulationFinished(false, true, ret);
		return false;
	}

	_stopped = false;
	
	updateDebugMode();

	QString modName;
	QString modPath;

	int pos = scriptPath.indexOf(".py");
	if(pos != -1) {
		modName = scriptPath.mid(0, pos);
	}
	else
		modName = scriptPath;

	pos = modName.lastIndexOf('/');
	if(pos != -1) {
		modPath = modName.mid(0, pos);
		modName = modName.mid(pos+1);

		QString appendPath = QString("sys.path.append(\'%1\')\n").arg(modPath);

		PyRun_SimpleString(appendPath.toAscii());
	}

	while(1) {
		// borrowed reference
		PyObject *mdict = PyModule_GetDict(_sgguitoolsMod);
		if(mdict == NULL) {
			emitScriptError(tr("Error getting dictionary\n"));
			PyErr_Print();
			break;
		}

		// borrowed reference
		PyObject *interceptFnc = PyDict_GetItemString(mdict, "setInterceptor");
		if(interceptFnc == NULL) {
			emitScriptError(tr("Cannot get setInterceptor function\n"));
			PyErr_Print();
			break;
		}

		PyObject *cObj = PyCObject_FromVoidPtr((MsgInterceptor *) this, NULL);
		if(cObj == NULL) {
			emitScriptError(tr("Cannot create interceptor function object\n"));
			PyErr_Print();
			break;
		}

		result = PyObject_CallFunctionObjArgs(interceptFnc, cObj, NULL);
		Py_XDECREF(cObj);
		if(result == NULL) {
			emitScriptError(tr("Error calling interception function\n"));
			PyErr_Print();
			break;
		}

		Py_XDECREF(result);

		name = PyString_FromString(modName.toAscii());
		if(name == NULL) {
			emitScriptError(tr("Error creating module\n"));
			PyErr_Print();
			return false;
		}
		
		_runningMod = PyImport_Import(name);
		if(_runningMod == NULL) {
			emitScriptError(tr("Error importing module\n"));
			PyErr_Print();
			break;
		}

		// borrowed reference
		mdict = PyModule_GetDict(_runningMod);
		if(mdict == NULL) {
			emitScriptError(tr("Error getting dictionary\n"));
			PyErr_Print();
			break;
		}

		// borrowed reference
		mainFnc = PyDict_GetItemString(mdict, "main");
		if(mainFnc == NULL) {
			emitScriptError(tr("Cannot get main function\n"));
			PyErr_Print();
			break;
		}

		if(deploymentPath.isEmpty()) {
			result = PyObject_CallFunctionObjArgs(mainFnc, NULL);
		}
		else {
			platName = PyString_FromString(platPath.toAscii());
			if(platName == NULL) {
				emitScriptError(tr("Error creating platform file string\n"));
				PyErr_Print();
				break;
			}
			
			deployName = PyString_FromString(deploymentPath.toAscii());
			if(deployName == NULL) {
				emitScriptError(tr("Error creating deployment file string\n"));
				PyErr_Print();
				break;
			}
			
			//try {
				result = PyObject_CallFunctionObjArgs(mainFnc, platName, deployName, NULL);
			//}
			//catch(...) {
			//	
			//}
		}

		if(result == NULL) {
			PyErr_Print();
			emitScriptError(tr("Error calling main function\n"));
			break;
		}

		if(PyLong_Check(result)) {
			ret = PyInt_AsLong(result);

			QString exitMsg = QString("Main function returned %1\n").arg(ret);

			emitScriptOutput(exitMsg);
		}

		error = false;

		Py_XDECREF(result);

		break;
	}

	Py_XDECREF(name);
	Py_XDECREF(_runningMod);

	_runningMod = NULL;

	emit simulationFinished(_stopped, error, ret);

	return ret;
}

void SimgridSimulation::updateDebugMode()
{
	if(_init && _sgguitoolsMod != NULL) {
		PyObject *result = NULL;
		PyObject *pyDebug = NULL;
		while(1) {
			PyObject *mdict = PyModule_GetDict(_sgguitoolsMod);
			if(mdict == NULL) {
				emitScriptError(tr("Error getting dictionary\n"));
				PyErr_Print();
				break;
			}

			// borrowed reference
			PyObject *debugFnc = PyDict_GetItemString(mdict, "setDebugMode");
			if(debugFnc == NULL) {
				emitScriptError(tr("Cannot get setDebugMode function\n"));
				PyErr_Print();
				break;
			}

			pyDebug = PyBool_FromLong((int) _debugMode);
			if(pyDebug == NULL) {
				emitScriptError(tr("Error creating python argument for setDebugMode\n"));
				PyErr_Print();
				break;
			}
			
			PyObject *result = PyObject_CallFunctionObjArgs(debugFnc, pyDebug, NULL);
			if(result == NULL) {
				emitScriptError(tr("Error calling setDebugMode function\n"));
				PyErr_Print();
				break;
			}

			break;
		}
		
		Py_XDECREF(pyDebug);
		Py_XDECREF(result);
	}
}

double SimgridSimulation::privateSimulationTime()
{
	double ret = 0;

	if(_init && _sgguitoolsMod != NULL) {
		PyObject *result = NULL;
		while(1) {
			PyObject *mdict = PyModule_GetDict(_sgguitoolsMod);
			if(mdict == NULL) {
				emitScriptError(tr("Error getting dictionary\n"));
				PyErr_Print();
				break;
			}

			// borrowed reference
			PyObject *simTimeFnc = PyDict_GetItemString(mdict, "simulationTime");
			if(simTimeFnc == NULL) {
				emitScriptError(tr("Cannot get simulationTime function\n"));
				PyErr_Print();
				break;
			}

			PyObject *result = PyObject_CallFunctionObjArgs(simTimeFnc, NULL);
			if(result == NULL) {
				emitScriptError(tr("Error calling simulationTime function\n"));
				PyErr_Print();
				break;
			}

			if(PyFloat_Check(result)) {
				ret = PyFloat_AsDouble(result);
			}
			break;
		}

		Py_XDECREF(result);
	}

	return ret;
}

double SimgridSimulation::simulationTime()
{
	// from the main thread is necessary to lock the interpreter and switch
	PyEval_AcquireLock();
	PyThreadState_Swap(_mainThread);

	double simTime = privateSimulationTime();
	
	PyThreadState_Swap(NULL);
	PyEval_ReleaseLock();

	return (simTime - _simOffset);
}

/*
void SimgridSimulationStopper::run()
{
	if(_simgridSim->_init && _simgridSim->_sgguitoolsMod != NULL) {
		PyObject *result = NULL;
		PyThreadState *threadInfo = NULL;
		bool ok = false;

		PyEval_AcquireLock();
		threadInfo = PyThreadState_New(_simgridSim->_mainThread->interp);
		PyThreadState_Swap(threadInfo);

		while(1) {

			PyObject *mdict = PyModule_GetDict(_simgridSim->_sgguitoolsMod);
			if(mdict == NULL) {
				emitScriptError(tr("Error getting dictionary\n"));
				PyErr_Print();
				break;
			}

			// borrowed reference
			PyObject *simTimeFnc = PyDict_GetItemString(mdict, "stopSimulation");
			if(simTimeFnc == NULL) {
				emitScriptError(tr("Cannot get stopSimulation function\n"));
				PyErr_Print();
				break;
			}

			PyObject *result = PyObject_CallFunctionObjArgs(simTimeFnc, NULL);
			if(result == NULL) {
				emitScriptError(tr("Error calling simulationTime function\n"));
				PyErr_Print();
				break;
			}
			ok = true;

			break;
		}

		Py_XDECREF(result);	

		PyThreadState_Swap(NULL);

		PyThreadState_Clear(threadInfo);
		PyThreadState_Delete(threadInfo);
		PyEval_ReleaseLock();

		if(ok)
			emit stopped();
	}

}
*/

/*
void SimgridSimulation::stopped()
{
	emit simulationFinished(false);
}
*/

void SimgridSimulation::stopSimulation()
{
	if(_init && _runningMod != NULL) {
		while(1) {
			PyEval_AcquireLock();
			PyThreadState_Swap(_mainThread);

			// borrowed reference
			PyObject *mdict = PyModule_GetDict(_runningMod);
			if(mdict == NULL) {
				emitScriptError(tr("Error getting dictionary\n"));
				PyErr_Print();
				break;
			}

			// borrowed reference
			PyObject *stopFnc = PyDict_GetItemString(mdict, "stop");
			if(stopFnc == NULL) {
				break;
			}

			PyObject *result = PyObject_CallFunctionObjArgs(stopFnc, NULL);
			Py_XDECREF(result);

			PyThreadState_Swap(NULL);

			PyEval_ReleaseLock();

			_stopped = true;
	
			break;
		}
	}
}

QString SimgridSimulation::errorString(SGError error)
{
	QString ret;
	switch(error) {
		case SG_OK:
		{
			ret = tr("Ok");
			break;
		}
		case SG_WARNING:
		{
			ret = tr("Almost Ok");
			break;
		}
		case SG_TRANSFER_FAILURE:
		{
			ret = tr("Transfer Failure");
			break;
		}
		case SG_HOST_FAILURE:
		{
			ret = tr("Host Failure");
			break;
		}
		case SG_TASK_CANCELLED:
		{
			ret = tr("Task Cancelled");
			break;
		}
		case SG_FATAL:
		{
			ret = tr("Fatal Error");
			break;
		}
	}
	return ret;
}

void SimgridSimulation::emitScriptOutput(const QString &scriptMsg)
{
	PythonAutoLock pyLock;
	emit scriptOutput(scriptMsg);
}

void SimgridSimulation::emitScriptError(const QString &scriptMsg)
{
	PythonAutoLock pyLock;
	emit scriptError(scriptMsg);
}

void SimgridSimulation::emitSimulationFinished(bool stopped, bool error, int retCode)
{
	PythonAutoLock pyLock;
	emit simulationFinished(stopped, error, retCode);
}

void SimgridSimulation::onTaskPut(const char *who, const char *dest, const char *task, int channel)
{
	emit taskPut(who, dest, task, channel);
}

void SimgridSimulation::onTaskExecuteStart(const char *host, const char *procName, int procId, const char *task)
{
	emit taskExecuteStart(host, procName, procId, task);
}

void SimgridSimulation::onTaskExecuteFinish(const char *host, const char *procName, int procId, const char *task, SGError error)
{
	const QString &errorStr = errorString(error);
	emit taskExecuteFinish(host, procName, procId, task, errorStr);
}

void SimgridSimulation::onProcessCreate(const char *whoHost, const char *fncName, const char *destHost)
{
	emit processCreate(whoHost, fncName, destHost);
}

void SimgridSimulation::onProcessStart(const char *fncName, const char *host, const char *procName, int procId)
{
	emit processStart(fncName, host, procName, procId);
}

void SimgridSimulation::onProcessTerminate(const char *fncName, const char *host, const char *procName, int procId, int retCode)
{
	emit processTerminate(fncName, host, procName, procId, retCode);
}

void SimgridSimulation::onProcessSuspend(const char *host, const char *procName, int procId)
{
	emit processSuspend(host, procName, procId);
}

void SimgridSimulation::onProcessResume(const char *host, const char *procName, int procId)
{
	emit processResume(host, procName, procId);
}

void SimgridSimulation::onFunctionEntry(const char *fncName)
{
	emit functionEntry(fncName);
}

void SimgridSimulation::onFunctionExit(const char *fncName)
{
	emit functionExit(fncName);
}
