#if defined(WIN32) && defined(TARGET_BUILDTYPE_Debug)
// Windows debug build doesn't typically link as most people don't have the python debug library.
#else

#if defined TARGET_HAS_ThirdPartyPython2

#include "ovpCBoxAlgorithmPython2.h"

#if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 2)

#define UN_CONST(c) const_cast<char*>(c)
//#define UN_CONST(c) (char*)(c)
//#define UN_CONST(c) (c)

#include <iostream>

namespace OpenViBE {
namespace Plugins {
namespace Python {

//****************************
//***** Static Functions *****
//****************************
///-------------------------------------------------------------------------------------------------
static bool appendToPyObject(PyObject* obj, PyObject* buffer)
{
	PyObject* methodToCall = PyString_FromString("append");
	PyObject* result       = PyObject_CallMethodObjArgs(buffer, methodToCall, obj, NULL);
	Py_CLEAR(methodToCall);
	if (result == nullptr) { return false; }
	Py_CLEAR(result);
	return true;
}

static bool getLenFromPyObject(PyObject* obj, size_t& len)
{
	PyObject* pyLen = PyObject_CallMethod(obj, UN_CONST("__len__"), nullptr);
	if (pyLen == nullptr) { return false; }
	len = size_t(PyInt_AsUnsignedLongMask(pyLen));
	Py_CLEAR(pyLen);
	return true;
}

static void getTimeFromPyObject(PyObject* obj, const char* attr, uint64_t& time)
{
	PyObject* pyTime = PyObject_GetAttrString(obj, attr);
	time             = CTime(PyFloat_AsDouble(pyTime)).time();
	Py_CLEAR(pyTime);
}

static void getTimesFromPyObject(PyObject* obj, uint64_t& start, uint64_t& end)
{
	getTimeFromPyObject(obj, "startTime", start);
	getTimeFromPyObject(obj, "endTime", end);
}

static bool setMatrixInfosFromPyObject(PyObject* obj, CMatrix* matrix)
{
	PyObject* pyNDim = PyObject_CallMethod(obj, UN_CONST("getDimensionCount"), nullptr);
	if (pyNDim == nullptr) { return false; }

	const size_t nDim = PyInt_AsUnsignedLongMask(pyNDim);
	matrix->setDimensionCount(nDim);
	Py_CLEAR(pyNDim);

	PyObject* pySizeDim  = PyObject_GetAttrString(obj, "dimensionSizes");
	PyObject* pyLabelDim = PyObject_GetAttrString(obj, "dimensionLabels");

	size_t offset = 0;
	for (size_t i = 0; i < nDim; ++i)
	{
		const size_t size = PyInt_AsUnsignedLongMask(PyList_GetItem(pySizeDim, Py_ssize_t(i)));
		matrix->setDimensionSize(i, size);
		for (size_t j = 0; j < size; ++j) { matrix->setDimensionLabel(i, j, PyString_AsString(PyList_GetItem(pyLabelDim, offset + j))); }
		offset = offset + size;
	}
	Py_CLEAR(pySizeDim);
	Py_CLEAR(pyLabelDim);
	return true;
}
///-------------------------------------------------------------------------------------------------

bool CBoxAlgorithmPython2::m_isPythonInitialized    = false;
PyObject* CBoxAlgorithmPython2::m_mainModule        = nullptr;
PyObject* CBoxAlgorithmPython2::m_mainDictionnary   = nullptr;
PyObject* CBoxAlgorithmPython2::m_matrixHeader      = nullptr;
PyObject* CBoxAlgorithmPython2::m_matrixBuffer      = nullptr;
PyObject* CBoxAlgorithmPython2::m_matrixEnd         = nullptr;
PyObject* CBoxAlgorithmPython2::m_signalHeader      = nullptr;
PyObject* CBoxAlgorithmPython2::m_signalBuffer      = nullptr;
PyObject* CBoxAlgorithmPython2::m_signalEnd         = nullptr;
PyObject* CBoxAlgorithmPython2::m_stimulationHeader = nullptr;
PyObject* CBoxAlgorithmPython2::m_stimulation       = nullptr;
PyObject* CBoxAlgorithmPython2::m_stimulationSet    = nullptr;
PyObject* CBoxAlgorithmPython2::m_stimulationEnd    = nullptr;
PyObject* CBoxAlgorithmPython2::m_buffer            = nullptr;
PyObject* CBoxAlgorithmPython2::m_execFileFunction  = nullptr;
PyObject* CBoxAlgorithmPython2::m_stdout            = nullptr;
PyObject* CBoxAlgorithmPython2::m_stderr            = nullptr;

bool CBoxAlgorithmPython2::logSysStd(const bool out)
{
	//New reference
	PyObject* pyLog = PyObject_CallMethod((out ? m_stdout : m_stderr), UN_CONST("getvalue"), nullptr);
	if (pyLog == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to call sys.std" << (out ? "out" : "err") << ".getvalue().\n";
		return false;
	}

	char* log = PyString_AsString(pyLog);
	if (log == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to convert pyLog to (char *).\n";
		Py_CLEAR(pyLog);
		return false;
	}
	Py_CLEAR(pyLog);

	if (strlen(log) > 0)
	{
		this->getLogManager() << Kernel::LogLevel_Info << log;

		PyObject* result = PyObject_CallMethod((out ? m_stdout : m_stderr), UN_CONST("flush"), nullptr);
		if (result == nullptr)
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Failed to call sys.std" << (out ? "out" : "err") << ".flush().\n";
			return false;
		}
		Py_CLEAR(result);
	}
	return true;
}

uint64_t CBoxAlgorithmPython2::getClockFrequency()
{
	getLogManager() << Kernel::LogLevel_Trace << "Clock frequency requested at time " << getPlayerContext().getCurrentTime() << "\n";
	return m_clockFrequency << 32;
}

void CBoxAlgorithmPython2::buildPythonSettings()
{
	const Kernel::IBox* boxCtx = getBoxAlgorithmContext()->getStaticBoxContext();
	for (uint32_t i = 2; i < boxCtx->getSettingCount(); ++i)
	{
		CString name;
		boxCtx->getSettingName(i, name);
		const CString value = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i);
		PyDict_SetItemString(m_boxSetting, name, PyString_FromString(value.toASCIIString()));
	}
}

bool CBoxAlgorithmPython2::initializePythonSafely()
{
	// Only the first Python box does the initialization of the global parts
	if (m_isPythonInitialized) { return true; }

	this->getLogManager() << Kernel::LogLevel_Info << "Discovered Python is " << Py_GetVersion() << " (" << Py_GetPlatform() << ")\n";
	this->getLogManager() << Kernel::LogLevel_Debug << "The Python path is [" << Py_GetPath() << "]\n";

	const std::string cmd = string("import sys\nsys.path.append('") + Directories::getDataDir().toASCIIString() + "/plugins/python2')\nsys.argv = [\"openvibe\"]\n";
	// cmd += "import openvibe\n" + "from StimulationsCodes import *\n";
	this->getLogManager() << Kernel::LogLevel_Trace << "Running [" << cmd << "].\n";

	PyRun_SimpleString(cmd.c_str());

	//Borrowed reference
	m_mainModule = PyImport_AddModule("__main__");
	//Borrowed reference
	m_mainDictionnary = PyModule_GetDict(m_mainModule);

	//Execute the script which contains the different classes to interact with OpenViBE
	const std::string path = string(Directories::getDataDir().toASCIIString()) + "/plugins/python2/openvibe.py";
	//New reference
	PyObject* pyScriptFile = PyFile_FromString(UN_CONST(path.c_str()), UN_CONST("r"));
	if (pyScriptFile == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to open '" << path << "'.\n";
		return false;
	}

	if (PyRun_SimpleFile(PyFile_AsFile(pyScriptFile), UN_CONST(path.c_str())) == -1)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to run " << path << ".\n";
		Py_CLEAR(pyScriptFile);
		return false;
	}
	Py_CLEAR(pyScriptFile);

