#pragma warning(disable: 4786)

#include "msg_python.h"

#include <assert.h>

#ifdef WIN32
#include <windows.h>
#endif // WIN32

/** \defgroup m_process_management Management Functions of Agents
*  \brief This section describes the agent structure of MSG
*  (#PyMsgProcess) and the functions for managing it.
*/

/** \defgroup m_task_management Managing functions of Tasks
*  \brief This section describes the task structure of MSG
*  (#PyMsgTask) and the functions for managing it.
*/

/** \defgroup m_channel_management    Understanding channels
*  \brief This section briefly describes the channel notion of MSG
*  (#m_channel_t).
*/

/** \defgroup m_host_management Management functions of Hosts
*  \brief This section describes the host structure of MSG
*/

/** \defgroup msg_gos_functions MSG Operating System Functions
*  \brief This section describes the functions that can be used
*  by an agent for handling some task.
*/

extern "C" {
	#undef _THROW
	#include "msg/msg.h"
	#include "xbt/sysdep.h"
	#define using _using
//	#include "msg/private.h"
};

PyMsgGlobal* PyMsgGlobal::_instance = NULL;
double PyMsgGlobal::_startClock = 0;

PyMsgGlobal::PyMsgGlobal()
{
	PythonMgr::instance()->setListener(this);
	_interceptor = NULL;
}

PyMsgGlobal::~PyMsgGlobal()
{
	AutoMutextLock lock(_fncMapLock);
	_fncMap.clear();

	MSG_clean();
}

PyMsgGlobal* PyMsgGlobal::instance()
{
	ENTER_FUNCTION(PyMsgGlobal::instance);

	if(_instance == NULL) {
		_instance = new PyMsgGlobal;
	}
	return _instance;
}

void PyMsgGlobal::destroy()
{
	ENTER_FUNCTION(PyMsgGlobal::destroy);

	if(_instance != NULL) {
		PythonMgr::instance()->destroyAllThreadInfos();
		PythonMgr::instance()->setListener(NULL);
		delete _instance;
		_instance = NULL;
	}
}

void PyMsgGlobal::setReportMode(ReportLevel mode)
{
	_repMode = mode;
	if(_repMode == _report_debug)
		PythonMgr::instance()->setDebugLevel(PythonMgr::_py_print_all);
	else
		PythonMgr::instance()->setDebugLevel(PythonMgr::_py_print_errors);
}

/** \ingroup msg_easier_life
* \brief A platform constructor.
*
* Creates a new platform, including hosts, links and the
* routing_table. 
*/
void PyMsgGlobal::createEnvironment(const char *file)
{
	ENTER_FUNCTION(PyMsgGlobal::createEnvironment);

	MSG_create_environment(file);
}

/** \brief set a configuration variable
* 
* Currently existing configuation variable:
*   - workstation_model (string): Model of workstation to use.  
*     Possible values (defaults to "KCCFLN05"):
*     - "CLM03": realistic TCP behavior + basic CPU model (see [CML03 at CCGrid03]) + support for parallel tasks
*     - "KCCFLN05": realistic TCP behavior + basic CPU model (see [CML03 at CCGrid03]) + failure handling + interference between communications and computations if precised in the platform file.
*     - "KCCFLN05": realistic TCP behavior + basic CPU model (see [CML03 at CCGrid03]) + failure handling + interference between communications and computations if precised in the platform file. Use maxmin for the network.
*     - "KCCFLN05_proportional": realistic TCP behavior + basic CPU model (see [CML03 at CCGrid03]) + failure handling + interference between communications and computations if precised in the platform file. Uses the proportional approahc as described in the Corine Touati's PhD Thesis.
*     - "KCCFLN05_Vegas": realistic TCP behavior + basic CPU model (see [CML03 at CCGrid03]) + failure handling + interference between communications and computations if precised in the platform file. Uses the fairness adapted to the TCP Vegas flow control.
*     - "KCCFLN05_Reno": realistic TCP behavior + basic CPU model (see [CML03 at CCGrid03]) + failure handling + interference between communications and computations if precised in the platform file. Uses the fairness adapted to the TCP Reno flow control.
* 
* Example:
* config("workstation_model","KCCFLN05");
*/
void PyMsgGlobal::config(const char *name, ...)
{
	ENTER_FUNCTION(PyMsgGlobal::config);

	va_list va;

	va_start(va, name);
	MSG_config(name, va);
}

void PyMsgGlobal::convertStrListToArgv(const StrList &args, int *argc, char ***argv)
{
	int i = 0;

	*argv = NULL;

	*argc = args.size();
	if(*argc > 0) {
		*argv = (char **) malloc(sizeof(char*) * (*argc));
		assert(*argv != NULL);

		for(StrList::const_iterator it = args.begin(); it != args.end(); it++) {
			char *arg = *it;

			*argv[i++] = arg;
		}
	}
}

/** \ingroup msg_simulation
* \brief Initialize some MSG internal data.
*/
void PyMsgGlobal::globalInit(const StrList &args)
{
	ENTER_FUNCTION(PyMsgGlobal::globalInit);

	int argc;
	char **argv = NULL;

	convertStrListToArgv(args, &argc, &argv);

	MSG_global_init(&argc, argv);

	if(argv)
		free(argv);
}

/** \ingroup msg_simulation
* \brief Initialize some MSG internal data.
*/
void PyMsgGlobal::globalInitArgs(const StrList &args)
{
	int argc;
	char **argv = NULL;

	convertStrListToArgv(args, &argc, &argv);

	MSG_global_init_args(&argc, argv);

	if(argv)
		free(argv);
}

/** \ingroup m_channel_management
* \brief Set the number of channel in the simulation.
*
* This function has to be called to fix the number of channel in the
simulation before creating any host. Indeed, each channel is
represented by a different mailbox on each #PyMsgHost. This
function can then be called only once. This function takes only one
parameter.
* \param number the number of channel in the simulation. It has to be >0
*/
MSG_error_t PyMsgGlobal::setChannelNumber(int number)
{
	ENTER_FUNCTION(PyMsgGlobal::setChannelNumber);

	return MSG_set_channel_number(number);
}

