#include "StdAfx.h"
#include "ScriptExecutor.h"
#include <fstream>
#include <io.h>
#include <fcntl.h>

CScriptExecutor::CScriptExecutor(void)
{
	m_hWorkThread = NULL;
	m_hWorkEvent = NULL;
	m_hLogThread = NULL;
	m_hLogEvent = NULL;
	m_bStatus = FALSE;
	m_nRunType = 0;
	m_hStdOutRead = NULL;
	m_hStdOutWrite = NULL;
	m_hStdErrorWrite = NULL;
	m_hOldStdOutWrite = NULL;

	memset(m_szScriptPath, 0, MAX_PATH);
	memset(m_szLogPath, 0, MAX_PATH);
	memset(m_szExtraSentence, 0, MAX_PATH);

	memset(&m_si, 0, sizeof(m_si));
	memset(&m_pi, 0, sizeof(m_pi));
	m_si.cb = sizeof(m_si);

	m_bKillProcess = FALSE;
	m_bStopLogExport = FALSE;
	m_dwThreadId = 0;
	m_dwSubprocessId = 0;
	m_bRuned = FALSE;
}

CScriptExecutor::~CScriptExecutor(void)
{
}

DWORD CScriptExecutor::_WorkerThread(LPVOID lpParam)
{
	CScriptExecutor* that = (CScriptExecutor*)lpParam;
	if ( !that )
	{
		printf("Error:参数失效\n");
		return -1;
	}

	if ( that->m_nRunType == EMBEDDED )
	{
		that->RedirectProcessStdout();
		ResumeThread(that->m_hLogThread);
		that->RunEmbeddedScript();
	}
	else if (that->m_nRunType == SUBPROCESS)
	{
		that->RunScripProcess();
	}

	CloseHandle(that->m_hWorkThread);
	that->m_hWorkThread = NULL;

	// 结束日志线程;
	that->m_bStopLogExport = TRUE;

	return 0;
}

DWORD CScriptExecutor::_LogExportThread(LPVOID lpParam)
{
	CScriptExecutor* that = (CScriptExecutor*)lpParam;
	if (!that)
	{
		printf("Error:参数失效\n");
		return -1;
	}

	if (that->m_nRunType == SUBPROCESS)
	{
		DWORD dwRead;
		CHAR chBuf[BUFSIZE] = {0};
		BOOL bSuccess = FALSE;
		
		do
		{
			bSuccess = ReadFile(that->m_hStdOutRead, chBuf, BUFSIZE, &dwRead, NULL);
			if (!bSuccess || dwRead == 0 || !_tcslen(chBuf)) 
				continue;

			Global::WritePythonLog(that->m_szLogPath, chBuf);
			memset(chBuf, 0, BUFSIZE);
		} while (!that->m_bStopLogExport);
	}
	else if ( that->m_nRunType == EMBEDDED)
	{
		DWORD dwRead;
		CHAR chBuf[BUFSIZE] = {0};
		BOOL bSuccess = FALSE;

		do
		{	
			bSuccess = ReadFile(that->m_hStdOutRead, chBuf, BUFSIZE, &dwRead, NULL);
			if (!bSuccess || dwRead == 0 || !_tcslen(chBuf)) 
				continue;

			Global::WritePythonLog(that->m_szLogPath, chBuf);
			memset(chBuf, 0, BUFSIZE);
		} while (!that->m_bStopLogExport);
	}

	// 关闭线程句柄;
	CloseHandle(that->m_hLogThread);
	that->m_hLogThread = NULL;

	// 结束重定向;
	that->EndSubprocessStdOut();

	return 0;
}

int CScriptExecutor::RedirectSubprocessStdout(LPSTARTUPINFO si /*=NULL*/)
{
#ifdef _DEBUG
	OutputDebugString("RedirectSubprocessStdout\n");
#endif

	// Python脚本中,必须使用sys.__stdout__()
	if (m_nRunType == SUBPROCESS)
	{
		SECURITY_ATTRIBUTES sa;
		sa.bInheritHandle = TRUE;
		sa.lpSecurityDescriptor = NULL;
		sa.nLength = sizeof(sa);

		// 创建stdout的管道;
		if (!CreatePipe(&m_hStdOutRead, &m_hStdOutWrite, &sa, 0))
		{
			OutputDebugString("Error:创建stdout管道失败\n");
			return -1;
		}

		// 创建stderr的管道,由于stderr一般就是stdout,直接复制句柄;
		if (!DuplicateHandle(GetCurrentProcess(), m_hStdOutWrite, GetCurrentProcess(), &m_hStdErrorWrite, 0, TRUE, DUPLICATE_SAME_ACCESS))
		{
			OutputDebugString("创建stderr管道失败\n");
			return -2;
		}

		si->dwFlags |= STARTF_USESTDHANDLES;
		// 将子进程的stdout输出到句柄hStdOutWrite;
		si->hStdOutput = m_hStdOutWrite;
		// 将子进程的stderr输出到句柄hStdErrWrite;
		si->hStdError = m_hStdErrorWrite;

		return 0;
	}

	// 异常类型;
	OutputDebugString("Error:异常类型\n");

	return -5;
}