	//Borrowed reference
	m_stdout = PySys_GetObject(UN_CONST("stdout"));
	if (m_stdout == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "sys.stdout does not exist.\n";
		return false;
	}
	//Borrowed reference
	m_stderr = PySys_GetObject(UN_CONST("stderr"));
	if (m_stderr == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "sys.stderr does not exist.\n";
		return false;
	}

	//Borrowed reference
	m_execFileFunction = PyDict_GetItemString(m_mainDictionnary, "execfileHandlingException");
	if (m_execFileFunction == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "openvibe.py doesn't have a execfileHandlingException function, quitting" << ".\n";
		return false;
	}

	if (!PyCallable_Check(m_execFileFunction))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "openvibe.py doesn't have a execfileHandlingException function callable, " << ".\n";
		return false;
	}

	//Borrowed reference
	m_matrixHeader = PyDict_GetItemString(m_mainDictionnary, "OVStreamedMatrixHeader");
	if (m_matrixHeader == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVStreamedMatrixHeader\".\n";
		return false;
	}

	//Borrowed reference
	m_matrixBuffer = PyDict_GetItemString(m_mainDictionnary, "OVStreamedMatrixBuffer");
	if (m_matrixBuffer == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVStreamedMatrixBuffer\".\n";
		return false;
	}

	//Borrowed reference
	m_matrixEnd = PyDict_GetItemString(m_mainDictionnary, "OVStreamedMatrixEnd");
	if (m_matrixEnd == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVStreamedMatrixEnd\".\n";
		return false;
	}

	//Borrowed reference
	m_signalHeader = PyDict_GetItemString(m_mainDictionnary, "OVSignalHeader");
	if (m_signalHeader == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVSignalHeader\".\n";
		return false;
	}

	//Borrowed reference
	m_signalBuffer = PyDict_GetItemString(m_mainDictionnary, "OVSignalBuffer");
	if (m_signalBuffer == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVSignalBuffer\".\n";
		return false;
	}

	//Borrowed reference
	m_signalEnd = PyDict_GetItemString(m_mainDictionnary, "OVSignalEnd");
	if (m_signalEnd == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVSignalEnd\".\n";
		return false;
	}

	//Borrowed reference
	m_stimulationHeader = PyDict_GetItemString(m_mainDictionnary, "OVStimulationHeader");
	if (m_stimulationHeader == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVStimulationHeader\".\n";
		return false;
	}

	//Borrowed reference
	m_stimulation = PyDict_GetItemString(m_mainDictionnary, "OVStimulation");
	if (m_stimulation == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVStimulation\".\n";
		return false;
	}

	//Borrowed reference
	m_stimulationSet = PyDict_GetItemString(m_mainDictionnary, "OVStimulationSet");
	if (m_stimulationSet == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVStimulationSet\".\n";
		return false;
	}

	//Borrowed reference
	m_stimulationEnd = PyDict_GetItemString(m_mainDictionnary, "OVStimulationEnd");
	if (m_stimulationEnd == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVStimulationEnd\".\n";
		return false;
	}

	//Borrowed reference
	m_buffer = PyDict_GetItemString(m_mainDictionnary, "OVBuffer");
	if (m_buffer == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load class \"OVBuffer\".\n";
		return false;
	}

	m_isPythonInitialized = true;

	this->getLogManager() << Kernel::LogLevel_Info << "Python Interpreter initialized\n";

	return true;
}