/** \ingroup m_channel_management
* \brief Return the number of channel in the simulation.
*
* This function has to be called once the number of channel is fixed. I can't 
figure out a reason why anyone would like to call this function but nevermind.
* \return the number of channel in the simulation.
*/
int PyMsgGlobal::getChannelNumber()
{
	ENTER_FUNCTION(PyMsgGlobal::getChannelNumber);

	return MSG_get_channel_number();
}

/** \ingroup msg_simulation
* \brief Launch the MSG simulation
*/
MSG_error_t PyMsgGlobal::main()
{
	ENTER_FUNCTION(PyMsgGlobal::main);

	return MSG_main();
}

/** \ingroup msg_simulation
* \brief Clean the MSG simulation
*/
MSG_error_t PyMsgGlobal::clean()
{
	ENTER_FUNCTION(PyMsgGlobal::clean);

	AutoMutextLock lock(_fncMapLock);
	_fncMap.clear();

	return MSG_clean();
}

/** \ingroup msg_easier_life
* \brief Registers the main function of an agent in a global table.
*
* Registers a code function in a global table. 
* This table is then used by #PyMsgGlobal::launchApplication. 
* \param name the reference name of the function.
* \param fnc the function (must receive a list of strings as parameter)
*/
void PyMsgGlobal::functionRegister(const char *name, PyObject *fnc)
{
	ENTER_FUNCTION(PyMsgGlobal::functionRegister);

	// register static function and add the entry in the map to call the function from the trampoline
	AutoMutextLock lock(_fncMapLock);

	_fncMap[name] = fnc;

	MSG_function_register(name, trampoline);
}

void PyMsgGlobal::processRegister(const char *name, PyObject *fnc)
{
	ENTER_FUNCTION(PyMsgGlobal::processRegister);

	AutoMutextLock lock(_fncMapLock);

	_fncMap[name] = fnc;
}

//m_process_code_t PyMsgGlobal::getRegisteredFunction(const char *name)
//{
//	return MSG_get_registered_function(name);
//}

/** \ingroup msg_easier_life
* \brief An application deployer.
*
* Creates the process described in \a file.
* \param file see surfxml.dtd in SimGrid project for more details of the format of this file.
*
*/
void PyMsgGlobal::launchApplication(const char *file)
{
	ENTER_FUNCTION(PyMsgGlobal::launchApplication);

	_startClock = MSG_get_clock();
	MSG_launch_application(file);
}

void PyMsgGlobal::setPajeOutput(const char *filename)
{
	ENTER_FUNCTION(PyMsgGlobal::setPajeOutput);

	MSG_paje_output(filename);
}

/** \ingroup msg_easier_life
* \brief A clock (in second).
*/
double PyMsgGlobal::getClock()
{
	ENTER_FUNCTION(PyMsgGlobal::getClock);

	return MSG_get_clock() - _startClock;
}

/** \ingroup msg_gos_functions
*
* \brief Return the last value returned by a MSG function (except
* PyMsgGlobal::getErrno ...).
*/
MSG_error_t PyMsgGlobal::getErrno()
{
	ENTER_FUNCTION(PyMsgGlobal::getErrno);

	return MSG_get_errno();
}

/** \ingroup msg_simulation
* \brief Kill all running process

* \param resetPIDs should we reset the PID numbers. A negative
*   number means no reset and a positive number will be used to set the PID
*   of the next newly created process.
*/
int PyMsgGlobal::killAllProcesses(int resetPIDs)
{
	ENTER_FUNCTION(PyMsgGlobal::killAllProcesses);

	int ret = MSG_process_killall(resetPIDs);

#ifdef WIN32
	bool killCurr = false;
	for(std::list<HANDLE>::iterator it = _threads.begin(); it != _threads.end(); it++) {
		HANDLE hThread = *it;

		if(hThread) {
			if(hThread != GetCurrentThread()) {
				TerminateThread(hThread, 0);
				CloseHandle(hThread);
			}
			else {
				killCurr = true;
			}
		}
	}
	if(killCurr)
		TerminateThread(GetCurrentThread(), 0);
#endif // WIN32

	return ret;
}

/** \ingroup m_process_management
* \brief Return the current agent.
*
* This functions returns the currently running #PyMsgProcess.
*/
PyMsgProcess *PyMsgGlobal::getCurrentProcess()
{
	ENTER_FUNCTION(PyMsgGlobal::getCurrentProcess);

	PyMsgProcess *ret = NULL;
	m_process_t proc_t;

	proc_t = MSG_process_self();
	
	if(proc_t != NULL) {
		ret = new PyMsgProcess(proc_t);
	}

	return ret;
}

PyMsgProcess *PyMsgGlobal::getCurrentProcessInternal()
{
	PyMsgProcess *ret = NULL;
	m_process_t proc_t;

	proc_t = MSG_process_self();
	
	if(proc_t != NULL) {
		ret = new PyMsgProcess(proc_t);
	}

	return ret;
}

/** \ingroup msg_gos_functions
* \brief Sleep for the specified number of seconds
*
* Makes the current process sleep until \a time seconds have elapsed.
*
* \param nb_sec a number of second
*/
MSG_error_t PyMsgGlobal::currentProcessSleep(double nb_sec)
{
	ENTER_FUNCTION(PyMsgGlobal::currentProcessSleep);

	return MSG_process_sleep(nb_sec);
}

/** \ingroup m_host_management
*
* \brief Return the location on which the current process is executed.
*/
PyMsgHost *PyMsgGlobal::getCurrentHost()
{
	ENTER_FUNCTION(PyMsgGlobal::getCurrentHost);

	m_host_t host_t = MSG_host_self();
	PyMsgHost *host = NULL;
	if(host_t)
		host = new PyMsgHost(host_t);
	return host;
}

PyMsgHost *PyMsgGlobal::getCurrentHostInternal()
{
	m_host_t host_t = MSG_host_self();
	PyMsgHost *host = NULL;
	if(host_t)
		host = new PyMsgHost(host_t);
	return host;
}

