#ifndef __MSG_PYTHON_H
#define __MSG_PYTHON_H

#pragma warning(disable: 4786)


#include <list>
#include <string>
#include <map>

#include <Python.h>



extern "C" {
	#ifdef TRUE
	#	undef TRUE
	#endif // TRUE
	#ifdef FALSE
	#	undef FALSE
	#endif // FALSE
	
	#include "msg/datatypes.h"
	//#undef _THROW
	//#include "msg/msg.h"
	//#include "xbt/sysdep.h"
};

#include "python_utils.h"
#include "../cpp-wrapper/mutex_lock.h"

#include "msg_intercept.h"

#define MSG_INFINITE -1

class PyMsgTask;
class PyMsgHost;
class PyMsgProcess;
class MsgInterceptor;


typedef std::list<char*> StrList;
typedef std::list<PyMsgHost *> PyMsgHostList;
typedef std::map<std::string, PyObject *> PythonFunctionMap;
typedef std::map<void *, PyObject *> PythonProcessFunctionMap;

typedef void (*interceptorFunction)(int );

class PyMsgGlobal : public PythonDebugListener {
	friend class PyMsgHost;
	friend class PyMsgTask;
	friend class PyMsgProcess;

public:
	typedef enum {
		_report_debug = 5,
		_report_key_functions = 1,
		_report_none = 0
	} ReportLevel;

	static PyMsgGlobal* instance();
	static void destroy();

	void setReportMode(ReportLevel mode);

	void createEnvironment(const char *file);

	void config(const char *name, ...);
	void globalInit(const StrList &args);
	void globalInitArgs(const StrList &args);
	MSG_error_t setChannelNumber(int number);
	int getChannelNumber();
	MSG_error_t main();
	MSG_error_t clean();

	void functionRegister(const char *name, PyObject *fnc);
	void processRegister(const char *name, PyObject *fnc);

	void launchApplication(const char *file);
	void setPajeOutput(const char *filename);
	double getClock();

	int killAllProcesses(int resetPIDs);
	PyMsgProcess *getCurrentProcess();
	MSG_error_t currentProcessSleep(double nb_sec);

	MSG_error_t getErrno();

	PyMsgHost *getCurrentHost();

	PyMsgHost *getHostByName(const char *name);
	PyMsgProcess *processFromPID(int PID);

	PyMsgTask *getTaskFromCurrentHost(m_channel_t channel, double max_duration = MSG_INFINITE);

	int getHostCount();
	PyMsgHost *getHostByIndex(int hostIdx);

	// Current host
	bool currentHostPendingComm(m_channel_t channel);
	int currentHostPendingCommPID(m_channel_t channel);
	MSG_error_t currentHostPendingCommPID(m_channel_t channel, double max_duration, int *PID);
	int currentHostPendingCommFromHost(m_channel_t channel, PyMsgHost *host);

	int run(PyObject *mainFnc, const char *deployFile);

	static int trampoline(int argc, char *argv[]);

	void setInterceptor(MsgInterceptor *interceptor) { _interceptor = interceptor; }

	static void printException(const char *fnc);
	static void printError(const char *fnc, const char *errMsg);

	virtual void entryFunction(const char *name);
	virtual void exitFunction(const char *name);

protected:
	void reportTaskPut(PyMsgHost *who, PyMsgHost *dest, PyMsgTask *task, m_channel_t channel);
	void reportTaskExecuteStart(PyMsgTask *task);
	void reportTaskExecuteFinish(PyMsgTask *task, MSG_error_t error);
	void reportProcessCreate(const char *fncName, PyMsgHost *dest);
	void reportProcessStart(const char *fncName);
	void reportProcessTerminate(const char *fncName, int retCode);
	void reportProcessSuspend(PyMsgProcess *proc);
	void reportProcessResume(PyMsgProcess *proc);

	
	PyMsgGlobal();
	~PyMsgGlobal();


	static void convertStrListToArgv(const StrList &args, int *argc, char ***argv);