bool CBoxAlgorithmPython2::initialize()
{
	m_box                 = nullptr;
	m_boxInput            = nullptr;
	m_boxOutput           = nullptr;
	m_boxCurrentTime      = nullptr;
	m_boxSetting          = nullptr;
	m_boxInitialize       = nullptr;
	m_boxProcess          = nullptr;
	m_boxUninitialize     = nullptr;
	m_initializeSucceeded = false;

	if (!initializePythonSafely()) { return false; }

	//Initialize the clock frequency of the box depending on the first setting of the box
	m_clockFrequency = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
	m_scriptFilename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);

	if (strlen(m_scriptFilename.toASCIIString()) == 0)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "You have to choose a script.\n";
		return false;
	}

	//Create the decoders for the inputs
	const Kernel::IBox& boxCtx = this->getStaticBoxContext();
	CIdentifier typeID;
	for (size_t i = 0; i < boxCtx.getInputCount(); ++i)
	{
		boxCtx.getInputType(i, typeID);
		if (typeID == OV_TypeId_StreamedMatrix) { m_decoders.push_back(new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_Signal) { m_decoders.push_back(new Toolkit::TSignalDecoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_FeatureVector) { m_decoders.push_back(new Toolkit::TFeatureVectorDecoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_Spectrum) { m_decoders.push_back(new Toolkit::TSpectrumDecoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_ChannelLocalisation) { m_decoders.push_back(new Toolkit::TChannelLocalisationDecoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_Stimulations) { m_decoders.push_back(new Toolkit::TStimulationDecoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_ExperimentInfo) { m_decoders.push_back(new Toolkit::TExperimentInfoDecoder<CBoxAlgorithmPython2>(*this, i)); }
		else
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Codec to decode " << typeID.str() << " is not implemented.\n";
			return false;
		}
	}

	//Create the encoders for the outputs
	for (size_t i = 0; i < boxCtx.getOutputCount(); ++i)
	{
		boxCtx.getOutputType(i, typeID);
		if (typeID == OV_TypeId_StreamedMatrix) { m_encoders.push_back(new Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_Signal) { m_encoders.push_back(new Toolkit::TSignalEncoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_FeatureVector) { m_encoders.push_back(new Toolkit::TFeatureVectorEncoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_Spectrum) { m_encoders.push_back(new Toolkit::TSpectrumEncoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_ChannelLocalisation) { m_encoders.push_back(new Toolkit::TChannelLocalisationEncoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_Stimulations) { m_encoders.push_back(new Toolkit::TStimulationEncoder<CBoxAlgorithmPython2>(*this, i)); }
		else if (typeID == OV_TypeId_ExperimentInfo) { m_encoders.push_back(new Toolkit::TExperimentInfoEncoder<CBoxAlgorithmPython2>(*this, i)); }
		else
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Codec to encode " << typeID.str() << " is not implemented.\n";
			return false;
		}
	}

	//New reference
	PyObject* pyTmp = Py_BuildValue("s,O", m_scriptFilename.toASCIIString(), m_mainDictionnary);
	//New reference
	PyObject* result = PyObject_CallObject(m_execFileFunction, pyTmp);
	if (result == nullptr || PyInt_AsLong(result) != 0)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to run [" << m_scriptFilename << "]";
		if (result) { this->getLogManager() << ", result = " << PyInt_AsLong(result) << "\n"; }
		else { this->getLogManager() << ", result = NULL\n"; }
		logSysStdout();
		logSysStderr();
		Py_CLEAR(pyTmp);
		Py_CLEAR(result);
		return false;
	}

	Py_CLEAR(pyTmp);
	Py_CLEAR(result);

	/*
	PyObject *pyScriptFile = PyFile_FromString((char *) m_scriptFilename.toASCIIString(), (char *) "r");
	if (pyScriptFile == NULL)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to open " << m_scriptFilename.toASCIIString() << ".\n";
		Py_CLEAR(pyScriptFile);
		return false;
	}

	if (PyRun_SimpleFile(PyFile_AsFile(pyScriptFile), m_scriptFilename.toASCIIString()) == -1)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to run " << m_scriptFilename.toASCIIString() << ".\n";
		Py_CLEAR(pyScriptFile);
		return false;
	}
	Py_CLEAR(pyScriptFile);
	*/

	//New reference
	m_box = PyObject_GetAttrString(m_mainModule, "box"); // la box qui vient juste d'etre creee
	if (m_box == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load \"box\" object.\n";
		return false;
	}
	//New reference
	m_boxInput = PyObject_GetAttrString(m_box, "input");
	if (m_boxInput == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load \"box.input\".\n";
		return false;
	}
	//New reference
	m_boxOutput = PyObject_GetAttrString(m_box, "output");
	if (m_boxOutput == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load \"box.output\".\n";
		return false;
	}

	std::string type;
	for (size_t i = 0; i < boxCtx.getInputCount(); ++i)
	{
		boxCtx.getInputType(i, typeID);
		if (typeID == OV_TypeId_StreamedMatrix) { type = "StreamedMatrix"; }
		else if (typeID == OV_TypeId_Signal) { type = "Signal"; }
		else if (typeID == OV_TypeId_FeatureVector) { type = "FeatureVector"; }
		else if (typeID == OV_TypeId_Spectrum) { type = "Spectrum"; }
		else if (typeID == OV_TypeId_ChannelLocalisation) { type = "ChannelLocalisation"; }
		else if (typeID == OV_TypeId_Stimulations) { type = "Stimulations"; }
		else if (typeID == OV_TypeId_ExperimentInfo) { type = "ExperimentInfo"; }
		//New reference
		PyObject* pyCall = PyObject_CallMethod(m_box, UN_CONST("addInput"), UN_CONST("s"), type.c_str());
		if (pyCall == nullptr)
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Failed to call box.addInput().\n";
			return false;
		}
		Py_CLEAR(pyCall);
	}

	for (size_t i = 0; i < boxCtx.getOutputCount(); ++i)
	{
		boxCtx.getOutputType(i, typeID);
		if (typeID == OV_TypeId_StreamedMatrix) { type = "StreamedMatrix"; }
		else if (typeID == OV_TypeId_Signal) { type = "Signal"; }
		else if (typeID == OV_TypeId_FeatureVector) { type = "FeatureVector"; }
		else if (typeID == OV_TypeId_Spectrum) { type = "Spectrum"; }
		else if (typeID == OV_TypeId_ChannelLocalisation) { type = "ChannelLocalisation"; }
		else if (typeID == OV_TypeId_Stimulations) { type = "Stimulations"; }
		else if (typeID == OV_TypeId_ExperimentInfo) { type = "ExperimentInfo"; }
		//New reference
		PyObject* pyCall = PyObject_CallMethod(m_box, UN_CONST("addOutput"), UN_CONST("s"), type.c_str());
		if (pyCall == nullptr)
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Failed to call box.addOutput().\n";
			return false;
		}
		Py_CLEAR(pyCall);
	}

	//New reference
	m_boxSetting = PyObject_GetAttrString(m_box, "setting");
	if (m_boxSetting == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to load \"box.setting\".\n";
		return false;
	}
	buildPythonSettings();

	if (!PyObject_HasAttrString(m_box, "_clock"))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to initialize \"box._clock\" attribute because it does not exist.\n";
		return false;
	}
	//New reference
	PyObject* pyBoxClock = PyInt_FromLong(long(m_clockFrequency));
	if (pyBoxClock == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to convert m_clockFrequency into PyInt.\n";
		return false;
	}
	if (PyObject_SetAttrString(m_box, "_clock", pyBoxClock) == -1)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to initialize \"box._clock\" attribute.\n";
		return false;
	}
	Py_CLEAR(pyBoxClock);

	if (!PyObject_HasAttrString(m_box, "_currentTime"))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to initialize \"box._currentTime\" attribute because it does not exist.\n";
		return false;
	}
	//New reference
	m_boxCurrentTime = PyFloat_FromDouble(CTime(this->getPlayerContext().getCurrentTime()).toSeconds());
	if (m_boxCurrentTime == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to convert the current time into a PyFloat.\n";
		return false;
	}
	if (PyObject_SetAttrString(m_box, "_currentTime", m_boxCurrentTime) == -1)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to initialize \"box._currentTime\" attribute.\n";
		return false;
	}

	if (!PyObject_HasAttrString(m_box, "realInitialize"))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "No realInitialize.\n";
		return false;
	}

	//New reference
	m_boxInitialize = PyObject_GetAttrString(m_box, "realInitialize");
	if (m_boxInitialize == nullptr) { this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Failed to load \"box.realInitialize\" function.\n"; }
	else if (!PyCallable_Check(m_boxInitialize)) { this->getLogManager() << Kernel::LogLevel_ImportantWarning << "\"box.realInitialize\" is not callable.\n"; }

	//New reference
	m_boxProcess = PyObject_GetAttrString(m_box, "realProcess");
	if (m_boxProcess == nullptr) { this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Failed to load \"box.realProcess\" function.\n"; }
	else if (!PyCallable_Check(m_boxProcess)) { this->getLogManager() << Kernel::LogLevel_ImportantWarning << "\"box.realProcess\" is not callable.\n"; }

	//New reference
	m_boxUninitialize = PyObject_GetAttrString(m_box, "realUninitialize");
	if (m_boxUninitialize == nullptr) { this->getLogManager() << Kernel::LogLevel_ImportantWarning << "Failed to load \"box.realUninitialize\" function.\n"; }
	else if (!PyCallable_Check(m_boxUninitialize)) { this->getLogManager() << Kernel::LogLevel_ImportantWarning << "\"box.realUninitialize\" is not callable.\n"; }

	//Execute the initialize function defined in the python user script
	if (m_boxInitialize && PyCallable_Check(m_boxInitialize))
	{
		//New reference
		result           = PyObject_CallObject(m_boxInitialize, nullptr);
		bool stdoutError = logSysStdout(); // souci car la si l'init plante pas de sortie au bon endroit
		bool stderrError = logSysStderr();
		if ((result == nullptr) || (!stdoutError) || (!stderrError))
		{
			if (result == nullptr) { this->getLogManager() << Kernel::LogLevel_Error << "Failed to call \"box.__initialize\" function.\n"; }
			if (!stdoutError) { this->getLogManager() << Kernel::LogLevel_Error << "logSysStdout() failed during box.__initialization.\n"; }
			if (!stderrError) { this->getLogManager() << Kernel::LogLevel_Error << "logSysStderr() failed during box.__initialization.\n"; }
			Py_CLEAR(result);
			return false;
		}
		Py_CLEAR(result);
	}

	m_initializeSucceeded = true;
	return true;
}