/** \ingroup msg_easier_life
* \brief A name directory service...
*
* Finds a PyMsgHost using its name.
* \param name the name of an host.
* \return the corresponding host
*/
PyMsgHost *PyMsgGlobal::getHostByName(const char *name)
{
	ENTER_FUNCTION(PyMsgGlobal::getHostByName);

	PyMsgHost *host = NULL;
	m_host_t host_t = MSG_get_host_by_name(name);
	if(host_t)
		host = new PyMsgHost(host_t);
	return host;
}

/** \ingroup m_process_management
*
* \brief Return a #PyMsgProcess given its PID.
*
* This functions search in the list of all the created PyMsgProcess for a PyMsgProcess 
whose PID is equal to \a PID. If no host is found, \c NULL is returned. 
Note that the PID are uniq in the whole simulation, not only on a given host.
*/
PyMsgProcess *PyMsgGlobal::processFromPID(int pid)
{
	ENTER_FUNCTION(PyMsgGlobal::processFromPID);

	PyMsgProcess *ret = NULL;
	m_process_t proc_t;

	proc_t = MSG_process_from_PID(pid);
	
	if(proc_t != NULL) {
		ret = new PyMsgProcess(proc_t);
	}

	return ret;
}

/** \ingroup msg_gos_functions
* \brief Listen on a channel and wait for receiving a task.
*
* It takes two parameters.
* \param channel the channel on which the agent should be
listening. This value has to be >=0 and < than the maximal
number of channels fixed with PyMsgGlobal::setChannelNumber().
* \param max_duration the maximum time to wait for a task before
giving up. In the case of a reception, *\a PID will be updated
with the PID of the first process to send a task.
* \return PyMsgTask object with next task for current process in the specified channel.
*/
PyMsgTask *PyMsgGlobal::getTaskFromCurrentHost(m_channel_t channel, double max_duration)
{
	ENTER_FUNCTION(PyMsgGlobal::getTaskFromCurrentHost);

	PyMsgTask *ret = NULL;
	MSG_error_t e;
	m_task_t task_t = NULL;
	
	e = MSG_task_get_with_time_out(&task_t, channel, max_duration);

	if(e == MSG_OK) {
		ret = new PyMsgTask(task_t);
	}

	return ret;
}

int PyMsgGlobal::getHostCount()
{
	ENTER_FUNCTION(PyMsgGlobal::getHostCount);

	return MSG_get_host_number();
}

PyMsgHost *PyMsgGlobal::getHostByIndex(int hostIdx)
{
	if(hostIdx < 0 || hostIdx >= MSG_get_host_number())
		printError("PyMsgGlobal::getHostByIndex", "Index out of range.");

	m_host_t *table = MSG_get_host_table();
	PyMsgHost *ret = new PyMsgHost(table[hostIdx]);

	return ret;
}

void PyMsgGlobal::addProcessToMap(const char *fncName, m_process_t proc_t)
{
	// add the new process to the map so I can know the function that should be executed for proc_t
	AutoMutextLock lock(_fncMapLock);

	PythonFunctionMap::iterator it =  _fncMap.find(fncName);
	if(it == _fncMap.end()) {
		char msg[1024];
		snprintf(msg, sizeof(msg), "Cannot find function %s", fncName);
		printError("PyMsgGlobal::addProcessToMap", msg);
		return;
	}

	_procMap[proc_t] = it->second;
}

/**
\brief Test whether there is a pending communication on a channel.

It takes one parameter.
\param channel the channel on which the agent should be
listening. This value has to be >=0 and < than the maximal
number of channels fixed with MsgSimulation::setChannelNumber.
\return true if there is a pending communication and false otherwise
*/
bool PyMsgGlobal::currentHostPendingComm(m_channel_t channel)
{
	ENTER_FUNCTION(PyMsgGlobal::currentHostPendingComm);

	return (MSG_task_Iprobe(channel) != 0);
}

/**
\brief Test whether there is a pending communication on a channel, and who sent it.

It takes one parameter.
\param channel the channel on which the agent should be
listening. This value has to be >=0 and < than the maximal
number of channels fixed with MsgSimulation::setChannelNumber.
\return -1 if there is no pending communication and the PID of the process who sent it otherwise
*/
int PyMsgGlobal::currentHostPendingCommPID(m_channel_t channel)
{
	ENTER_FUNCTION(PyMsgGlobal::currentHostPendingCommPID);

	return MSG_task_probe_from(channel);
}

/**
\brief Wait for at most \a max_duration second for a task reception
on \a channel. *\a PID is updated with the PID of the first process
that triggered this event if any.

 It takes three parameters:
 \param channel the channel on which the agent should be
 listening. This value has to be >=0 and < than the maximal.
 number of channels fixed with MsgSimulation::setChannelNumber.
 \param PID a memory location for storing an int.
 \param max_duration the maximum time to wait for a task before
giving up. In the case of a reception, *\a PID will be updated
with the PID of the first process to send a task.
\return #MSG_HOST_FAILURE if the host is shut down in the meantime
and #MSG_OK otherwise.
*/
MSG_error_t PyMsgGlobal::currentHostPendingCommPID(m_channel_t channel, double max_duration, int *PID)
{
	ENTER_FUNCTION(PyMsgGlobal::currentHostPendingCommPID);

	return MSG_channel_select_from(channel, max_duration, PID);
}

/**
\brief Return the number of tasks waiting to be received on a \a
channel and sent by \a host.

It takes two parameters.
\param channel the channel on which the agent should be
listening. This value has to be >=0 and < than the maximal
number of channels fixed with MsgSimulation::setChannelNumber.
\param host the host that is to be watched.
\return the number of tasks waiting to be received on \a channel
and sent by \a host.
*/
int PyMsgGlobal::currentHostPendingCommFromHost(m_channel_t channel, PyMsgHost *host)
{
	ENTER_FUNCTION(PyMsgGlobal::currentHostPendingCommFromHost);

	return MSG_task_probe_from_host(channel, host->getObject());
}

