#include <windows.h>
#include <shlwapi.h>
#include <thread>
#include <chrono>

#include <iostream>
#include <fstream>

#include <map>


#define TSFUNCS_EXCLUDE_MINHOOK /* This program doesn't use any hooks */

#include "TSfuncs/TSFuncs.cpp"
#include "TSfuncs/TSHooks.cpp"
#include "TSfuncs/TSVector.cpp"
#include "TSFuncsAddition.h"

#include "BLstructs/include.hpp"

#include "python/Python.h"

#include "main.h"
#include "interface.cpp"

boolean always_overwrite_bootstrap = false;

bool py_initialized = false;
int BLpython_api_initialized = false;
int BLpython_api_version = 1;

#define char_clear(search, orig) \
	char_clear2(std::string(search), std::string(orig))

#define char_clear2(search, orig) \
	orig.replace(orig.find(search), search.size(), "").c_str()


bool init()
{
	BlInit;

	if (!tsf_InitInternal())
	{
		BlPrintf("    - tsfuncs init failure");
		return false;
	}

	tsf_AddVar("BLpython_api_initialized", &BLpython_api_initialized);
	tsf_AddVar("BLpython_api_version", &BLpython_api_version);




	char exeDir[MAX_PATH];

	if(!GetModuleFileNameA(NULL, exeDir, MAX_PATH))
	{
		BlPrintf("    - Failed to determine EXE directory");
		return false;
	}

	PathRemoveFileSpec(exeDir);
	BlPrintf("    - EXE directory: %s", exeDir);
 
	WIN32_FIND_DATA FindFileData;
	HANDLE hFind;

	char pythonEmbedPattern[MAX_PATH];
	strcpy(pythonEmbedPattern, exeDir);
	PathAppend(pythonEmbedPattern, "modules");
	PathAppend(pythonEmbedPattern, "python");
	PathAppend(pythonEmbedPattern, "python-*-embed-win32.");
	BlPrintf("    - Searching for embedded Python3: %s", char_clear(exeDir, pythonEmbedPattern));


	//TODO: keep searching, don't stop at first one
	if((hFind = FindFirstFileA(pythonEmbedPattern, &FindFileData)) == INVALID_HANDLE_VALUE) 
	{
		BlPrintf("    - FindFirstFile failed with error: 0x%08X", GetLastError());
		return false;
	}

	FindClose(hFind);

	char pythonEmbedDir[MAX_PATH];
	strcpy(pythonEmbedDir, pythonEmbedPattern);
	PathRemoveFileSpec(pythonEmbedDir);
	PathAppend(pythonEmbedDir, FindFileData.cFileName);

	BlPrintf("    - Python3 embedded dir: %s", char_clear(exeDir, pythonEmbedDir));

	char pythonEmbedPythonDLLSearch[MAX_PATH];
	strcpy(pythonEmbedPythonDLLSearch, pythonEmbedDir);
	PathAppend(pythonEmbedPythonDLLSearch, "python3*.dll");

	const char* dll_name = "python3.dll";
	BlPrintf("    - Searching for DLL: %s", char_clear(exeDir, pythonEmbedPythonDLLSearch));
	BlPrintf("          Candidate: %s (default)", dll_name);


	if((hFind = FindFirstFileA(pythonEmbedPythonDLLSearch, &FindFileData)) == INVALID_HANDLE_VALUE) 
	{
		BlPrintf("    - FindFirstFile failed with error: %d", GetLastError());
		return false;
	}

	else
	{
		do
			if(!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				&& strcmp(FindFileData.cFileName, "python3.dll") != 0)
			{
				dll_name = FindFileData.cFileName;
				BlPrintf("          Candidate: %s", dll_name);
			}

		while(FindNextFile(hFind, &FindFileData) != 0);
	}

	FindClose(hFind);

	char pythonEmbedPythonDLL[MAX_PATH];
	strcpy(pythonEmbedPythonDLL, pythonEmbedPythonDLLSearch);
	PathRemoveFileSpec(pythonEmbedPythonDLL);
	PathAppend(pythonEmbedPythonDLL, dll_name);

	//don't remember why I previously used LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR, but it broke this for Windows users
	//HINSTANCE hinstLib = LoadLibraryExA(pythonEmbedPythonDLL, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR);
	HINSTANCE hinstLib = LoadLibraryA(pythonEmbedPythonDLL);

	if (hinstLib == NULL)
	{
		BlPrintf("    - Library \"%s\" error: 0x%08x", char_clear(exeDir, pythonEmbedPythonDLL), GetLastError());
		return false;
	}

	BlPrintf("    - Library \"%s\" loaded", char_clear(exeDir, pythonEmbedPythonDLL));
	FARPROC test_init = GetProcAddress(hinstLib, "Py_InitializeEx");
	if(test_init == 0)
	{
		BlPrintf("          Failed to find Py_InitializeEx");
		return false;
	}
	BlPrintf("          Found Py_InitializeEx function");


	char pythonModDir[MAX_PATH];
	strcpy(pythonModDir, exeDir);
	PathAppend(pythonModDir, "modules");
	PathAppend(pythonModDir, "BLpython");

	char pythonModBootstrapFile[MAX_PATH];
	strcpy(pythonModBootstrapFile, pythonModDir);
	PathAppend(pythonModBootstrapFile, "bootstrap.py");

	if(CreateDirectory(pythonModDir, NULL) || GetLastError() == ERROR_ALREADY_EXISTS)
	{
		if(always_overwrite_bootstrap || access(pythonModBootstrapFile, 0) != 0)
		{
			BlPrintf("    - Creating bootstrap file: %s", char_clear(exeDir, pythonModBootstrapFile));

			std::ofstream file(pythonModBootstrapFile);
			file << file_load_py_main;

			file.close();
		}
		else
			BlPrintf("    - Not overwriting existing: %s", char_clear(exeDir, pythonModBootstrapFile));
	}

	else
	{
		BlPrintf("          Failed to create dir: %s", char_clear(exeDir, pythonModDir));
		return 0;
	}

	//TODO: python embedded redirect print

	//_putenv("PYTHONVERBOSE=2");
	//_putenv("PYTHONLEGACYWINDOWSSTDIO=1");
	//_putenv("PYTHONDEBUG=2");
	//_putenv("PYTHONHOME=./modules/python-3.10.6-embed-win32/");
	//_putenv("PYTHONPATH=./modules/python-3.10.6-embed-win32/");

	ePyIMPORT(hinstLib, Py_IsInitialized);
	ePyIMPORT(hinstLib, Py_InitializeEx);

	ePyIMPORT(hinstLib, PyRun_SimpleString);
	ePyIMPORT(hinstLib, PyRun_SimpleStringFlags);
	ePyIMPORT(hinstLib, PyRun_SimpleFile);
	ePyIMPORT(hinstLib, PyRun_SimpleFileEx);
	ePyIMPORT(hinstLib, PyRun_SimpleFileExFlags);

	ePyIMPORT(hinstLib, Py_FinalizeEx);

	ePyIMPORT(hinstLib, _Py_fopen_obj);

	ePyIMPORT(hinstLib, PyObject_Call);
	ePyIMPORT(hinstLib, PyObject_CallNoArgs);
	ePyIMPORT(hinstLib, PyObject_CallObject);
	ePyIMPORT(hinstLib, PyObject_Bytes);
	ePyIMPORT(hinstLib, PyObject_IsTrue);

	ePyIMPORT(hinstLib, PyCallable_Check);
	ePyIMPORT(hinstLib, PyErr_SetString);
	ePyIMPORT(hinstLib, PyErr_Print);

	ePyIMPORT(hinstLib, PyArg_ParseTuple);
	ePyIMPORT(hinstLib, PyTuple_Size);
	ePyIMPORT(hinstLib, PyTuple_GetItem);

	ePyIMPORT(hinstLib, PyLong_FromSize_t);
	ePyIMPORT(hinstLib, PyLong_FromSsize_t);
	ePyIMPORT(hinstLib, PyLong_FromLong);
	ePyIMPORT(hinstLib, PyLong_FromUnsignedLong);
	ePyIMPORT(hinstLib, PyLong_AsSsize_t);
	ePyIMPORT(hinstLib, PyLong_AsSize_t);

	ePyIMPORT(hinstLib, PyFloat_AsDouble);


	ePyIMPORT(hinstLib, PyUnicode_FromString);
	ePyIMPORT(hinstLib, PyUnicode_AsASCIIString);

	ePyIMPORT(hinstLib, PyBytes_AsString);

	ePyIMPORT(hinstLib, Py_BuildValue);
	ePyIMPORT(hinstLib, PyList_New);
	ePyIMPORT(hinstLib, PyList_Append);
	ePyIMPORT(hinstLib, PyDict_New);
	ePyIMPORT(hinstLib, PyDict_SetItemString);

	ePyIMPORT(hinstLib, PyArg_UnpackTuple);
	ePyIMPORT(hinstLib, PySequence_Check);
	ePyIMPORT(hinstLib, PySequence_Length);
	ePyIMPORT(hinstLib, PySequence_GetItem);

	ePyIMPORT(hinstLib, PyImport_AppendInittab);
	ePyIMPORT(hinstLib, PyModule_Create2);
	ePyIMPORT(hinstLib, PyModule_AddObject);

	ePyIMPORT(hinstLib, Py_NewRef);
	ePyIMPORT(hinstLib, Py_IncRef);
	ePyIMPORT(hinstLib, Py_DecRef);
	ePyIMPORT(hinstLib, PyErr_NewException);
	ePyIMPORT(hinstLib, PyErr_Occurred);
	ePyDEFINE(hinstLib, PyErr_Clear);
	ePyIMPORT(hinstLib, PyErr_SetNone);

	ePyIMPORT(hinstLib, PyObject_SelfIter);
	ePyIMPORT(hinstLib, PyType_Ready);
	ePyIMPORT(hinstLib, PyType_GenericNew);
	ePyIMPORT(hinstLib, PyType_GenericAlloc);

	ePyIMPORT(hinstLib, Py_IsNone);

	//exported symbols
	sPyIMPORT(hinstLib, PyExc_TypeError);
	sPyIMPORT(hinstLib, PyExc_ValueError);
	sPyIMPORT(hinstLib, PyExc_RuntimeError);
	sPyIMPORT(hinstLib, PyExc_StopIteration);

	sPyIMPORT(hinstLib, PyUnicode_Type);
	sPyIMPORT(hinstLib, PyLong_Type);
	sPyIMPORT(hinstLib, PyFloat_Type);
	sPyIMPORT(hinstLib, PyBool_Type);


	PythonABI::PyImport_AppendInittab("BLpython", &PyInit_BLPiface);

	PythonABI::Py_InitializeEx(0);
	py_initialized = true;
	BLpython_api_initialized = true;

	int basic_test_ok = PythonABI::PyRun_SimpleString((const char*)file_load_py_test);

	if(basic_test_ok == 0)
		BlPrintf("    - Basic test passed");

	else
		BlPrintf("    - Basic test failed");

	BlPrintf("    - Executing: %s", char_clear(exeDir, pythonModBootstrapFile));

	PyObject *obj = PythonABI::Py_BuildValue("s", pythonModBootstrapFile);
	FILE *file = PythonABI::_Py_fopen_obj(obj, "r+");
	if(file != NULL)
	{
		PythonABI::PyRun_SimpleFileExFlags(file, "bootstrap.py", 0, 0);
	}

	else
	{
		PythonABI::Py_FinalizeEx();
		py_initialized = false;
		BlPrintf("    - Opening file failed; Deinitialized Python.");
	}

	//Problem with PyRun_SimpleFileEx. Executing file in Python instead:
	//PyRun_SimpleString("import sys\nsys.path.insert(0, 'modules\\\\BLpython\\\\')\nimport bootstrap\nbootstrap.load_main()");

	BlPrintf("    [ Initialized: %s (v%s-%s) ]", PROJECT_NAME, PROJECT_VERSION, TSFUNCS_DEBUG ? "debug":"release");

	return true;
}

bool deinit(bool terminating)
{
	printf("%s: deinit'd\n", PROJECT_NAME);

	if(!terminating)
	{
		printf("denit in terminating\n");
		if(py_initialized)
		{
			printf("    Python finalized\n");
			PythonABI::Py_FinalizeEx();
		}

		//BlTestDisableHook( ... );
	}

	return true;
}

bool __stdcall DllMain(HINSTANCE hinstance, int reason, void *reserved)
{
	switch (reason)
	{
		case DLL_PROCESS_ATTACH:
			return init();
		case DLL_PROCESS_DETACH:
			return deinit(reserved != NULL);
		default:
			return true;
	}
}

extern "C" void __declspec(dllexport) __cdecl PROJECT_EXPORT(){}
