

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of PSYCLONE version 2.0.0.170915_Beta4 (15 September 2017)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2017
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  PSYCLONE 2.0 is released under the GNU Lesser General Public License 
//_/_/  (LGPL) version 3.0, and is subject to the following
//_/_/  
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/    
//_/_/  A commercial license for PSYCLONE 2.0 is also available to 
//_/_/  any interested party from the copyright holder.
//_/_/  
//_/_/  You can redistribute this copy of PSYCLONE 2.0 and/or modify it under 
//_/_/  the terms of the GNU General Public License as published by the Free 
//_/_/  Software Foundation, either version 3 of the License, or (at your 
//_/_/  option) any later version.
//_/_/  
//_/_/  You should have received a copy of the GNU General Public License
//_/_/  and the GNU Lesser General Public License along with PSYCLONE 2.0.  
//_/_/  If not, see <http://www.gnu.org/licenses/>.
//_/_/  
//_/_/  PSYCLONE 2.0 is distributed in the hope that it will be useful,
//_/_/  but WITHOUT ANY WARRANTY; without even the implied warranty of
//_/_/  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//_/_/  GNU General Public License for more details.
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#ifdef _DEBUG
	#include "cmsdk2debug_wrap.cxx"
#else
	#include "cmsdk2_wrap.cxx"
#endif
#include "Python2Link.h"

int python() {
	//PyObject *pName, *pModule, *pDict, *pFunc;
	//PyObject *pArgs, *pValue;
	//int i;

	//if (argc < 3) {
	//	fprintf(stderr, "Usage: call pythonfile funcname [args]\n");
	//	return 1;
	//}

	//Py_Initialize();
	//pName = PyUnicode_DecodeFSDefault(argv[1]);
	///* Error checking of pName left out */

	//pModule = PyImport_Import(pName);
	//Py_DECREF(pName);

	//if (pModule != NULL) {
	//	pFunc = PyObject_GetAttrString(pModule, argv[2]);
	//	/* pFunc is a new reference */

	//	if (pFunc && PyCallable_Check(pFunc)) {
	//		pArgs = PyTuple_New(argc - 3);
	//		for (i = 0; i < argc - 3; ++i) {
	//			pValue = PyLong_FromLong(atoi(argv[i + 3]));
	//			if (!pValue) {
	//				Py_DECREF(pArgs);
	//				Py_DECREF(pModule);
	//				fprintf(stderr, "Cannot convert argument\n");
	//				return 1;
	//			}
	//			/* pValue reference stolen here: */
	//			PyTuple_SetItem(pArgs, i, pValue);
	//		}
	//		pValue = PyObject_CallObject(pFunc, pArgs);
	//		Py_DECREF(pArgs);
	//		if (pValue != NULL) {
	//			printf("Result of call: %ld\n", PyLong_AsLong(pValue));
	//			Py_DECREF(pValue);
	//		}
	//		else {
	//			Py_DECREF(pFunc);
	//			Py_DECREF(pModule);
	//			PyErr_Print();
	//			fprintf(stderr, "Call failed\n");
	//			return 1;
	//		}
	//	}
	//	else {
	//		if (PyErr_Occurred())
	//			PyErr_Print();
	//		fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
	//	}
	//	Py_XDECREF(pFunc);
	//	Py_DECREF(pModule);
	//}
	//else {
	//	PyErr_Print();
	//	fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
	//	return 1;
	//}
	Py_Finalize();
	return 0;
}

namespace	cmlabs {