	PyMsgHost *getCurrentHostInternal();
	PyMsgProcess *getCurrentProcessInternal();

	void addProcessToMap(const char *fncName, m_process_t proc_t);

private:
	static PyMsgGlobal* _instance;
	PythonFunctionMap _fncMap;
	PythonProcessFunctionMap _procMap;
	MutexLock _fncMapLock;
	MsgInterceptor *_interceptor;
	ReportLevel _repMode;

	// there is a bug in simgrid that doesn't reset the clock after the first run, so I get the start clock to substract it
	static double _startClock;

#ifdef WIN32
	std::list<HANDLE> _threads;
#endif // WIN32
};


class PyMsgHost {
	friend class PyMsgGlobal;
	friend class PyMsgProcess;
	friend class PyMsgTask;

public:
	~PyMsgHost();

	MSG_error_t setData(PyObject *data);
	PyObject *getData();
	const char *getName();
	int getMsgload();
	double getSpeed();
	int isAvail();

	// why this function doesn't receive any parameter?
	//int MSG_get_host_number(void);
	//m_host_t *MSG_get_host_table(void);

	PyMsgTask *getTask(m_channel_t channel);

	MSG_error_t putTask(PyMsgTask *task, m_channel_t channel);
	MSG_error_t putTaskBounded(PyMsgTask *task, m_channel_t channel, double max_rate);
	
protected:
	PyMsgHost(m_host_t host_t);

	m_host_t getObject() { return _host_t; }

	/**
	Get name of the object without unlocking interpreter.
	*/
	const char *getNameInternal();

private:
	m_host_t _host_t;
	PyObject *_data;
};


class PyMsgProcess {
	friend class PyMsgGlobal;
	friend class PyMsgHost;
	friend class PyMsgTask;

public:
	PyMsgProcess(const char *fncName, const char *name, PyObject *data,
				 PyMsgHost *host, const StrList &args = StrList());
	~PyMsgProcess();
	

	void kill();

	MSG_error_t changeHost(PyMsgHost *host);

	PyObject *getData();
	MSG_error_t setData(PyObject *data);
	PyMsgHost *getHost();
	int getPID();
	int getPPID();

	const char *getName();

	MSG_error_t suspend();
	MSG_error_t resume();
	bool isSuspended();


protected:
	PyMsgProcess(m_process_t proc_t);

	void init(m_process_t proc_t);

	m_process_t getObject() { return _proc_t; }

	/**
	Get name of the object without unlocking interpreter.
	*/
	const char *getNameInternal();
	int getPIDInternal();
	PyMsgHost *getHostInternal();

private:
	m_process_t _proc_t;
	PyObject *_data;
};


class PyMsgTask {
	friend class PyMsgGlobal;
	friend class PyMsgHost;
	friend class PyMsgProcess;

public:
	PyMsgTask(const char *name, double compute_duration, double message_size, PyObject *data);
	PyMsgTask(const char *name, PyMsgHostList &host_list, double *computation_amount, 
			  double *communication_amount, PyObject *data);
	~PyMsgTask();

	PyObject *getData();
	PyMsgProcess *getSender();
	PyMsgHost *getSource();
	const char *getName();
	MSG_error_t cancel();
	MSG_error_t destroy();

	MSG_error_t put(PyMsgHost *dest, m_channel_t channel, double max_duration = MSG_INFINITE);
	MSG_error_t putBounded(PyMsgHost *dest, m_channel_t channel, double max_rate);

	MSG_error_t execute();
	MSG_error_t parallelExecute();
	void setPriority(double priority);

	double getComputeDuration();
	double getRemainingComputation();
	double getDataSize();

protected:
	PyMsgTask(m_task_t task_t);

	void init(m_task_t task_t);

	m_task_t getObject() { return _task_t; }
	
	/**
	Get name of the object without unlocking interpreter.
	*/
	const char *getNameInternal();

private:
	m_task_t _task_t;
	PyObject *_data;
};


#endif // __MSG_PYTHON_H