int CScriptExecutor::RedirectProcessStdout()
{
#ifdef _DEBUG
	OutputDebugString("RedirectProcessStdout\n");
#endif
	if (m_nRunType == EMBEDDED)
	{
		// 创建stdout的管道;
		if (!CreatePipe(&m_hStdOutRead, &m_hStdOutWrite, NULL, 0))
		{
			printf("Error:创建stdout管道失败\n");
			return -1;
		}

		// 将 C 运行时文件描述符与现有操作系统文件句柄关联;
		int wpfd = _open_osfhandle((intptr_t)m_hStdOutWrite, _O_TEXT);
		// 为stdout分配新文件描述符;
		if (_dup2(wpfd, _fileno(stdout)) != 0)
		{
			printf("Error:dup2调用失败");
			return -2;
		}
		_close(wpfd);

		// 备份进程原始标准输出;
		m_hOldStdOutWrite = GetStdHandle(STD_OUTPUT_HANDLE);
		// 设备进程标准输出,重定向到管道中;
		if (!SetStdHandle(STD_OUTPUT_HANDLE, m_hStdOutWrite))
		{
			printf("Error:重定向失败\n");
			return -3;
		}

		// 设置标准流不使用缓冲,即时写入;
		setvbuf(stdout, NULL, _IONBF, 0);

		return 0;
	}

	// 异常类型;
	printf("Error:异常类型\n");

	return -5;
}

void CScriptExecutor::CatchPythonException()
{
	if (!Py_IsInitialized())
	{
		printf("Error:未初始化Python环境\n");
		return;
	}

	if (!PyErr_Occurred())
	{
		printf("Error:没有异常产生\n");
		return;
	}

	// 捕获异常;
	const char* pszErrMsg = NULL;
	TCHAR* pszTraceback = NULL;

	PyObject* pPyType = NULL;
	PyObject* pPyValue = NULL;
	PyObject* pPyTraceback = NULL;
	// 非控制台,使用PyErr_Fetch捕获异常;
	PyErr_Fetch(&pPyType, &pPyValue, &pPyTraceback);
	PyErr_NormalizeException(&pPyType, &pPyValue, &pPyTraceback); // 可有可无,不影响获取异常;
	if (pPyValue)
	{
		PyObject* pPyStr = PyObject_Str(pPyValue);
		if (PyString_Check(pPyStr))
		{
			pszErrMsg = PyString_AsString(pPyStr);
			if (pszErrMsg)
				printf("Error Info=>%s\n", pszErrMsg);

			if (pPyTraceback)
			{
				PyObject* pPyTraceModule = PyImport_ImportModule("traceback");
				if (!pPyTraceModule)
				{
					printf("Error:导入traceback模块失败\n");
					return;
				}
#if 1 // 细分出所有换行符;
				PyObject* pPyModuleDict = PyModule_GetDict(pPyTraceModule);
				if (pPyModuleDict)
				{
					PyObject* pPyFunc = PyDict_GetItemString(pPyModuleDict, "format_exception");
					if (!pPyFunc || !PyCallable_Check(pPyFunc))
					{
						printf("Error:加载format_exception失败\n");
						return;
					}

					PyObject* pErroList = PyObject_CallFunctionObjArgs(pPyFunc, pPyType, pPyValue, pPyTraceback, NULL);
					if (pErroList)
					{
						int nSize = PyList_Size(pErroList);
						for (int i = 0; i < nSize; i++)
						{
							pszErrMsg = PyString_AsString(PyList_GetItem(pErroList, i));
							printf("%s", pszErrMsg);
						}
					}
					Py_DECREF(pErroList);
				}

				Py_XDECREF(pPyTraceModule);
#else // 不细分Item;
				PyObject* pPyFunc = PyObject_GetAttrString(pPyTraceModule, "format_exception");
				if (!pPyFunc || !PyCallable_Check(pPyFunc))
				{
					printf("Error:加载format_exception失败\n");
					return;
				}

				PyObject* pPyResult = PyObject_CallFunctionObjArgs(pPyFunc, pPyType, pPyValue, pPyTraceback, NULL);
				pPyStr = PyObject_Str(pPyResult);
				pszErrMsg = PyString_AsString(pPyStr);
				if (pszErrMsg)
					printf("%s\n", pszErrMsg);

				Py_DECREF(pPyResult);
				Py_XDECREF(pPyTraceModule);
#endif
			}
		}
	}

	Py_XDECREF(pPyType);
	Py_XDECREF(pPyValue);
	Py_XDECREF(pPyTraceback);
}