	// Internal module for calling Python
	// Can call python with either script="mycode.py" or embedded inline python code
	int8 Python2Link(PsyAPI* api) {
		PyObject *pModule, *pFunc;
		//PyObject *pArgs, *pValue;
	//	int i;

		std::string name = api->getModuleName();
		std::string language = api->getCurrentScriptLanguage();
		std::string scriptFile = api->getCurrentScriptFilename();
		std::string script = api->getCurrentScript();

		//printf("-----------------------\n");

		std::string cmsdkName = "cmsdk2";
		// Importing correct cmsdk2 library
		#ifdef _DEBUG
				cmsdkName += "debug";
		#endif
		#ifdef WINDOWS
			std::string pylibfile = utils::StringFormat("_%s.pyd", cmsdkName.c_str());
		#else
			std::string pylibfile = utils::StringFormat("_%s.so", cmsdkName.c_str());
		#endif

		// Check that we have the right information
		if (stricmp(language.c_str(), "python2") != 0) {
			api->logPrint(0, "Unsupported script language: %s", language.c_str());
			return -1;
		}

		if (!scriptFile.length() && !script.length()) {
			api->logPrint(0, "No script or script file provided");
			return -1;
		}

		// Get the original signal handler
		
		#ifdef WINDOWS
			_crt_signal_t origSignal = signal(SIGINT, SIG_GET);
		#else
			sighandler_t origSignal = signal(SIGINT, SIG_IGN);
		#endif
		if (origSignal != SIG_IGN) {
			//api->logPrint(0, "Saved original signal");
		}
		else
			api->logPrint(0, "Saved ignore signal");

		bool isFirstThread = false;
		// Check that the script engine is setup and ready
		if (!Py_IsInitialized()) {
			Py_Initialize();
			int argc = 0;
			char** argv = utils::SplitCommandline("MyProgram", argc);
			PySys_SetArgv(argc, argv);
			isFirstThread = true;
			PyEval_InitThreads();
		}

		utils::FileDetails fileDetails;
		std::string path;
		std::string file;
		std::string cmsdkpyd;
		int ret;
		 
		std::string libpath = api->getParameterString("libpath");
		if (!libpath.length())
			libpath = api->getCommandlineBasedir();

		if (!utils::TextEndsWith(libpath.c_str(), "/") && !utils::TextEndsWith(libpath.c_str(), "\\"))
			libpath += "/";

		PyGILState_STATE gstate;
		gstate = PyGILState_Ensure();

		if (scriptFile.length()) {
			fileDetails = utils::GetFileDetails(scriptFile.c_str());
			if (!fileDetails.doesExist || fileDetails.isDirectory || !fileDetails.isReadable) {
				api->logPrint(0, "Script file '%s' is not accessible", scriptFile.c_str());
				return -1;
			}
			path = utils::GetFilePath(scriptFile.c_str());
			file = utils::GetFileBasename(scriptFile.c_str());
			utils::StringSingleReplace(file, ".py", "", false);
			if (libpath.length())
				cmsdkpyd = utils::StringFormat("%s%s", libpath.c_str(), pylibfile.c_str());
			else
				cmsdkpyd = utils::StringFormat("%s%s", path.c_str(), pylibfile.c_str());
			fileDetails = utils::GetFileDetails(cmsdkpyd.c_str());
			if (!fileDetails.doesExist || fileDetails.isDirectory || !fileDetails.isReadable) {
				cmsdkpyd = utils::StringFormat("%s%s", path.c_str(), pylibfile.c_str());
				fileDetails = utils::GetFileDetails(cmsdkpyd.c_str());
				if (!fileDetails.doesExist || fileDetails.isDirectory || !fileDetails.isReadable) {
					if (libpath.length() && path.length())
						api->logPrint(0, "CMSDK Python library file '%s' is not accessible in '%s' or '%s'", pylibfile.c_str(), libpath.c_str(), path.c_str());
					else if (path.length())
						api->logPrint(0, "CMSDK Python library file '%s' is not accessible in '%s'", pylibfile.c_str(), path.c_str());
					else if (libpath.length())
						api->logPrint(0, "CMSDK Python library file '%s' is not accessible in '%s'", pylibfile.c_str(), libpath.c_str());
					else
						api->logPrint(0, "CMSDK Python library file '%s' is not accessible in the current directory", pylibfile.c_str());
					return -1;
				}
			}

			ret = PyRun_SimpleString("import sys, signal");
			if (ret != 0) {
				api->logPrint(0, "Error running commands in Python");
				return -1;
			}

			if (path.length()) {
				utils::StringSingleReplace(path, "\\", "/", false);
				ret = PyRun_SimpleString(utils::StringFormat("sys.path.append(\"%s\")", path.c_str()).c_str());
				if (ret != 0) {
					api->logPrint(0, "Script file path '%s' could not be found", path.c_str());
					return -1;
				}
			}

			if (libpath.length() && (strcmp(path.c_str(), libpath.c_str()) != 0)) {
				utils::StringSingleReplace(libpath, "\\", "/", false);
				ret = PyRun_SimpleString(utils::StringFormat("sys.path.append(\"%s\")", libpath.c_str()).c_str());
				if (ret != 0) {
					api->logPrint(0, "Script libpath '%s' could not be found", libpath.c_str());
					return -1;
				}
			}

			//ret = PyRun_SimpleString("original_sigint_handler = signal.getsignal(signal.SIGINT)");
			//if (ret != 0) {
			//	api->logPrint(0, "Error getting signal SIGINT");
			//}

			if (!libpath.length() && !path.length())
				api->logPrint(1, "Script file path and libpath is not provided, looking in current dir only");

			ret = PyRun_SimpleString(utils::StringFormat("import %s as cmsdk", cmsdkName.c_str()).c_str());
			if (ret != 0) {
				api->logPrint(0, "Error loading library '%s' into Python", cmsdkName.c_str());
				return -1;
			}

			PyObject* cmsdk_module = PyImport_ImportModule(cmsdkName.c_str());
			if (!cmsdk_module) {
				api->logPrint(0, "Couldn't load python CMSDK module '%s' in script path '%s'", cmsdkName.c_str(), path.c_str());
				if (PyErr_Occurred())
				{
					PyErr_Print();
					PyErr_Clear();
				}
				return -1;
			}
			pModule = PyImport_ImportModule(file.c_str());
			if (pModule == NULL) {
				api->logPrint(0, "Couldn't load python script file '%s' in script path '%s'", file.c_str(), path.c_str());
				if (PyErr_Occurred())
				{
					PyErr_Print();
					PyErr_Clear();
				}
				return -1;
			}
			PyObject_SetAttrString(pModule, "cmsdk", cmsdk_module);
			Py_XDECREF(cmsdk_module);

			if (pModule != NULL) {
				pFunc = PyObject_GetAttrString(pModule, "PsyCrank");
				/* pFunc is a new reference */

				if (pFunc && PyCallable_Check(pFunc)) {
				//	pArgs = PyTuple_New(1);
				//	pValue = PyLong_FromUnsignedLong((unsigned long)(uint64)api);
					/* pValue reference stolen here: */
				//	PyTuple_SetItem(pArgs, 0, pValue);

					//PyObject *argobj = SWIG_NewPointerObj((void*)api, SWIGTYPE_p_cmlabs__PsyAPI, 0);
					try {
						//PyObject_CallObject(pFunc, pArgs);
						//PyObject *arglist = Py_BuildValue("(O)", argobj);             // Build argument list
						//PyObject_CallObject(pFunc, arglist);     // Call Python
						PyObject_CallFunction(pFunc, "K", (unsigned long long)api);
						if (PyErr_Occurred())
						{
							PyErr_Print();
							PyErr_Clear();
						}
						//Py_DECREF(arglist);
					}
					catch (...) {
						api->logPrint(0, "Error running crank");
					}
					api->logPrint(0, "Python2 crank finished");
					//Py_DECREF(argobj);
				}
				//Py_DECREF(pValue);
				//Py_DECREF(pArgs);
				Py_DECREF(pFunc);
				Py_DECREF(pModule);
			}
		}
		else { // we are running the script provided inline
			// inject the script top
			std::string fullScript = utils::StringFormat(
				"import %s as cmsdk\n"
				"def PsyCrank(apilink):\n"
				"\tapi = cmsdk.PsyAPI.fromPython(apilink)\n"
				"%s", cmsdkName.c_str(), utils::TextIndent(script.c_str(), "\t").c_str());

			//printf("%s\n", fullScript.c_str());

			cmsdkpyd = utils::StringFormat("%s%s", libpath.c_str(), pylibfile.c_str());

			fileDetails = utils::GetFileDetails(cmsdkpyd.c_str());
			if (!fileDetails.doesExist || fileDetails.isDirectory || !fileDetails.isReadable) {
				api->logPrint(0, "CMSDK Python library file '%s' is not accessible", cmsdkpyd.c_str());
				return -1;
			}

			ret = PyRun_SimpleString("import sys, signal");
			if (ret != 0) {
				api->logPrint(0, "Error running commands in Python");
				return -1;
			}

			if (libpath.length()) {
				utils::StringSingleReplace(libpath, "\\", "/", false);
				ret = PyRun_SimpleString(utils::StringFormat("sys.path.append(\"%s\")", libpath.c_str()).c_str());
				if (ret != 0) {
					api->logPrint(0, "Script libpath '%s' could not be found", libpath.c_str());
					return -1;
				}
			}
			else
				api->logPrint(1, "Script libpath is not provided, looking in current dir only");

			//ret = PyRun_SimpleString("original_sigint_handler = signal.getsignal(signal.SIGINT)");
			//if (ret != 0) {
			//	api->logPrint(0, "Error getting signal SIGINT");
			//}

			PyObject* pBinary = Py_CompileString(fullScript.c_str(), "PsyCrankCode", Py_file_input);
			if (!pBinary) {
				api->logPrint(0, "Inline crank script could not be compiled - please check code:\n%s\n", fullScript.c_str());
				return -1;
			}
			pModule = PyImport_ExecCodeModule("PsyCrankCode", pBinary);
			Py_DECREF(pBinary);

			if (pModule != NULL) {
				pFunc = PyObject_GetAttrString(pModule, "PsyCrank");
				/* pFunc is a new reference */

				if (pFunc && PyCallable_Check(pFunc)) {
				//	pArgs = PyTuple_New(1);
				//	pValue = PyLong_FromUnsignedLong((unsigned long)api);
					/* pValue reference stolen here: */
				//	PyTuple_SetItem(pArgs, 0, pValue);

					try {
						PyObject_CallFunction(pFunc, "K", (unsigned long long)api);
						if (PyErr_Occurred())
						{
							PyErr_Print();
							PyErr_Clear();
						}
					}
					catch (...) {
						api->logPrint(0, "Error running crank");
					}
				}
				api->logPrint(0, "Python2 inline crank finished");
				//Py_DECREF(pValue);
				//Py_DECREF(pArgs);
				Py_DECREF(pFunc);
				Py_DECREF(pModule);
			}
		}

		//ret = PyRun_SimpleString("if (original_sigint_handler != signal.SIG_IGN):\n\tsignal.signal(signal.SIGINT, original_sigint_handler)");
		//if (ret != 0) {
		//	api->logPrint(0, "Error restoring signal SIGINT");
		//}

		/* Release the thread. No Python API allowed beyond this point. */
		PyGILState_Release(gstate);

		Py_Finalize();

		if (origSignal != SIG_IGN)
			signal(SIGINT, origSignal);

		return 0;
	}


} // namespace cmlabs