bool CBoxAlgorithmPython2::uninitialize()
{
	for (size_t i = 0; i < m_decoders.size(); ++i)
	{
		m_decoders[i]->uninitialize();
		delete m_decoders[i];
	}
	m_decoders.clear();

	for (size_t i = 0; i < m_encoders.size(); ++i)
	{
		m_encoders[i]->uninitialize();
		delete m_encoders[i];
	}
	m_encoders.clear();

	if (m_initializeSucceeded)	// we call this uninit only if init had succeeded Execute the uninitialize function defined in the python script
	{	// il y a un souci ici si le script n'a pas ete charge ca ne passe pas
		if (m_boxUninitialize && PyCallable_Check(m_boxUninitialize))
		{
			//New reference
			PyObject* result       = PyObject_CallObject(m_boxUninitialize, nullptr);
			const bool stdoutError = logSysStdout();
			const bool stderrError = logSysStderr();
			if ((result == nullptr) || (!stdoutError) || (!stderrError))
			{
				if (result == nullptr) { this->getLogManager() << Kernel::LogLevel_Error << "Failed to call \"box.__uninitialize\" function.\n"; }
				if (!stdoutError) { this->getLogManager() << Kernel::LogLevel_Error << "logSysStdout() failed during box.__uninitialization.\n"; }
				if (!stderrError) { this->getLogManager() << Kernel::LogLevel_Error << "logSysStderr() failed during box.__uninitialization.\n"; }
				Py_CLEAR(result);
				return false;
			}
			Py_CLEAR(result);
		}
	}

	// Note: Py_CLEAR is safe to use on NULL pointers, so we can clean everything here
	Py_CLEAR(m_box);
	Py_CLEAR(m_boxInput);
	Py_CLEAR(m_boxOutput);
	Py_CLEAR(m_boxCurrentTime);
	Py_CLEAR(m_boxSetting);
	Py_CLEAR(m_boxInitialize);
	Py_CLEAR(m_boxProcess);
	Py_CLEAR(m_boxUninitialize);

	// Py_Initialize() and Py_Finalize() are called in ovp_main.cpp, we never uninitialize Python here

	return true;
}

bool CBoxAlgorithmPython2::processClock(Kernel::CMessageClock& msg)
{
	this->getLogManager() << Kernel::LogLevel_Trace << "Received clock message at time " << msg.getTime() << "\n";
	getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
	return true;
}

bool CBoxAlgorithmPython2::processInput(size_t /*index*/)
{
	getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
	return true;
}