int CScriptExecutor::RunEmbeddedScript()
{
	// 参数有效性判断;
	if (!PathFileExists(m_szScriptPath))
	{
		printf("Error:脚本文件不存在\n");
		return -1;
	}

	// 初始化Python环境;
	if (!Py_IsInitialized())
		Py_Initialize();

	if (!Py_IsInitialized())
	{
		printf("Error:初始化Python环境失败\n");
		return -2;
	}

	// 解析脚本路径和脚本名称;
	std::string scriptdir;
	TCHAR szDrive[_MAX_DRIVE] = { 0 };
	TCHAR szDir[_MAX_DIR] = { 0 };
	TCHAR szExt[_MAX_EXT] = { 0 };
	TCHAR szFilename[_MAX_FNAME] = { 0 };
	TCHAR szScriptDir[MAX_PATH] = { 0 };
	_tsplitpath_s(m_szScriptPath, szDrive, szDir, szFilename, szExt);
	_stprintf_s(szScriptDir, _T("%s%s"), szDrive, szDir);
	// 缓存一份路径;
	scriptdir = szScriptDir;
	// 将'\'转换成'/', Python才设置运行目录成功;
	int len = _tcslen(szScriptDir);
	for (int i = 0; i < len; i++)
	{
		if (szScriptDir[i] == '\\')
			szScriptDir[i] = '/';
	}
	//szScriptDir[len-1] = '\0';

	TCHAR szExecuteDir[MAX_PATH] = { 0 };
	_stprintf_s(szExecuteDir, _T("sys.path.append(\"%s\")"), szScriptDir);
	// 添加系统模块,并指定当前脚本路径为运行路径;
	PyRun_SimpleString("import sys");
	PyRun_SimpleString(szExecuteDir);
	// 运行额外的语句,一般用于传递命令行参数;
	if (_tcslen(m_szExtraSentence))
		PyRun_SimpleString(m_szExtraSentence);

	// 注意：脚本名称尽量不要与系统目录其他py文件相同;
	// 导入脚本，以脚本名称为模块名加载;
	PyObject* pModuleObj = PyImport_ImportModule(szFilename);
	if (!pModuleObj)
	{
		printf("Error:加载脚本模块失败\n");
		Py_Finalize();
		return -3;
	}

	// 获取脚本的主函数名称(规定所有脚本的主函数名为main)
	// 加载函数：注意，如果目录下有同名的脚本文件，可能会加载失败;
	PyObject* pFunction = PyObject_GetAttrString(pModuleObj, "main");
	if (!pFunction || !PyCallable_Check(pFunction))
	{
		printf("Error:加载函数失败\n");
		Py_Finalize();
		return -4;
	}

	m_bRuned = TRUE; 
	// 执行main函数;
	PyObject* pResult = PyObject_CallObject(pFunction, NULL);
	if (!pResult)
	{
		printf("Error:执行函数失败\n");
		CatchPythonException();
		Py_Finalize();
		
		return -5;
	}

	Py_DECREF(pResult);

	Py_Finalize();

	return 0;
}