int PyMsgGlobal::trampoline(int argc, char *argv[])
{
	PythonThread pyThread;
	PyMsgGlobal *pThis = PyMsgGlobal::_instance;
	int ret = -1;
	PyObject *pyFnc = NULL;
	char *name = NULL;
	//bool delParams = false;

	try {
		m_process_t proc = MSG_process_self();

		{
			AutoMutextLock lock(pThis->_fncMapLock);
			PythonFunctionMap::iterator it;
			
			PythonProcessFunctionMap::iterator it2;
			it2 = pThis->_procMap.find(proc);
			if(it2 == pThis->_procMap.end()) {
				name = argv[0];
				it = pThis->_fncMap.find(name);
				if(it == pThis->_fncMap.end()) {
					char msg[1024];
					snprintf(msg, sizeof(msg), "Cannot find function %s", name);
					pThis->printError("PyMsgGlobal::trampoline", msg);
				}
				pyFnc = it->second;
			}
			else {
				// if in the _procMap the parameters are allocated for this function
				//delParams = true;
				pyFnc = it2->second;
			}
		}

		PyMsgGlobal *g = PyMsgGlobal::_instance;

		// I'm holding interpreter lock here so release it before calling reportProcessStart
		{
			ENTER_FUNCTION(PyMsgGlobal::reportProcessStart);
			g->reportProcessStart(MSG_process_get_name(proc));
		}

		if(pyFnc) {
			PyObject* args = PythonArgs::createArg(argc, argv);
			if(args) {
				PyObject* rslt = PyObject_CallFunctionObjArgs(pyFnc, args, NULL);
				if (rslt) {
					ret = PyInt_AsLong(rslt);
					Py_XDECREF(rslt);
				}
				else {
					PyErr_Print();
				}

				Py_XDECREF(args);
			}
		}

		// I'm holding interpreter lock here so release it before calling reportProcessTerminate
		{
			ENTER_FUNCTION(PyMsgGlobal::reportProcessTerminate);
			g->reportProcessTerminate(name, ret);
		}

		//if(delParams && argc != -1) {
		//	for(int i=0; i<argc; i++) {
		//		free(argv[i]);
		//	}
		//	free(argv);
		//}
	}
	catch(...) {
		printException("PyMsgGlobal::trampoline");
	}


	return ret;
}

/**
\brief Run simulation.

It takes one parameters.
\param deployFile the file that will be passed as parameter to mainFnc and should be used as deployment file
of the simulation.
\param mainFnc Function that is called to run the simulation. It's prototype must be main(platFile, deployFile).
\return -1 on error or the return code of the specified function on success.
*/
int PyMsgGlobal::run(PyObject *mainFnc, const char *deployFile)
{
	int ret = -1;
	try {
		PyObject* arg = PythonArgs::createArg(deployFile);
		if(arg) {
			PyObject* rslt = PyObject_CallFunctionObjArgs(mainFnc, arg, NULL);
			if (rslt) {
				ret = PyInt_AsLong(rslt);
				Py_XDECREF(rslt);
			}
			else {
				PyErr_Print();
			}

			Py_XDECREF(arg);
		}
	}
	catch(...) {
		printException("PyMsgGlobal::run");
	}

	return ret;
}

void PyMsgGlobal::reportTaskPut(PyMsgHost *who, PyMsgHost *dest, PyMsgTask *task, m_channel_t channel)
{
	if(_interceptor && _repMode >= _report_key_functions)
		_interceptor->onTaskPut(who->getNameInternal(), dest->getNameInternal(), task->getNameInternal(), (int) channel);
}

void PyMsgGlobal::reportTaskExecuteStart(PyMsgTask *task)
{
	if(_interceptor && _repMode >= _report_key_functions) {
		PyMsgHost *host = getCurrentHostInternal();
		PyMsgProcess *proc = getCurrentProcessInternal();

		assert(host != NULL && proc != NULL);

		_interceptor->onTaskExecuteStart(host->getNameInternal(), proc->getNameInternal(), proc->getPIDInternal(), task->getNameInternal());

		delete proc;
		delete host;
	}
}

void PyMsgGlobal::reportTaskExecuteFinish(PyMsgTask *task, MSG_error_t error)
{
	if(_interceptor && _repMode >= _report_key_functions) {
		PyMsgHost *host = getCurrentHostInternal();
		PyMsgProcess *proc = getCurrentProcessInternal();

		assert(host != NULL && proc != NULL);

		_interceptor->onTaskExecuteFinish(host->getNameInternal(), proc->getNameInternal(), proc->getPIDInternal(), task->getNameInternal(), (SGError) error);

		delete proc;
		delete host;
	}
}

void PyMsgGlobal::reportProcessCreate(const char *fncName, PyMsgHost *host)
{
	if(_interceptor && _repMode >= _report_key_functions) {
		PyMsgHost *currHost = getCurrentHostInternal();

		assert(currHost != NULL);

		_interceptor->onProcessCreate(currHost->getNameInternal(), fncName, host->getNameInternal());

		delete currHost;
	}
}

void PyMsgGlobal::reportProcessStart(const char *fncName)
{
#ifdef WIN32
	_threads.push_back(GetCurrentThread());
#endif // WIN32
	if(_interceptor && _repMode >= _report_key_functions) {
		PyMsgHost *host = getCurrentHostInternal();
		PyMsgProcess *proc = getCurrentProcessInternal();

		assert(host != NULL && proc != NULL);

		_interceptor->onProcessStart(fncName, host->getNameInternal(), proc->getNameInternal(), proc->getPIDInternal());

		delete proc;
		delete host;
	}
}

void PyMsgGlobal::reportProcessTerminate(const char *fncName, int retCode)
{
	if(_interceptor && _repMode >= _report_key_functions) {
		PyMsgHost *host = getCurrentHostInternal();
		PyMsgProcess *proc = getCurrentProcessInternal();

		assert(host != NULL && proc != NULL);

		_interceptor->onProcessTerminate(fncName, host->getNameInternal(), proc->getNameInternal(), proc->getPIDInternal(), retCode);

		delete proc;
		delete host;
	}
}

void PyMsgGlobal::reportProcessSuspend(PyMsgProcess *proc)
{
	if(_interceptor && _repMode >= _report_key_functions)
		_interceptor->onProcessSuspend(proc->getHostInternal()->getNameInternal(), proc->getNameInternal(), proc->getPIDInternal());
}

void PyMsgGlobal::reportProcessResume(PyMsgProcess *proc)
{
	if(_interceptor && _repMode >= _report_key_functions)
		_interceptor->onProcessResume(proc->getHostInternal()->getNameInternal(), proc->getNameInternal(), proc->getPIDInternal());
}