bool CBoxAlgorithmPython2::transferStreamedMatrixInputChunksToPython(const size_t index)
{
	Kernel::IBoxIO& boxCtx = this->getDynamicBoxContext();

	if (!PyList_Check(m_boxInput))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "box.input must be a list.\n";
		return false;
	}

	//Borrowed reference
	PyObject* pyBuffer = PyList_GetItem(m_boxInput, Py_ssize_t(index));
	if (pyBuffer == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to get box.input[" << index << "].\n";
		return false;
	}
	//Expose input streamed matrix chunks to python
	for (size_t idx = 0; idx < boxCtx.getInputChunkCount(index); ++idx)
	{
		m_decoders[index]->decode(idx);

		if (m_decoders[index]->isHeaderReceived())
		{
			CMatrix* matrix = (dynamic_cast<Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmPython2>*>(m_decoders[index]))->getOutputMatrix();
			size_t nDim     = matrix->getDimensionCount();

			//New reference
			PyObject* pySizeDim = PyList_New(nDim);
			if (pySizeDim == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new list pySizeDim.\n";
				return false;
			}

			//New reference
			PyObject* pyLabelDim = PyList_New(0);
			if (pyLabelDim == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new list pyLabelDim.\n";
				Py_CLEAR(pySizeDim);
				return false;
			}

			for (size_t i = 0; i < nDim; ++i)
			{
				size_t dimSize = matrix->getDimensionSize(i);
				if (PyList_SetItem(pySizeDim, i, PyInt_FromLong(dimSize)) == -1)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item " << i << " in dimension size list.\n";
					Py_CLEAR(pySizeDim);
					Py_CLEAR(pyLabelDim);
					return false;
				}
				for (size_t j = 0; j < dimSize; ++j)
				{
					if (PyList_Append(pyLabelDim, PyString_FromString(matrix->getDimensionLabel(i, j))) == -1)
					{
						this->getLogManager() << Kernel::LogLevel_Error << "Failed to append \"" << matrix->getDimensionLabel(i, j) <<
								"\" in dimension label list.\n";
						Py_CLEAR(pySizeDim);
						Py_CLEAR(pyLabelDim);
						return false;
					}
				}
			}

			//New reference
			PyObject* pyArg = PyTuple_New(4);
			if (pyArg == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 0, PyFloat_FromDouble(CTime(boxCtx.getInputChunkStartTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (start time) in tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(boxCtx.getInputChunkEndTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (end time) in tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 2, pySizeDim) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 2 (dimension size) in tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 3, pyLabelDim) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 3 (dimension label) in tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			//New reference
			PyObject* pyMatrixHeader = PyObject_Call(m_matrixHeader, pyArg, nullptr);
			if (pyMatrixHeader == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new OVStreamedMatrixHeader pyMatrixHeader.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			Py_CLEAR(pySizeDim);
			Py_CLEAR(pyLabelDim);
			Py_CLEAR(pyArg);

			if (!appendToPyObject(pyMatrixHeader, pyBuffer))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to append an OVStreamedMatrixHeader to box.input[" << index << "].\n";
				Py_CLEAR(pyMatrixHeader);
				return false;
			}
			Py_CLEAR(pyMatrixHeader);
		}

		if (m_decoders[index]->isBufferReceived())
		{
			//New reference
			PyObject* pyArg = PyTuple_New(3);
			if (pyArg == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
				return false;
			}
			if (PyTuple_SetItem(pyArg, 0, PyFloat_FromDouble(CTime(boxCtx.getInputChunkStartTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (start time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(boxCtx.getInputChunkEndTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (end time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 2, PyList_New(0)) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 2 (bufferElements) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}

			//New reference
			PyObject* pyMatrixBuffer = PyObject_Call(m_matrixBuffer, pyArg, nullptr);
			if (pyMatrixBuffer == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new OVStreamedMatrixBuffer pyMatrixBuffer.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			Py_CLEAR(pyArg);

			CMatrix* matrix    = (dynamic_cast<Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmPython2>*>(m_decoders[index]))->getOutputMatrix();
			double* bufferBase = matrix->getBuffer();
			for (size_t i = 0; i < matrix->getBufferElementCount(); ++i)
			{
				if (PyList_Append(pyMatrixBuffer, PyFloat_FromDouble(bufferBase[i])) == -1)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to append element " << i << " to pyMatrixBuffer.\n";
					Py_CLEAR(pyMatrixBuffer);
					return false;
				}
			}

			if (!appendToPyObject(pyMatrixBuffer, pyBuffer))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to append an OVStreamedMatrixBuffer to box.input[" << index << "].\n";
				Py_CLEAR(pyMatrixBuffer);
				return false;
			}
			Py_CLEAR(pyMatrixBuffer);
		}

		if (m_decoders[index]->isEndReceived())
		{
			//New reference
			PyObject* pyArg = PyTuple_New(2);
			if (pyArg == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
				return false;
			}
			if (PyTuple_SetItem(pyArg, 0, PyFloat_FromDouble(CTime(boxCtx.getInputChunkStartTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (start time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(boxCtx.getInputChunkEndTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (end time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}

			//New reference
			PyObject* pyMatrixEnd = PyObject_Call(m_matrixEnd, pyArg, nullptr);
			if (pyMatrixEnd == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new OVStreamedMatrixEnd pyMatrixEnd.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			Py_CLEAR(pyArg);

			if (!appendToPyObject(pyMatrixEnd, pyBuffer))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to append an OVStreamedMatrixEnd to box.input[" << index << "].\n";
				Py_CLEAR(pyMatrixEnd);
				return false;
			}
			Py_CLEAR(pyMatrixEnd);
		}
	}

	return true;
}

bool CBoxAlgorithmPython2::transferStreamedMatrixOutputChunksFromPython(const size_t index)
{
	Kernel::IBoxIO& boxCtx  = this->getDynamicBoxContext();
	CMatrix* matrix = (dynamic_cast<Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmPython2>*>(m_encoders[index]))->getInputMatrix();

	if (!PyList_Check(m_boxOutput))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "box.output must be a list.\n";
		return false;
	}

	//Borrowed reference
	PyObject* pyBuffer = PyList_GetItem(m_boxOutput, Py_ssize_t(index));
	if (pyBuffer == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to get box.output[" << index << "].\n";
		return false;
	}

	size_t len;
	if (!getLenFromPyObject(pyBuffer, len))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to get box.output[" << index << "].__len__().\n";
		return false;
	}

	for (size_t idx = 0; idx < len; ++idx)
	{
		//New reference
		PyObject* pyChunk = PyObject_CallMethod(pyBuffer, UN_CONST("pop"), nullptr);
		if (pyChunk == nullptr)
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Failed to get item " << idx << " of box.output[" << index << "].\n";
			return false;
		}

		if (PyObject_IsInstance(pyChunk, m_matrixHeader) == 1)
		{
			if (!setMatrixInfosFromPyObject(pyChunk, matrix))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to call chunk's getDimensionCount method.\n";
				Py_CLEAR(pyChunk);
				return false;
			}

			m_encoders[index]->encodeHeader();

			uint64_t startTime, endTime;
			getTimesFromPyObject(pyChunk, startTime, endTime);

			boxCtx.markOutputAsReadyToSend(index, startTime, endTime);
		}

		else if (PyObject_IsInstance(pyChunk, m_matrixBuffer) == 1)
		{
			double* bufferBase = matrix->getBuffer();
			for (size_t i = 0; i < matrix->getBufferElementCount(); ++i) { bufferBase[i] = PyFloat_AsDouble(PyList_GetItem(pyChunk, i)); }

			uint64_t startTime, endTime;
			getTimesFromPyObject(pyChunk, startTime, endTime);

			m_encoders[index]->encodeBuffer();
			boxCtx.markOutputAsReadyToSend(index, startTime, endTime);
		}

		else if (PyObject_IsInstance(pyChunk, m_matrixEnd) == 1)
		{
			uint64_t startTime, endTime;
			getTimesFromPyObject(pyChunk, startTime, endTime);

			m_encoders[index]->encodeEnd();
			boxCtx.markOutputAsReadyToSend(index, startTime, endTime);
		}

		else
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Unexpected object type for item " << idx << " in box.output[" << index << "].\n";
			Py_CLEAR(pyChunk);
			return false;
		}

		Py_CLEAR(pyChunk);
	}
	return true;
}

bool CBoxAlgorithmPython2::transferSignalInputChunksToPython(const size_t index)
{
	Kernel::IBoxIO& boxCtx = this->getDynamicBoxContext();

	if (!PyList_Check(m_boxInput))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "box.input must be a list.\n";
		return false;
	}

	//Borrowed reference
	PyObject* pyBuffer = PyList_GetItem(m_boxInput, Py_ssize_t(index));
	if (pyBuffer == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to get box.input[" << index << "].\n";
		return false;
	}
	//Expose input signal chunks to python
	for (size_t idx = 0; idx < boxCtx.getInputChunkCount(index); ++idx)
	{
		m_decoders[index]->decode(idx);

		if (m_decoders[index]->isHeaderReceived())
		{
			CMatrix* matrix = (dynamic_cast<Toolkit::TSignalDecoder<CBoxAlgorithmPython2>*>(m_decoders[index]))->getOutputMatrix();
			size_t nDim     = matrix->getDimensionCount();

			//New reference
			PyObject* pySizeDim = PyList_New(nDim);
			if (pySizeDim == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new list pySizeDim.\n";
				return false;
			}

			//New reference
			PyObject* pyLabelDim = PyList_New(0);
			if (pyLabelDim == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new list pyLabelDim.\n";
				Py_CLEAR(pySizeDim);
				return false;
			}

			for (size_t i = 0; i < nDim; ++i)
			{
				size_t dimSize = matrix->getDimensionSize(i);
				if (PyList_SetItem(pySizeDim, i, PyInt_FromLong(dimSize)) == -1)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item " << i << " in dimension size list.\n";
					Py_CLEAR(pySizeDim);
					Py_CLEAR(pyLabelDim);
					return false;
				}
				for (size_t j = 0; j < dimSize; ++j)
				{
					if (PyList_Append(pyLabelDim, PyString_FromString(matrix->getDimensionLabel(i, j))) == -1)
					{
						this->getLogManager() << Kernel::LogLevel_Error << "Failed to append \"" << matrix->getDimensionLabel(i, j) << "\" in dimension label list.\n";
						Py_CLEAR(pySizeDim);
						Py_CLEAR(pyLabelDim);
						return false;
					}
				}
			}

			//New reference
			PyObject* pyArg = PyTuple_New(5);
			if (pyArg == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 0, PyFloat_FromDouble(CTime(boxCtx.getInputChunkStartTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (start time) in tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(boxCtx.getInputChunkEndTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (end time) in tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 2, pySizeDim) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 2 (dimension size) in tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 3, pyLabelDim) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 3 (dimension label) in tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(
					pyArg, 4, PyInt_FromLong(long((dynamic_cast<Toolkit::TSignalDecoder<CBoxAlgorithmPython2>*>(m_decoders[index]))->getOutputSamplingRate()))) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 4 (samplingRate) in tuple pyArg.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			//New reference
			PyObject* pySignalHeader = PyObject_Call(m_signalHeader, pyArg, nullptr);
			if (pySignalHeader == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new OVSignalHeader pySignalHeader.\n";
				Py_CLEAR(pySizeDim);
				Py_CLEAR(pyLabelDim);
				Py_CLEAR(pyArg);
				return false;
			}
			Py_CLEAR(pySizeDim);
			Py_CLEAR(pyLabelDim);
			Py_CLEAR(pyArg);

			if (!appendToPyObject(pySignalHeader, pyBuffer))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to append an OVSignalHeader to box.input[" << index << "].\n";
				Py_CLEAR(pySignalHeader);
				return false;
			}
			Py_CLEAR(pySignalHeader);
		}

		if (m_decoders[index]->isBufferReceived())
		{
			//New reference
			PyObject* pyArg = PyTuple_New(3);
			if (pyArg == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
				return false;
			}
			if (PyTuple_SetItem(pyArg, 0, PyFloat_FromDouble(CTime(boxCtx.getInputChunkStartTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (startTime) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(boxCtx.getInputChunkEndTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (endTime) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 2, PyList_New(0)) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 2 (bufferElements) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}

			//New reference
			PyObject* pySignalBuffer = PyObject_Call(m_signalBuffer, pyArg, nullptr);
			if (pySignalBuffer == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new OVSignalBuffer pySignalBuffer.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			Py_CLEAR(pyArg);

			CMatrix* matrix    = (dynamic_cast<Toolkit::TSignalDecoder<CBoxAlgorithmPython2>*>(m_decoders[index]))->getOutputMatrix();
			double* bufferBase = matrix->getBuffer();
			for (size_t i = 0; i < matrix->getBufferElementCount(); ++i)
			{
				if (PyList_Append(pySignalBuffer, PyFloat_FromDouble(bufferBase[i])) == -1)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to append element " << i << " to pySignalBuffer.\n";
					Py_CLEAR(pySignalBuffer);
					return false;
				}
			}

			if (!appendToPyObject(pySignalBuffer, pyBuffer))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to append an OVSignalBuffer to box.input[" << index << "].\n";
				Py_CLEAR(pySignalBuffer);
				return false;
			}
			Py_CLEAR(pySignalBuffer);
		}

		if (m_decoders[index]->isEndReceived())
		{
			//New reference
			PyObject* pyArg = PyTuple_New(2);
			if (pyArg == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
				return false;
			}
			if (PyTuple_SetItem(pyArg, 0, PyFloat_FromDouble(CTime(boxCtx.getInputChunkStartTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (start time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(boxCtx.getInputChunkEndTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (end time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}

			//New reference
			PyObject* pySignalEnd = PyObject_Call(m_signalEnd, pyArg, nullptr);
			if (pySignalEnd == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new m_signalEnd pySignalEnd.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			Py_CLEAR(pyArg);

			if (!appendToPyObject(pySignalEnd, pyBuffer))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to append an OVSignalEnd to box.input[" << index << "].\n";
				Py_CLEAR(pySignalEnd);
				return false;
			}
			Py_CLEAR(pySignalEnd);
		}
	}
	return true;
}

bool CBoxAlgorithmPython2::transferSignalOutputChunksFromPython(const size_t index)
{
	Kernel::IBoxIO& boxCtx  = this->getDynamicBoxContext();
	CMatrix* matrix = (dynamic_cast<Toolkit::TSignalEncoder<CBoxAlgorithmPython2>*>(m_encoders[index]))->getInputMatrix();

	if (!PyList_Check(m_boxOutput))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "box.output must be a list.\n";
		return false;
	}

	//Borrowed reference
	PyObject* pyBuffer = PyList_GetItem(m_boxOutput, Py_ssize_t(index));
	if (pyBuffer == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to get box.output[" << index << "].\n";
		return false;
	}

	size_t len;
	if (!getLenFromPyObject(pyBuffer, len))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to get box.output[" << index << "].__len__().\n";
		return false;
	}

	for (size_t idx = 0; idx < len; ++idx)
	{
		//New reference
		PyObject* pyChunk = PyObject_CallMethod(pyBuffer, UN_CONST("pop"), nullptr);
		if (pyChunk == nullptr)
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Failed to get item " << idx << " of box.output[" << index << "].\n";
			return false;
		}

		if (PyObject_IsInstance(pyChunk, m_signalHeader) == 1)
		{
			if (!setMatrixInfosFromPyObject(pyChunk, matrix))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to call chunk's getDimensionCount method.\n";
				Py_CLEAR(pyChunk);
				return false;
			}
			
			//New reference
			PyObject* pySampling = PyObject_GetAttrString(pyChunk, "samplingRate");
			if (pySampling == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to load signal header sampling rate.\n";
				return false;
			}
			Kernel::TParameterHandler<uint64_t>& sampling = (dynamic_cast<Toolkit::TSignalEncoder<CBoxAlgorithmPython2>*>(m_encoders[index]))->getInputSamplingRate();
			sampling                              = uint64_t(PyInt_AsLong(pySampling));
			m_encoders[index]->encodeHeader();
			Py_CLEAR(pySampling);

			uint64_t startTime, endTime;
			getTimesFromPyObject(pyChunk, startTime, endTime);

			boxCtx.markOutputAsReadyToSend(index, startTime, endTime);
		}

		else if (PyObject_IsInstance(pyChunk, m_signalBuffer) == 1)
		{
			double* bufferBase = matrix->getBuffer();
			for (size_t i = 0; i < matrix->getBufferElementCount(); ++i) { bufferBase[i] = PyFloat_AsDouble(PyList_GetItem(pyChunk, i)); }

			uint64_t startTime, endTime;
			getTimesFromPyObject(pyChunk, startTime, endTime);

			m_encoders[index]->encodeBuffer();
			boxCtx.markOutputAsReadyToSend(index, startTime, endTime);
		}

		else if (PyObject_IsInstance(pyChunk, m_signalEnd) == 1)
		{
			uint64_t startTime, endTime;
			getTimesFromPyObject(pyChunk, startTime, endTime);

			m_encoders[index]->encodeEnd();
			boxCtx.markOutputAsReadyToSend(index, startTime, endTime);
		}

		else
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Unexpected object type for item " << idx << " in box.output[" << index << "].\n";
			Py_CLEAR(pyChunk);
			return false;
		}

		Py_CLEAR(pyChunk);
	}
	return true;
}

bool CBoxAlgorithmPython2::transferStimulationInputChunksToPython(const size_t index)
{
	Kernel::IBoxIO& boxCtx = this->getDynamicBoxContext();
	//Borrowed reference
	PyObject* pyBuffer = PyList_GetItem(m_boxInput, Py_ssize_t(index));
	if (pyBuffer == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to get box.input[" << index << "].\n";
		return false;
	}
	for (size_t idx = 0; idx < boxCtx.getInputChunkCount(index); ++idx)
	{
		m_decoders[index]->decode(idx);

		if (m_decoders[index]->isHeaderReceived())
		{
			//New reference
			PyObject* pyArg = PyTuple_New(2);
			if (pyArg == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
				return false;
			}
			if (PyTuple_SetItem(pyArg, 0, PyFloat_FromDouble(CTime(boxCtx.getInputChunkStartTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (start time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(boxCtx.getInputChunkEndTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (end time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}

			PyObject* pyStimHeader = PyObject_Call(m_stimulationHeader, pyArg, nullptr);
			if (pyStimHeader == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new OVStimulationHeader pyStimHeader.\n";
				PyErr_Print();
				Py_CLEAR(pyArg);
				return false;
			}
			Py_CLEAR(pyArg);

			if (!appendToPyObject(pyStimHeader, pyBuffer))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to append an OVStimulationHeader to box.input[" << index << "].\n";
				Py_CLEAR(pyStimHeader);
				return false;
			}
			Py_CLEAR(pyStimHeader);
		}

		if (m_decoders[index]->isBufferReceived())
		{
			CStimulationSet* stimSet = (dynamic_cast<Toolkit::TStimulationDecoder<CBoxAlgorithmPython2>*>(m_decoders[index]))->getOutputStimulationSet();

			//New reference
			PyObject* pyArg = PyTuple_New(2);
			if (pyArg == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
				return false;
			}
			if (PyTuple_SetItem(pyArg, 0, PyFloat_FromDouble(CTime(boxCtx.getInputChunkStartTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (start time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(boxCtx.getInputChunkEndTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (end time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			//New reference
			PyObject* pyStimSet = PyObject_Call(m_stimulationSet, pyArg, nullptr);
			if (pyStimSet == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new OVStimulationSet pyStimSet.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			Py_CLEAR(pyArg);

			for (size_t i = 0; i < stimSet->size(); ++i)
			{
				const uint64_t id       = stimSet->getId(i);
				const uint64_t date     = stimSet->getDate(i);
				const uint64_t duration = stimSet->getDuration(i);

				//New reference
				pyArg = PyTuple_New(3);
				if (pyArg == nullptr)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
					return false;
				}
				if (PyTuple_SetItem(pyArg, 0, PyInt_FromLong(long(id))) != 0)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (identifier) in tuple pyArg.\n";
					Py_CLEAR(pyArg);
					return false;
				}
				if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(date).toSeconds())) != 0)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (date) in tuple pyArg.\n";
					Py_CLEAR(pyArg);
					return false;
				}
				if (PyTuple_SetItem(pyArg, 2, PyFloat_FromDouble(CTime(duration).toSeconds())) != 0)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 2 (duration) in tuple pyArg.\n";
					Py_CLEAR(pyArg);
					return false;
				}
				//New reference
				PyObject* pyStim = PyObject_Call(m_stimulation, pyArg, nullptr);
				if (pyStim == nullptr)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new OVStimulation pyStim.\n";
					Py_CLEAR(pyArg);
					return false;
				}
				Py_CLEAR(pyArg);

				if (!appendToPyObject(pyStim, pyStimSet))
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to append stimulation to box.input[" << index << "].\n";
					Py_CLEAR(pyStim);
					return false;
				}
				Py_CLEAR(pyStim);
			}

			if (!appendToPyObject(pyStimSet, pyBuffer))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to append stimulation to box.input[" << index << "].\n";
				Py_CLEAR(pyStimSet);
				return false;
			}
			Py_CLEAR(pyStimSet);
		}

		if (m_decoders[index]->isEndReceived())
		{
			//New reference
			PyObject* pyArg = PyTuple_New(2);
			if (pyArg == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new tuple pyArg.\n";
				return false;
			}
			if (PyTuple_SetItem(pyArg, 0, PyFloat_FromDouble(CTime(boxCtx.getInputChunkStartTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 0 (start time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			if (PyTuple_SetItem(pyArg, 1, PyFloat_FromDouble(CTime(boxCtx.getInputChunkEndTime(index, idx)).toSeconds())) != 0)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to set item 1 (end time) in tuple pyArg.\n";
				Py_CLEAR(pyArg);
				return false;
			}

			//New reference
			PyObject* pyStimEnd = PyObject_Call(m_stimulationEnd, pyArg, nullptr);
			if (pyStimEnd == nullptr)
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to create a new OVStimulationEnd pyStimEnd.\n";
				Py_CLEAR(pyArg);
				return false;
			}
			Py_CLEAR(pyArg);

			if (!appendToPyObject(pyStimEnd, pyBuffer))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to append an OVStimulationEnd to box.input[" << index << "].\n";
				Py_CLEAR(pyStimEnd);
				return false;
			}
			Py_CLEAR(pyStimEnd);
		}
	}
	return true;
}

bool CBoxAlgorithmPython2::transferStimulationOutputChunksFromPython(const size_t index)
{
	Kernel::IBoxIO& boxCtx           = this->getDynamicBoxContext();
	CStimulationSet* stimSet = (dynamic_cast<Toolkit::TStimulationEncoder<CBoxAlgorithmPython2>*>(m_encoders[index]))->getInputStimulationSet();

	if (!PyList_Check(m_boxOutput))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "box.output must be a list.\n";
		return false;
	}
	//Borrowed reference
	PyObject* pyBuffer = PyList_GetItem(m_boxOutput, Py_ssize_t(index));
	if (pyBuffer == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to get box.output[" << index << "].\n";
		return false;
	}

	size_t len;
	if (!getLenFromPyObject(pyBuffer, len))
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to get box.output[" << index << "].__len__().\n";
		return false;
	}

	for (size_t idx = 0; idx < len; ++idx)
	{
		//New reference
		PyObject* pyChunk = PyObject_CallMethod(pyBuffer, UN_CONST("pop"), nullptr);
		if (pyChunk == nullptr)
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Failed to get item " << idx << " of box.output[" << index << "].\n";
			return false;
		}

		if (PyObject_IsInstance(pyChunk, m_stimulationHeader) == 1)
		{
			uint64_t startTime, endTime;
			getTimesFromPyObject(pyChunk, startTime, endTime);

			stimSet->resize(0);
			m_encoders[index]->encodeHeader();
			boxCtx.markOutputAsReadyToSend(index, startTime, endTime);
		}

		else if (PyObject_IsInstance(pyChunk, m_stimulationSet) == 1)
		{
			size_t chunkLen;
			if (!getLenFromPyObject(pyChunk, chunkLen))
			{
				this->getLogManager() << Kernel::LogLevel_Error << "Failed to get stimulations set length.\n";
				return false;
			}

			stimSet->resize(0);
			for (size_t i = 0; i < chunkLen; ++i)
			{
				//Borrowed reference
				PyObject* pyStim = PyList_GetItem(pyChunk, Py_ssize_t(i));
				if (pyStim == nullptr)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Failed to get item " << i << " of chunk " << idx << ".\n";
					return false;
				}
				if (PyObject_IsInstance(pyStim, m_stimulation) < 1)
				{
					this->getLogManager() << Kernel::LogLevel_Error << "Item " << i << " is not an OVStimulation.\n";
					return false;
				}
				//New reference
				PyObject* pyID    = PyObject_GetAttrString(pyStim, "identifier");
				const uint64_t id = uint64_t(PyFloat_AsDouble(pyID));
				Py_CLEAR(pyID);

				uint64_t date, duration;
				getTimeFromPyObject(pyStim, "date", date);
				getTimeFromPyObject(pyStim, "duration", duration);

				stimSet->push_back(id, date, duration);
			}

			uint64_t startTime, endTime;
			getTimesFromPyObject(pyChunk, startTime, endTime);

			m_encoders[index]->encodeBuffer();
			boxCtx.markOutputAsReadyToSend(index, startTime, endTime);
		}

		else if (PyObject_IsInstance(pyChunk, m_stimulationEnd) == 1)
		{
			uint64_t startTime, endTime;
			getTimesFromPyObject(pyChunk, startTime, endTime);

			m_encoders[index]->encodeEnd();
			boxCtx.markOutputAsReadyToSend(index, startTime, endTime);
		}

		else
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Unexpected object type for item " << idx << " in box.output[" << index << "].\n";
			Py_CLEAR(pyChunk);
			return false;
		}

		Py_CLEAR(pyChunk);
	}

	return true;
}

bool CBoxAlgorithmPython2::process()
{
	const Kernel::IBox& boxCtx = this->getStaticBoxContext();
	CIdentifier typeID;

	for (size_t i = 0; i < boxCtx.getInputCount(); ++i)
	{
		boxCtx.getInputType(i, typeID);
		if (typeID == OV_TypeId_StreamedMatrix) { if (!transferStreamedMatrixInputChunksToPython(i)) { return false; } }
		else if (typeID == OV_TypeId_Signal) { if (!transferSignalInputChunksToPython(i)) { return false; } }
		else if (typeID == OV_TypeId_Stimulations) { if (!transferStimulationInputChunksToPython(i)) { return false; } }
			//else if (typeID == OV_TypeId_FeatureVector) { }
			//else if (typeID == OV_TypeId_Spectrum) { }
			//else if (typeID == OV_TypeId_ChannelLocalisation) { }
			//else if (typeID == OV_TypeId_ExperimentInfo) { }
		else
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Codec to decode " << typeID.str() << " is not implemented.\n";
			return false;
		}
	}

	//update the python current time
	m_boxCurrentTime = PyFloat_FromDouble(CTime(this->getPlayerContext().getCurrentTime()).toSeconds());
	if (m_boxCurrentTime == nullptr)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to convert the current time into a PyFloat during update.\n";
		return false;
	}
	if (PyObject_SetAttrString(m_box, "_currentTime", m_boxCurrentTime) == -1)
	{
		this->getLogManager() << Kernel::LogLevel_Error << "Failed to update \"box._currentTime\" attribute.\n";
		return false;
	}

	//call the python process function
	if (m_boxProcess && PyCallable_Check(m_boxProcess))
	{
		//New reference
		PyObject* result       = PyObject_CallObject(m_boxProcess, nullptr);
		const bool stdoutError = logSysStdout();
		const bool stderrError = logSysStderr();
		if ((result == nullptr) || (!stdoutError) || (!stderrError))
		{
			if (result == nullptr) { this->getLogManager() << Kernel::LogLevel_Error << "Failed to call \"box.__process\" function.\n"; }
			if (!stdoutError) { this->getLogManager() << Kernel::LogLevel_Error << "logSysStdout() failed during box.__process.\n"; }
			if (!stderrError) { this->getLogManager() << Kernel::LogLevel_Error << "logSysStderr() failed during box.__process.\n"; }
			Py_CLEAR(result);
			return false;
		}
		Py_CLEAR(result);
	}

	for (size_t i = 0; i < boxCtx.getOutputCount(); ++i)
	{
		boxCtx.getOutputType(i, typeID);
		if (typeID == OV_TypeId_StreamedMatrix) { if (!transferStreamedMatrixOutputChunksFromPython(i)) { return false; } }
		else if (typeID == OV_TypeId_Signal) { if (!transferSignalOutputChunksFromPython(i)) { return false; } }
		else if (typeID == OV_TypeId_Stimulations) { if (!transferStimulationOutputChunksFromPython(i)) { return false; } }
			//else if (typeID == OV_TypeId_FeatureVector) { }
			//else if (typeID == OV_TypeId_Spectrum) { }
			//else if (typeID == OV_TypeId_ChannelLocalisation) { }
			//else if (typeID == OV_TypeId_ExperimentInfo) { }
		else
		{
			this->getLogManager() << Kernel::LogLevel_Error << "Codec to encode " << typeID.str() << " is not implemented.\n";
			return false;
		}
	}

	return true;
}

}  // namespace Python
}  // namespace Plugins
}  // namespace OpenViBE
#endif // #if defined(PY_MAJOR_VERSION) && (PY_MAJOR_VERSION == 2)
#endif // TARGET_HAS_ThirdPartyPython
#endif