int CScriptExecutor::RunScripProcess()
{
	if (!PathFileExists(m_szScriptPath))
	{
		printf("Error:脚本路径无效\n");
		return -1;
	}

	// 初始化参数;
	::memset(&m_si, 0, sizeof(m_si));
	::memset(&m_pi, 0, sizeof(m_pi));
	m_si.cb = sizeof(m_si);
	GetStartupInfo(&m_si); 

	// 强制stdion, stdout和stderr完全无缓冲:python -u
	TCHAR szCommandLine[MAX_PATH] = { 0 };
	if (_tcslen(m_szExtraSentence))
		_stprintf_s(szCommandLine, _T("python -W ignore -u %s %s"), m_szScriptPath, m_szExtraSentence);
	else
		_stprintf_s(szCommandLine, _T("python -W ignore -u %s"), m_szScriptPath);

	// 重定向输出;
	RedirectSubprocessStdout(&m_si);
	// 恢复日志线程;
	ResumeThread(m_hLogThread);

	// 启动子进程;
	if (!CreateProcess(
		NULL,					// No module name (use command line)
		szCommandLine,			// Command line
		NULL,					// Process handle not inheritable
		NULL,					// Thread handle not inheritable
		TRUE,					// Set handle inheritance to TRUE
		0,						// No creation flags
		NULL,					// Use parent's environment block
		NULL,					// Use parent's starting directory 
		&m_si,					// Pointer to STARTUPINFO structure
		&m_pi)					// Pointer to PROCESS_INFORMATION structure
		)
	{
		printf("Error:创建子进程失败 (%d).\n", GetLastError());
		return -3;
	}

	m_bRuned = TRUE;
	m_dwSubprocessId = m_pi.dwProcessId;
	// 等待进程完成退出.
	WaitForSingleObject(m_pi.hProcess, INFINITE);

	Sleep(5000);
	// 结束日志线程;
	m_bStopLogExport = TRUE;

	// 关闭进程句柄. 
	CloseHandle(m_pi.hProcess);
	CloseHandle(m_pi.hThread);

	// 重置;
	memset(&m_si, 0, sizeof(m_si));
	memset(&m_pi, 0, sizeof(m_pi));
	m_si.cb = sizeof(m_si);

	return 0;
}

bool CScriptExecutor::StartWorkThread()
{
	// 创建线程;
	m_hWorkThread = CreateThread(NULL, 0, _WorkerThread, this, 0, &m_dwThreadId);
	if (!m_hWorkThread)
	{
		printf("Error:创建线程失败\n");
		return false;
	}

	return true;
}

bool CScriptExecutor::StartLogThread()
{
	printf("StartLogThread\n");
	// 创建线程事件:手动控制,无信号状态;
	m_hLogEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (!m_hLogEvent)
	{
		printf("Error:无法为线程创建控制事件\n");
		return false;
	}

	// 创建线程;
	m_hLogThread = CreateThread(NULL, 0, _LogExportThread, this, CREATE_SUSPENDED, NULL);
	if (!m_hLogThread)
	{
		printf("Error:创建线程失败\n");
		CloseHandle(m_hLogEvent);
		m_hLogEvent = NULL;

		return false;
	}

	return true;
}

void CScriptExecutor::EndWorkThread()
{
	// 等待线程结束;
	if (m_hWorkThread)
	{
		// 尝试5次结束行为;
		for (int i = 0; i < 5; i++)
		{
			if (TerminateThread(m_hWorkThread, 0))
				break;
		}

		CloseHandle(m_hWorkThread);
		m_hWorkThread = NULL;
	}
}

void CScriptExecutor::EndLogThread()
{
	// 设置事件有信号;
	if (m_hLogEvent)
		SetEvent(m_hLogEvent);

	// 等待线程结束;
	if (m_hLogThread)
	{
		WaitForSingleObject(m_hLogThread, INFINITE);
		CloseHandle(m_hLogThread);
		m_hLogThread = NULL;
	}

	// 关闭句柄;
	if (m_hLogEvent)
		CloseHandle(m_hLogEvent);
	m_hLogEvent = NULL;
}

void CScriptExecutor::EndThread(HANDLE hThread, HANDLE hEvent)
{
	// 设置事件有信号;
	if (hEvent)
		SetEvent(hEvent);

	// 等待线程结束;
	if (hThread)
	{
		WaitForSingleObject(hThread, INFINITE);
		CloseHandle(hThread);
		hThread = NULL;
	}

	// 关闭句柄;
	if (hEvent)
		CloseHandle(hEvent);
	hEvent = NULL;
}

BOOL CScriptExecutor::EndSubprocess()
{
	BOOL ret = false;
	if (m_pi.hProcess)
	{
		// 尝试5次结束;
		for (int i = 0; i < 5; i++)
		{
			if ( (ret = TerminateProcess(m_pi.hProcess, 0)) )
				break;
		}
	}

	return ret;
}