void PyMsgGlobal::printException(const char *fnc)
{
	char pyStr[256];
	snprintf(pyStr, sizeof(pyStr), "print \"fnc\"\n");
	PyRun_SimpleString(pyStr);
}

void PyMsgGlobal::printError(const char *fnc, const char *errMsg)
{
	char pyStr[1024];
	if(errMsg) {
		snprintf(pyStr, sizeof(pyStr), "print >> sys.stderr, \'Error: Function %s Message: %s\'\n", fnc, errMsg);
	}
	else {
		snprintf(pyStr, sizeof(pyStr), "print >> sys.stderr, \'Error: Function %s'\n", fnc);
	}
	PyRun_SimpleString(pyStr);
}

void PyMsgGlobal::entryFunction(const char *name)
{
	if(_interceptor && _repMode >= _report_debug)
		_interceptor->onFunctionEntry(name);
}

void PyMsgGlobal::exitFunction(const char *name)
{
	if(_interceptor && _repMode >= _report_debug)
		_interceptor->onFunctionExit(name);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////
PyMsgHost::PyMsgHost(m_host_t host_t)
{
	_host_t = host_t;
	_data = NULL;
}

PyMsgHost::~PyMsgHost()
{
	if(_data) {
		//Py_XDECREF(_data);
		_data = NULL;
	}
}

/** \ingroup m_host_management
*
* \brief Set the user data of a #PyMsgHost.
*
* This functions checks whether some data has already been associated to \a host 
or not and attach \a data to \a host if it is possible.
*/
MSG_error_t PyMsgHost::setData(PyObject *data)
{
	ENTER_FUNCTION(PyMsgHost::setData);

	_data = data;
	Py_XINCREF(_data);
	
	return MSG_host_set_data(_host_t, data);
}

/** \ingroup m_host_management
*
* \brief Return the user data of a #PyMsgHost.
*
* This functions checks whether \a host is a valid pointer or not and return
the user data associated to \a host if it is possible.
*/
PyObject *PyMsgHost::getData()
{
	ENTER_FUNCTION(PyMsgHost::getData);

	PyObject *data = (PyObject *) MSG_host_get_data(_host_t);
	Py_XINCREF(data);

	return data;
}

/** \ingroup m_host_management
*
* \brief Return the name of the #PyMsgHost.
*
* This functions checks whether \a host is a valid pointer or not and return
its name.
*/
const char *PyMsgHost::getName()
{
	ENTER_FUNCTION(PyMsgHost::getName);

	return MSG_host_get_name(_host_t);
}

const char *PyMsgHost::getNameInternal()
{
	return MSG_host_get_name(_host_t);
}

/** \ingroup m_host_management
* \brief Return the number of MSG tasks currently running on a
* #PyMsgHost. The external load is not taken in account.
*/
int PyMsgHost::getMsgload()
{
	ENTER_FUNCTION(PyMsgHost::getMsgload);

	return MSG_get_host_msgload(_host_t);
}

/** \ingroup m_host_management
* \brief Return the speed of the processor (in flop/s), regardless of 
the current load on the machine.
*/
double PyMsgHost::getSpeed()
{
	ENTER_FUNCTION(PyMsgHost::getSpeed);

	return MSG_get_host_speed(_host_t);
}

/** \ingroup msg_gos_functions
* \brief Determine if a host is available.
*/
int PyMsgHost::isAvail()
{
	ENTER_FUNCTION(PyMsgHost::isAvail);

	return MSG_host_is_avail(_host_t);
}

/**
\brief Listen on \a channel and waits for receiving a task from \a host.

It takes three parameters.
\param channel the channel on which the agent should be
listening. This value has to be >=0 and < than the maximal
number of channels fixed with MsgSimulation::setChannelNumber.
\return NULL on error.
*/
PyMsgTask *PyMsgHost::getTask(m_channel_t channel)
{
	ENTER_FUNCTION(PyMsgHost::getTask);

	m_task_t task_t;
	MSG_error_t e;
	PyMsgTask *ret = NULL;

	e = MSG_task_get(&task_t, channel);
	if(e == MSG_OK) {
		ret = new PyMsgTask(task_t);
	}

	return ret;
}

/** \ingroup msg_gos_functions
* \brief Put a task on a channel of an host and waits for the end of the
* transmission.
*
* This function is used for describing the behavior of an agent. It
* takes three parameter.
* \param task a #PyMsgHost to send on another location. This task
will not be usable anymore when the function will return. There is
no automatic task duplication and you have to save your parameters
before calling this function. Tasks are unique and once it has been
sent to another location, you should not access it anymore. You do
not need to call PyMsgTask::destroy() but to avoid using, as an
effect of inattention, this task anymore, you definitely should
renitialize it with #MSG_TASK_UNINITIALIZED. Note that this task
can be transfered iff it has been correctly created with
PyMsgTask() constructors.
* \param channel the channel on which the agent should put this
task. This value has to be >=0 and < than the maximal number of
channels fixed with PyMsgGlobal::setChannelNumber().
* \return #MSG_FATAL if \a task is not properly initialized and
* #MSG_OK otherwise. Returns #MSG_HOST_FAILURE if the host on which
* this function was called was shut down. Returns
* #MSG_TRANSFER_FAILURE if the transfer could not be properly done
* (network failure, dest failure)
*/
MSG_error_t PyMsgHost::putTask(PyMsgTask *task, m_channel_t channel)
{
	ENTER_FUNCTION(PyMsgHost::putTask);

	PyMsgGlobal::_instance->reportTaskPut(PyMsgGlobal::_instance->getCurrentHostInternal(), this, task, channel);

	return MSG_task_put(task->getObject(), _host_t, channel);
}

/** \ingroup msg_gos_functions
* \brief Does exactly the same as PyMsgHost::putTask but with a bounded transmition 
* rate.
*
* \sa PyMsgHost::putTask
*/
MSG_error_t PyMsgHost::putTaskBounded(PyMsgTask *task, m_channel_t channel, double max_rate)
{
	ENTER_FUNCTION(PyMsgHost::putTaskBounded);

	PyMsgGlobal::_instance->reportTaskPut(PyMsgGlobal::_instance->getCurrentHostInternal(), this, task, channel);

	return MSG_task_put_bounded(task->getObject(), _host_t, channel, max_rate);
}

/////////////////////////////////////////////////////////////////////////////////////////////////////////
PyMsgProcess::PyMsgProcess(m_process_t proc_t)
{
	init(proc_t);
}

/** \ingroup m_process_management
* \brief Creates and runs a new #PyMsgProcess.

* A constructor for #PyMsgProcess taking four arguments and returning the 
* corresponding object. The structure (and the corresponding thread) is
* created, and put in the list of ready process.
* \param name a name for the object. It is for user-level information
and can be NULL.
* \param fncCode is a function describing the behavior of the agent. It
should then only use functions described in \ref
m_process_management (to create a new #PyMsgProcess for example),
in \ref m_host_management (only the read-only functions i.e. whose
name contains the word get), in \ref m_task_management (to create
or destroy some #PyMsgTask for example) and in \ref
msg_gos_functions (to handle file transfers and task processing).
* \param data a pointer to any data one may want to attach to the new
object.  It is for user-level information and can be NULL. It can
be retrieved with the function \ref PyMsgProcess::getData.
* \param host the location where the new agent is executed.
* \param args arguments passed to \a fncCode. NOTE: this parameter cannot be used due a problem with simgrid heap.
* \return The new corresponding object.
*/
PyMsgProcess::PyMsgProcess(const char *fncName, const char *name, PyObject *data,
						   PyMsgHost *host, const StrList &args)
{
	//ENTER_FUNCTION(PyMsgProcess::PyMsgProcess);

	int argc;
	char **argv = NULL;
	m_process_t proc_t = NULL;

	PyMsgGlobal *msgGlobal = PyMsgGlobal::_instance;
	//msgGlobal->processRegister(name, fncCode);
	
	argc = args.size();
	if(argc > 0) {
		// copy the parameters to a new buffer because they are needed in the trampoline. They are released there
		//argv = (char **) malloc(sizeof(char*) * argc);
		argv = (char **) xbt_malloc(sizeof(char*) * argc);
		assert(argv != NULL);

		StrList::const_iterator it;
		int i;
		for(i=0, it = args.begin(); i<argc; i++, it++) {
			//argv[i] = (char*) malloc(strlen(*it) + 1);
			argv[i] = (char *) xbt_malloc(strlen(*it) + 1);
			strcpy(argv[i], *it);
		}
	}

	PyMsgGlobal::_instance->reportProcessCreate(name, host);

	if(argc == 0 || argv == NULL) {
		proc_t = MSG_process_create(name, PyMsgGlobal::trampoline, (data == Py_None ? NULL : data), host->getObject());
	}
	else {
		proc_t = MSG_process_create_with_arguments(name, PyMsgGlobal::trampoline, (data == Py_None ? NULL : data), host->getObject(), argc, argv);
	}

	if(proc_t) {
		msgGlobal->addProcessToMap(fncName, proc_t);
	}

	init(proc_t);

	_data = data;
	Py_XINCREF(_data);
}

PyMsgProcess::~PyMsgProcess()
{
	if(_data) {
		//Py_XDECREF(_data);
		_data = NULL;
	}
}

void PyMsgProcess::init(m_process_t proc_t)
{
	_proc_t = proc_t;
	_data = NULL;
}

/** \ingroup m_process_management
* This function simply kills a \a process... scary isn't it ? :)
*/
void PyMsgProcess::kill()
{
	ENTER_FUNCTION(PyMsgProcess::kill);

	MSG_process_kill(_proc_t);
}

/** \ingroup m_process_management
* \brief Migrates an agent to another location.
*
* This functions checks whether \a process and \a host are valid pointers
and change the value of the #PyMsgHost on which \a process is running.
*/
MSG_error_t PyMsgProcess::changeHost(PyMsgHost *host)
{
	ENTER_FUNCTION(PyMsgProcess::changeHost);

	return MSG_process_change_host(_proc_t, host->getObject());
}

/** \ingroup m_process_management
* \brief Return the user data of a #PyMsgProcess.
*
* This functions checks whether \a process is a valid pointer or not 
and return the user data associated to \a process if it is possible.
*/
PyObject *PyMsgProcess::getData()
{
	ENTER_FUNCTION(PyMsgProcess::getData);

	return (PyObject *) MSG_process_get_data(_proc_t);
}

/** \ingroup m_process_management
* \brief Set the user data of a #PyMsgProcess.
*
* This functions checks whether \a process is a valid pointer or not 
and set the user data associated to \a process if it is possible.
*/
MSG_error_t PyMsgProcess::setData(PyObject *data)
{
	ENTER_FUNCTION(PyMsgProcess::setData);

	_data = data;
	Py_XINCREF(_data);

	return MSG_process_set_data(_proc_t, data);
}

/** \ingroup m_process_management
* \brief Return the location on which an agent is running.
*
* This functions checks whether \a process is a valid pointer or not 
and return the PyMsgHost corresponding to the location on which \a 
process is running.
*/
PyMsgHost *PyMsgProcess::getHost()
{
	ENTER_FUNCTION(PyMsgProcess::getHost);

	PyMsgHost *ret = NULL;
	m_host_t host_t;

	host_t = MSG_process_get_host(_proc_t);
	
	if(host_t != NULL) {
		ret = new PyMsgHost(host_t);
	}

	return ret;
}

PyMsgHost *PyMsgProcess::getHostInternal()
{
	PyMsgHost *ret = NULL;
	m_host_t host_t;

	host_t = MSG_process_get_host(_proc_t);

	if(host_t != NULL) {
		ret = new PyMsgHost(host_t);
	}

	return ret;
}

/** \ingroup m_process_management
* \brief Returns the process ID of \a process.
*
* This functions checks whether \a process is a valid pointer or not 
and return its PID (or 0 in case of problem).
*/
int PyMsgProcess::getPID()
{
	ENTER_FUNCTION(PyMsgProcess::getPID);

	return MSG_process_get_PID(_proc_t);
}

int PyMsgProcess::getPIDInternal()
{
	return MSG_process_get_PID(_proc_t);
}

/** \ingroup m_process_management
* \brief Returns the process ID of the parent of \a process.
*
* This functions checks whether \a process is a valid pointer or not 
and return its PID. Returns -1 if the agent has not been created by 
another agent.
*/
int PyMsgProcess::getPPID()
{
	ENTER_FUNCTION(PyMsgProcess::getPPID);

	return MSG_process_get_PPID(_proc_t);
}

/** \ingroup m_process_management
* \brief Return the name of an agent.
*
* This functions checks whether \a process is a valid pointer or not 
and return its name.
*/
const char *PyMsgProcess::getName()
{
	ENTER_FUNCTION(PyMsgProcess::getName);

	return MSG_process_get_name(_proc_t);
}

const char *PyMsgProcess::getNameInternal()
{
	return MSG_process_get_name(_proc_t);
}

/** \ingroup m_process_management
* \brief Suspend the process.
*
* This functions suspend the process by suspending the task on which
* it was waiting for the completion.
*/
MSG_error_t PyMsgProcess::suspend()
{
	ENTER_FUNCTION(PyMsgProcess::suspend);

	PyMsgGlobal::_instance->reportProcessSuspend(this);
	
	return MSG_process_suspend(_proc_t);
}

/** \ingroup m_process_management
* \brief Resume a suspended process.
*
* This functions resume a suspended process by resuming the task on
* which it was waiting for the completion.
*/
MSG_error_t PyMsgProcess::resume()
{
	ENTER_FUNCTION(PyMsgProcess::resume);

	PyMsgGlobal::_instance->reportProcessResume(this);
	
	return MSG_process_resume(_proc_t);
}

/** \ingroup m_process_management
* \brief Returns true if the process is suspended .
*
* This checks whether a process is suspended or not by inspecting the
* task on which it was waiting for the completion.
*/
bool PyMsgProcess::isSuspended()
{
	ENTER_FUNCTION(PyMsgProcess::isSuspended);

	return (MSG_process_is_suspended(_proc_t) != 0);
}

////////////////////////////////////////////////////////////////////////////////////////////
PyMsgTask::PyMsgTask(m_task_t task_t)
{
	init(task_t);
}

/** \ingroup m_task_management
* \brief Creates a new #PyMsgTask.
*
* A constructor for #PyMsgTask taking four arguments and returning the 
corresponding object.
* \param name a name for the object. It is for user-level information
and can be NULL.
* \param compute_duration a value of the processing amount (in flop)
needed to process this new task. If 0, then it cannot be executed with
PyMsgTask::execute(). This value has to be >=0.
* \param message_size a value of the amount of data (in bytes) needed to
transfer this new task. If 0, then it cannot be transfered with
PyMsgGlobal::getTaskFromCurrentProcess() and PyMsgHost::putTask() (or PyMsgHost::putTask). This value has to be >=0.
* \param data a pointer to any data may want to attach to the new
object.  It is for user-level information and can be NULL. It can
be retrieved with the function \ref PyMsgTask::getData.
* \return The new corresponding object.
*/
PyMsgTask::PyMsgTask(const char *name, double compute_duration, double message_size, PyObject *data)
{
	ENTER_FUNCTION(PyMsgTask::PyMsgTask);

	init(MSG_task_create(name, compute_duration, message_size, data));

	_data = data;
	Py_XINCREF(_data);
}

/** \ingroup m_task_management
* \brief Creates a new #PyMsgTask (a parallel one....).
*
* A constructor for #PyMsgTask taking six arguments and returning the 
corresponding object.
* \param name a name for the object. It is for user-level information
and can be NULL.
* \param host_list a list of \p host_nb PyMsgHost.
* \param computation_amount an array of \p host_nb
doubles. computation_amount[i] is the total number of operations
that have to be performed on host_list[i].
* \param communication_amount an array of \p host_nb* \p host_nb doubles.
* \param data a pointer to any data may want to attach to the new
object.  It is for user-level information and can be NULL. It can
be retrieved with the function \ref PyMsgTask::getData.
* \return The new corresponding object.
*/
PyMsgTask::PyMsgTask(const char *name, PyMsgHostList &host_list,
					 double *computation_amount, double *communication_amount,
					 PyObject *data)
{
	ENTER_FUNCTION(PyMsgTask::PyMsgTask);

	m_host_t *host_t_list;
	int i = 0;
	int host_nb = host_list.size();
	m_task_t task_t = NULL;

	_data = NULL;
	assert(host_nb != 0);

	host_t_list = (m_host_t *) malloc(sizeof(m_host_t) * host_nb);
	
	for(PyMsgHostList::iterator it = host_list.begin(); it != host_list.end(); it++) {
		PyMsgHost *host = *it;

		host_t_list[i++] = host->getObject();
	}

	task_t = MSG_parallel_task_create(name, host_nb, host_t_list, computation_amount, communication_amount, data);

	free(host_t_list);

	init(task_t);

	_data = data;
	Py_XINCREF(_data);
}

PyMsgTask::~PyMsgTask()
{
	if(_data) {
		//Py_XDECREF(_data);
		_data = NULL;
	}
}

void PyMsgTask::init(m_task_t task_t)
{
	_data = NULL;
	_task_t = task_t;
}

/** \ingroup m_task_management
* \brief Return the user data of a #PyMsgTask.
*
* This functions checks whether \a task is a valid pointer or not and return
the user data associated to \a task if it is possible.
*/
PyObject *PyMsgTask::getData()
{
	ENTER_FUNCTION(PyMsgTask::getData);

	PyObject *obj = (PyObject *) MSG_task_get_data(_task_t);
	Py_XINCREF(obj);
	return obj;
//	return (PyObject *) MSG_task_get_data(_task_t);
}

/** \ingroup m_task_management
* \brief Return the sender of a #PyMsgTask.
*
* This functions returns the #PyMsgProcess which sent this task
*/
PyMsgProcess *PyMsgTask::getSender()
{
	ENTER_FUNCTION(PyMsgTask::getSender);

	PyMsgProcess *ret = NULL;
	m_process_t proc_t;

	proc_t = MSG_task_get_sender(_task_t);
	
	if(proc_t != NULL) {
		ret = new PyMsgProcess(proc_t);
	}

	return ret;
}

/** \ingroup m_task_management
* \brief Return the source of a #PyMsgTask.
*
* This functions returns the #PyMsgHost from which this task was sent
*/
PyMsgHost *PyMsgTask::getSource()
{
	ENTER_FUNCTION(PyMsgTask::getSource);

	PyMsgHost *ret = NULL;
	m_host_t host_t;

	host_t = MSG_task_get_source(_task_t);
	
	if(host_t != NULL) {
		ret = new PyMsgHost(host_t);
	}

	return ret;
}

/** \ingroup m_task_management
* \brief Return the name of a #PyMsgTask.
*
* This functions returns the name of a #PyMsgTask as specified on creation
*/
const char *PyMsgTask::getName()
{
	ENTER_FUNCTION(PyMsgTask::getName);

	return MSG_task_get_name(_task_t);
}

const char *PyMsgTask::getNameInternal()
{
	return MSG_task_get_name(_task_t);
}

/** \ingroup m_task_management
* \brief Cancel a #PyMsgTask.
*/
MSG_error_t PyMsgTask::cancel()
{
	ENTER_FUNCTION(PyMsgTask::cancel);

	return MSG_task_cancel(_task_t);
}

/** \ingroup m_task_management
* \brief Destroy a #PyMsgTask.
*
* Destructor for #PyMsgTask. Note that you should free user data, if any, \b 
before calling this function.
*/
MSG_error_t PyMsgTask::destroy()
{
	ENTER_FUNCTION(PyMsgTask::destroy);

	PyObject *data = (PyObject *) MSG_task_get_data(_task_t);
	if(data)
		Py_XDECREF(data);
	return MSG_task_destroy(_task_t);
}

/** \ingroup msg_gos_functions \brief Put a task on a channel of an
* host (with a timeout on the waiting of the destination host) and
* waits for the end of the transmission.
*
* This function is used for describing the behavior of an agent. It
* takes four parameter.
* \param dest the destination of the message
* \param channel the channel on which the agent should put this
task. This value has to be >=0 and < than the maximal number of
channels fixed with PyMsgGlobal::setChannelNumber().
* \param max_duration the maximum time to wait for a task before giving
up. In such a case, #MSG_TRANSFER_FAILURE will be returned, \a task 
will not be modified. If MSG_INFINITE set, it waits until it finishes.
* \return #MSG_FATAL if \a task is not properly initialized and
#MSG_OK otherwise. Returns #MSG_HOST_FAILURE if the host on which
this function was called was shut down. Returns
#MSG_TRANSFER_FAILURE if the transfer could not be properly done
(network failure, dest failure, timeout...)
*/
MSG_error_t PyMsgTask::put(PyMsgHost *dest, m_channel_t channel, double max_duration)
{
	ENTER_FUNCTION(PyMsgTask::put);

	MSG_error_t ret;

	PyMsgGlobal::_instance->reportTaskPut(PyMsgGlobal::_instance->getCurrentHostInternal(), dest, this, channel);

	if(max_duration == MSG_INFINITE) {
		ret = MSG_task_put(_task_t, dest->getObject(), channel);
	}
	else {
		ret = MSG_task_put_with_timeout(_task_t, dest->getObject(), channel, max_duration);
	}

	return ret;
}

/** \ingroup msg_gos_functions
* \brief Does exactly the same as PyMsgTask::put but with a bounded transmition 
* rate.
*
* \sa PyMsgTask::put
*/
MSG_error_t PyMsgTask::putBounded(PyMsgHost *dest, m_channel_t channel, double max_rate)
{
	ENTER_FUNCTION(PyMsgTask::putBounded);

	PyMsgGlobal::_instance->reportTaskPut(PyMsgGlobal::_instance->getCurrentHostInternal(), dest, this, channel);

	return MSG_task_put_bounded(_task_t, dest->getObject(), channel, max_rate);
}

/** \ingroup msg_gos_functions
* \brief Executes a task and waits for its termination.
*
* This function is used for describing the behavior of an agent. It
* takes only one parameter.
* \return #MSG_FATAL if \a task is not properly initialized and
* #MSG_OK otherwise.
*/
MSG_error_t PyMsgTask::execute()
{
	ENTER_FUNCTION(PyMsgTask::execute);

	PyMsgGlobal *g = PyMsgGlobal::_instance;
	g->reportTaskExecuteStart(this);

	MSG_error_t ret = MSG_task_execute(_task_t);

	g->reportTaskExecuteFinish(this, ret);

	return ret;
}

MSG_error_t PyMsgTask::parallelExecute()
{
	ENTER_FUNCTION(PyMsgTask::parallelExecute);

	PyMsgGlobal *g = PyMsgGlobal::_instance;
	g->reportTaskExecuteStart(this);

	MSG_error_t ret = MSG_parallel_task_execute(_task_t);

	g->reportTaskExecuteFinish(this, ret);

	return ret;
}

/** \ingroup m_task_management
* \brief Changes the priority of a computation task. This priority doesn't affect 
*        the transfer rate. A priority of 2 will make a task receive two times more
*        cpu power than the other ones.
*
*/
void PyMsgTask::setPriority(double priority)
{
	ENTER_FUNCTION(PyMsgTask::setPriority);

	MSG_task_set_priority(_task_t, priority);
}

/** \ingroup m_task_management
* \brief Returns the computation amount needed to process a task #PyMsgTask.
*        Once a task has been processed, this amount is thus set to 0...
*/
double PyMsgTask::getComputeDuration()
{
	ENTER_FUNCTION(PyMsgTask::getComputeDuration);

	return MSG_task_get_compute_duration(_task_t);
}

/** \ingroup m_task_management
* \brief Returns the remaining computation amount of a task #PyMsgTask.
*
*/
double PyMsgTask::getRemainingComputation()
{
	ENTER_FUNCTION(PyMsgTask::getRemainingComputation);

	return MSG_task_get_remaining_computation(_task_t);
}

/** \ingroup m_task_management
* \brief Returns the size of the data attached to a task #PyMsgTask.
*
*/
double PyMsgTask::getDataSize()
{
	ENTER_FUNCTION(PyMsgTask::getDataSize);

	return MSG_task_get_data_size(_task_t);
}