void CScriptExecutor::EndProcessStdOut()
{
	OutputDebugString("--------------EndProcessStdOut----------------\n");
#if 0 // 恢复失败,代码有问题
	// 恢复标准输出（嵌入进程脚本）;
	if ( m_nRunType == EMBEDDED && m_hOldStdOutWrite)
	{
		int fn = _fileno(stdout);
		// 恢复进程默认标准输出;
		int fd = _open_osfhandle((intptr_t)m_hOldStdOutWrite, _O_RDWR);
		if ( _dup2(fd, _fileno(stdout)) == 0)
		{
			_close(fd);
			// 设置标准输出;
			SetStdHandle(STD_OUTPUT_HANDLE, m_hOldStdOutWrite);
			m_hOldStdOutWrite = NULL;
			// 设置标准流不使用缓冲,即时写入;
			setvbuf(stdout, NULL, _IONBF, 0);
		}
		else
		{
			printf("Error:_dup2分配文件描述符失败\n");
		}			
	}
#endif

	// 关闭重定向句柄;
	if (m_hStdErrorWrite)
		CloseHandle(m_hStdErrorWrite);
	m_hStdErrorWrite = NULL;

	// 只有子进程方式才关闭句柄;
	if ( m_hStdOutWrite)
		CloseHandle(m_hStdOutWrite);
	m_hStdOutWrite = NULL;

	if (m_hStdOutRead)
		CloseHandle(m_hStdOutRead);
	m_hStdOutRead = NULL;	
}

void CScriptExecutor::EndSubprocessStdOut()
{
	OutputDebugString("--------------EndSubprocessStdOut----------------\n");
	// 关闭重定向句柄;
	if (m_hStdErrorWrite)
		CloseHandle(m_hStdErrorWrite);
	m_hStdErrorWrite = NULL;

	// 只有子进程方式才关闭句柄;
	if ( m_hStdOutWrite)
		CloseHandle(m_hStdOutWrite);
	m_hStdOutWrite = NULL;

	if (m_hStdOutRead)
		CloseHandle(m_hStdOutRead);
	m_hStdOutRead = NULL;	
}

void CScriptExecutor::InitScript(std::string strScript, std::string strLogPath, std::string strScriptCmd, int nRunType /*= PY_RUN_TYPE::EMBEDDED*/)
{
	// 判断脚本是否存在;
	if (!PathFileExists(strScript.c_str()))
	{
		printf("Error:脚本文件不存在\n");
		return;
	}

	// 判断日志文件路径是否可创建;
	if (!PathFileExists(strLogPath.c_str()))
	{
		// 创建路径;
		if (!Global::MKDIR(strLogPath.c_str()))
		{
			printf("Error:创建目录失败\n");
			return;
		}

		// 创建文件;
		std::ofstream flog(strLogPath.c_str());
		if ( flog.bad() )
		{
			printf("Error:创建文件失败\n");
			return;
		}
		flog.close();
	}

	// 赋值参数;
	m_bRuned = FALSE;
	m_nRunType = nRunType;
	m_dwThreadId = m_dwSubprocessId = 0;
	_tcscpy_s(m_szScriptPath, strScript.c_str());
	_tcscpy_s(m_szLogPath, strLogPath.c_str());
	_tcscpy_s(m_szExtraSentence, strScriptCmd.c_str());
}

bool CScriptExecutor::StartScript()
{
	if (StartWorkThread())
	{
		// 开启日志导出线程;
		if (!StartLogThread())
		{
			printf("Error:日志导出线程创建出错\n");
		}

		Sleep(100);

		return true;
	}

	// 异常类型;
	printf("Error:异常类型脚本\n");

	return false;
}

void CScriptExecutor::StopScript()
{
	// 如果是子进程运行脚本,停止线程时kill进程;
	if (m_nRunType == SUBPROCESS)
		EndSubprocess();

	// 结束线程;
	EndWorkThread();
}

bool CScriptExecutor::IsScriptOver()
{
	int i = 10;
	while (!m_bRuned)
	{
		Sleep(300);
		if ( --i == 0 )
			break;
	}

	if (m_nRunType == EMBEDDED)
	{
		if ( m_hWorkThread == NULL )
			return true;
	}
	else if (m_nRunType == SUBPROCESS)
	{
		if ( m_pi.hProcess == NULL && m_hWorkThread == NULL )
			return true;
	}

	return false;
}