//
// ISXMr
//

// Version guideline: YYYYMMDD
// Add lettering to the end to indicate a new version on the same date, such as 20060305a, 20060305b, etc
// You can also use a standard version numbering system such as 1.00, 1.01, etc.
// Be aware that for the versioning system, this text is simply compared to another version text from the
// same extension to check for new versions -- if this version text comes before the compared text (in a
// dictionary), then an update is available.  Equal text means the version is up to date.  After means this
// is newer than the compared version.  With that said, use whatever version numbering system you'd like.
#define EXTENSION_VERSION "20060617"

#include "ISXMr.h"

#include <filesystem>
#include <fstream>
#include <iostream>
#include <__msvc_filebuf.hpp>

#include "Logger.h"
#include "isxeq2/Character.h"
#include "DataTypes/MrBotApiType.h"
#include "Tasks/TaskExecutor.h"
#pragma comment(lib, "isxdk.lib")
// The mandatory pre-setup function.  Our name is "ISXMr", and the class is ISXMr.
// This sets up a "ModulePath" variable which contains the path to this module in case we want it,
// and a "PluginLog" variable, which contains the path and filename of what we should use for our
// debug logging if we need it.  It also sets up a variable "pExtension" which is the pointer to
// our instanced class.
ISXPreSetup("ISXMr", ISXMr);

// Basic LavishScript datatypes, these get retrieved on startup by our initialize function, so we can
// use them in our Top-Level Objects or custom datatypes
LSType *pStringType = 0;
LSType *pIntType = 0;
LSType *pUintType = 0;
LSType *pBoolType = 0;
LSType *pFloatType = 0;
LSType *pTimeType = 0;
LSType *pByteType = 0;
LSType *pIntPtrType = 0;
LSType *pBoolPtrType = 0;
LSType *pFloatPtrType = 0;
LSType *pBytePtrType = 0;
LSType *pPoint3f = 0;

ISInterface *pISInterface = 0;
HISXSERVICE hPulseService;
HISXSERVICE hMemoryService;
HISXSERVICE hHTTPService;
HISXSERVICE hTriggerService;
HISXSERVICE hSystemService;

char Mr_Version[] = EXTENSION_VERSION;

// Forward declarations of callbacks
void __cdecl PulseService(bool Broadcast, unsigned int MSG, void *lpData);

void __cdecl MemoryService(bool Broadcast, unsigned int MSG, void *lpData);

void __cdecl TriggerService(bool Broadcast, unsigned int MSG, void *lpData);

void __cdecl HTTPService(bool Broadcast, unsigned int MSG, void *lpData);

void __cdecl SystemService(bool Broadcast, unsigned int MSG, void *lpData);

// The constructor of our class.  General initialization cannot be done yet, because we're not given
// the pointer to the Inner Space interface until it is ready for us to initialize.  Just set the
// pointer we have to the interface to 0.  Initialize data members, too.
ISXMr::ISXMr(void)
{
}

// Free any remaining resources in the destructor.  This is called when the DLL is unloaded, but
// Inner Space calls the "Shutdown" function first.  Most if not all of the shutdown process should
// be done in Shutdown.
ISXMr::~ISXMr(void)
{
}

// Initialize is called by Inner Space when the extension should initialize.
bool ISXMr::Initialize(ISInterface *p_ISInterface)
{
	/*
	 * Most of the functionality in Initialize is completely optional and could be removed or
	 * changed if so desired.  The defaults are simply a suggestion that can be easily followed.
	 */
	// constexpr size_t innerspacePathBufferLength = 255;
	// char innerspacePathBuffer[innerspacePathBufferLength];
	// const std::filesystem::path innerspacePath = p_ISInterface->GetInnerSpacePath(
	// 	innerspacePathBuffer, innerspacePathBufferLength);
	// std::filesystem::path fullPath = innerspacePath / R"(scripts\mr\ui\test.json)";
	//
	// if (fullPath.has_parent_path() && !std::filesystem::exists(fullPath.parent_path())) {
	// 	std::filesystem::create_directories(fullPath.parent_path());
	// }
	//
	// std::ofstream file(fullPath, std::ios::binary);
	// if (!file) {
	// 	std::cerr << "Error opening file for writing: " << fullPath << std::endl;
	// }
	//
	// file.write(reinterpret_cast<const char *>(test_json), test_json_len);
	// file.close();

	//__try // exception handling. See __except below.
	{
		// Keep a global copy of the ISInterface pointer, which is for calling Inner Space API
		pISInterface = p_ISInterface;

		// Register the extension to make launching and updating the extension easy
		RegisterExtension();

		// retrieve basic LavishScript data types for use in ISXMr data types
		pStringType = pISInterface->FindLSType(const_cast<char *>("string"));
		pIntType = pISInterface->FindLSType(const_cast<char *>("int"));
		pUintType = pISInterface->FindLSType(const_cast<char *>("uint"));
		pBoolType = pISInterface->FindLSType(const_cast<char *>("bool"));
		pFloatType = pISInterface->FindLSType(const_cast<char *>("float"));
		pTimeType = pISInterface->FindLSType(const_cast<char *>("time"));
		pByteType = pISInterface->FindLSType(const_cast<char *>("byte"));
		pIntPtrType = pISInterface->FindLSType(const_cast<char *>("intptr"));
		pBoolPtrType = pISInterface->FindLSType(const_cast<char *>("boolptr"));
		pFloatPtrType = pISInterface->FindLSType(const_cast<char *>("floatptr"));
		pBytePtrType = pISInterface->FindLSType(const_cast<char *>("byteptr"));
		pPoint3f = pISInterface->FindLSType(const_cast<char *>("point3f"));

		// Connect to commonly used Inner Space services
		ConnectServices();

		// Register LavishScript extensions (commands, aliases, data types, objects)
		RegisterCommands();
		RegisterAliases();
		RegisterDataTypes();
		RegisterTopLevelObjects();
		RegisterEvents();

		// Register (create) our own services
		RegisterServices();

		// Register any text triggers built into ISXMr
		RegisterTriggers();

		// pISInterface->RunScriptFromBuffer("mrbot", reinterpret_cast<const char *>(bot_iss), bot_iss_len);
		printf("ISXMr version %s Loaded", Mr_Version);
		return true;
	}

	// Exception handling sample.  Exception handling should at LEAST be used in functions that
	// are suspected of causing user crashes.  This will help users report the crash and hopefully
	// enable the extension developer to locate and fix the crash condition.
	//__except(EzCrashFilter(GetExceptionInformation(),"Crash in initialize routine"))
	{
		TerminateProcess(GetCurrentProcess(), 0);
		return 0;
	}
	/*
	 * EzCrashFilter takes printf-style formatting after the first parameter.  The above
	 * could look something like this:
	__except(EzCrashFilter(GetExceptionInformation(),"Crash in initialize routine (%s:%d)",__FILE__,__LINE__))
	{
		TerminateProcess(GetCurrentProcess(),0);
		return 0;
	}
	 * of course, the FILE and LINE macros would be the location of the exception handler, not the
	 * actual crash, but you should get the idea that extra parameters can be used as if EzCrashFilter
	 * was printf.
	 *
	 * EzCrashFilter will automatically produce a crash log (CrashLog.txt) and open it in notepad for
	 * non-breakpoint exceptions (and hopefully the user will report the crash to the extension developer).
	 * Your exception handler (the part within the {} under __except) should simply terminate the process
	 * and return from the function as in the sample.  The return will not be hit, but the compiler will
	 * whine without it because it doesn't automatically know that the function will not return.
	 */
}

// shutdown sequence
void ISXMr::Shutdown()
{
	logw << "Shutting down!" << endl;
	executor.Shutdown();
	// Disconnect from services we connected to
	DisconnectServices();

	// Unregister (destroy) services we created
	UnRegisterServices();

	// Remove LavishScript extensions (commands, aliases, data types, objects)
	UnRegisterDataTypes();
	UnRegisterTopLevelObjects();
	UnRegisterAliases();
	UnRegisterCommands();
	UnRegisterEvents();
}

/*
 * Note that Initialize and Shutdown are the only two REQUIRED functions in your ISXInterface class.
 * All others are for suggested breakdown of routines, and for example purposes.
 */

void ISXMr::RegisterExtension()
{
	// add this extension to, or update this extension's info in, InnerSpace.xml.
	// This accomplishes a few things.  A) The extension can be loaded by name (ISXMr)
	// no matter where it resides on the system.  B) A script or extension can
	// check a repository to determine if there is an update available (and update
	// if necessary)

	unsigned int ExtensionSetGUID = pISInterface->GetExtensionSetGUID("ISXMr");
	if (!ExtensionSetGUID)
	{
		ExtensionSetGUID = pISInterface->CreateExtensionSet("ISXMr");
		if (!ExtensionSetGUID)
			return;
	}
	pISInterface->SetSetting(ExtensionSetGUID, "Filename", ModuleFileName);
	pISInterface->SetSetting(ExtensionSetGUID, "Path", ModulePath);
	pISInterface->SetSetting(ExtensionSetGUID, "Version", Mr_Version);
}

void ISXMr::ConnectServices()
{
	// connect to any services.  Here we connect to "Pulse" which receives a
	// message every frame (after the frame is displayed) and "Memory" which
	// wraps "detours" and memory modifications
	hPulseService = pISInterface->ConnectService(this, "Pulse", PulseService);
	hMemoryService = pISInterface->ConnectService(this, "Memory", MemoryService);

	// The HTTP service handles URL retrieval
	hHTTPService = pISInterface->ConnectService(this, "HTTP", HTTPService);

	// The Triggers service handles trigger-related functions, including the
	// ability to pass text TO the trigger parser, as well as the ability to
	// add triggers.
	hTriggerService = pISInterface->ConnectService(this, "Triggers", TriggerService);

	// The System service provides general system-related services, including
	// a diagnostics message that allows the extension to insert diagnostic
	// information for the "diagnostics" command, and extension crash logs.
	hSystemService = pISInterface->ConnectService(this, "System", SystemService);
}

void ISXMr::RegisterCommands()
{
	// add any commands
	//	pISInterface->AddCommand("ISXMr",CMD_ISXMr,true,false);
#define COMMAND(name, cmd, parse, hide) pISInterface->AddCommand(name, cmd, parse, hide);
#include "Commands.h"
#undef COMMAND
}

void ISXMr::RegisterAliases()
{
	// add any aliases
}

MrBotApiType *pMrBotApi = nullptr;

void ISXMr::RegisterDataTypes()
{
	logw << "ISXMr::RegisterDataTypes" << endl;
	MrBotApiType::RegisterType(pISInterface);
}

void ISXMr::RegisterTopLevelObjects()
{
	logw << "ISXMr::RegisterDataTypes" << endl;
	// add any Top-Level Objects
	// pISInterface->AddTopLevelObject("ISXMr",TLO_ISXMr);
	// #define TOPLEVELOBJECT(name, funcname) pISInterface->AddTopLevelObject(name, funcname);
	// #include "TopLevelObjects.h"
	// #undef TOPLEVELOBJECT
	MrBotApiType::RegisterTLO(pISInterface);
}

void ISXMr::RegisterServices()
{
	// register any services.  Here we demonstrate a service that does not use a
	// callback
	// set up a 1-way service (broadcast only)
	//	hISXMrService=pISInterface->RegisterService(this,"ISXMr Service",0);
	// broadcast a message, which is worthless at this point because nobody will receive it
	// (nobody has had a chance to connect)
	//	pISInterface->ServiceBroadcast(this,hISXMrService,ISXSERVICE_MSG+1,0);
#define SERVICE(_name_, _callback_, _variable_) _variable_ = pISInterface->RegisterService(this, _name_, _callback_);
#include "Services.h"
#undef SERVICE
}

void ISXMr::RegisterTriggers()
{
	// add any Triggers
}

unsigned int onGetTargetEventId = 0;
unsigned int onCloseButtonClickedEventId = 0;

void __cdecl OnGetTargetEvent(int argc, char *argv[], PLSOBJECT lsObj)
{
	const auto me = pISInterface->IsTopLevelObject("Me");
	if (me != nullptr)
	{
		LSOBJECT response;
		me(0, nullptr, response);
		auto characterObject = make_shared<LSObject>(LSObject(response));
		Character c(characterObject);
		Actor target = c.Target();
		string targetName(target.Name());
		printf("Target ID: %d", target.Id());

		printf("Target: %s", targetName.c_str());
		LSOBJECT controller;
		pISInterface->DataParse("MRBotController", controller);
		// printf("Controller Type: %s", controller.Type->GetName());
		LSOBJECT targetObj;
		controller.Type->GetMemberEx(controller.GetObjectData(), const_cast<char *>("target"), 0, nullptr, targetObj);
		// printf("Target Type: %s", targetObj.Type->GetName());
		auto targetNameCstr = const_cast<char *>(targetName.c_str());
		targetObj.Type->GetMethodEx(targetObj.GetObjectData(), const_cast<char *>("Set"), 1, &targetNameCstr);
	}
	printf("OnGetTargetEvent");
}

void __cdecl OnCloseButtonClicked(int argc, char *argv[], PLSOBJECT lsObj)
{
	printf("OnCloseButtonClicked");
}

void ISXMr::RegisterEvents()
{
	// onGetTargetEventId = pISInterface->RegisterEvent("OnGetTarget");
	// pISInterface->AttachEventTarget(onGetTargetEventId, OnGetTargetEvent);
	//
	// onCloseButtonClickedEventId = pISInterface->RegisterEvent("OnCloseButtonClicked");
	// pISInterface->AttachEventTarget(onCloseButtonClickedEventId, OnCloseButtonClicked);
}

void ISXMr::DisconnectServices()
{
	// gracefully disconnect from services
	if (hPulseService)
		pISInterface->DisconnectService(this, hPulseService);
	if (hMemoryService)
	{
		pISInterface->DisconnectService(this, hMemoryService);
		// memory modifications are automatically undone when disconnecting
		// also, since this service accepts messages from clients we should reset our handle to
		// 0 to make sure we dont try to continue using it
		hMemoryService = 0;
	}
	if (hHTTPService)
	{
		pISInterface->DisconnectService(this, hHTTPService);
	}
	if (hTriggerService)
	{
		pISInterface->DisconnectService(this, hTriggerService);
	}
	if (hSystemService)
	{
		pISInterface->DisconnectService(this, hSystemService);
	}
}

void ISXMr::UnRegisterCommands()
{
	// remove commands
	//	pISInterface->RemoveCommand("ISXMr");
#define COMMAND(name, cmd, parse, hide) pISInterface->RemoveCommand(name);
#include "Commands.h"
#undef COMMAND
}

void ISXMr::UnRegisterAliases()
{
	// remove aliases
}

void ISXMr::UnRegisterDataTypes()
{
	logw << "ISXMr::UnRegisterDataTypes" << endl;
	MrBotApiType::RemoveType(pISInterface);
}

void ISXMr::UnRegisterTopLevelObjects()
{
	// remove Top-Level Objects
	//	pISInterface->RemoveTopLevelObject("ISXMr");
	// #define TOPLEVELOBJECT(name, funcname) pISInterface->RemoveTopLevelObject(name);
	// #include "TopLevelObjects.h"
	// #undef TOPLEVELOBJECT
	MrBotApiType::RemoveTLO(pISInterface);
}

void ISXMr::UnRegisterServices()
{
	// shutdown our own services
	//	if (hISXMrService)
	//		pISInterface->ShutdownService(this,hISXMrService);

#define SERVICE(_name_, _callback_, _variable_) _variable_ = pISInterface->ShutdownService(this, _variable_);
#include "Services.h"
#undef SERVICE
}

void ISXMr::UnRegisterEvents()
{
	// pISInterface->DetachEventTarget(onGetTargetEventId, OnGetTargetEvent);
	// pISInterface->UnregisterEvent(onGetTargetEventId);
	//
	// pISInterface->DetachEventTarget(onCloseButtonClickedEventId, OnCloseButtonClicked);
	// pISInterface->UnregisterEvent(onCloseButtonClickedEventId);
}

TaskExecutor executor;

std::chrono::milliseconds interval(100);
;
std::chrono::steady_clock::time_point nextCleanup = std::chrono::steady_clock::now() + interval;

void __cdecl PulseService(bool Broadcast, const unsigned int MSG, void *lpData)
{
	if (MSG == PULSE_PULSE)
	{
		// const auto logs = executor.GetAllLogs();
		// if (!logs.empty()) {
		// 	pISInterface->Printf("Logs: %d", logs.size());
		// }
		// for (const auto logEntry: logs) {
		// 	pISInterface->Printf("[%s]: %s", logEntry.GetFormattedTimestamp().c_str(), logEntry.message.c_str());
		// }

		if (const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); now > nextCleanup)
		{
			try
			{
				executor.RemoveFinishedTasks();
			}
			catch (const std::exception &e)
			{
				loge << "Error removing finished tasks: " << e.what() << endl;
			}
			catch (...)
			{
				loge << "Unknown error removing finished tasks" << endl;
			}
			nextCleanup = now + interval;
		}

		/*
		 * "OnPulse"
		 * This message is received by the extension before each frame is
		 * displayed by the game.  This is the place to put any repeating
		 * tasks.
		 */
		// if(frameCount++ > 80) {
		// 	frameCount = 0;
		// 	const auto me = pISInterface->IsTopLevelObject("Me");
		// 	if(me != nullptr) {
		// 		LSOBJECT response;
		// 		me(0, nullptr, response);
		// 		Character c(response);
		// 		printf("------------------------------------");
		// 		printf("ID:           %u", c.GetId());
		// 		printf("Name:         %s", c.GetName());
		// 		printf("Race:         %s", c.GetRace());
		// 		printf("Archetype:    %s", c.GetArchetype());
		// 		printf("Class:        %s", c.GetClass());
		// 		printf("SubClass:     %s", c.GetSubClass());
		// 		printf("TSArchetype:  %s", c.GetTradeskillArchtype());
		// 		printf("TSClass:      %s", c.GetTradeskillClass());
		// 		printf("TSSubClass:   %s", c.GetTradeskillSubClass());
		// 		printf("Gender:       %s", c.GetGender());
		// 		printf("------------------------------------");
		// 	}
		// 	// if(pISInterface->IsExtensionLoaded("ISXEq2")) {
		// 	// 	printf("Loaded");
		// 	// } else {
		// 	// 	printf("Not loaded");
		// 	// }
		//
		// }
	}
}

void __cdecl MemoryService(bool Broadcast, unsigned int MSG, void *lpData)
{
	// no messages are currently associated with this service (other than
	// system messages such as client disconnect), so do nothing.
}

void __cdecl TriggerService(bool Broadcast, unsigned int MSG, void *lpData)
{
	// no messages are currently associated with this service (other than
	// system messages such as client disconnect), so do nothing.
}

void __cdecl SystemService(bool Broadcast, unsigned int MSG, void *lpData)
{
	if (MSG == SYSTEMSERVICE_DIAGNOSTICS)
	{
		// Diagnostics sample
		/*
		FILE *file=(FILE*)lpData;
		fprintf(file,"ISXMr version %s\n",EXTENSION_VERSION);
		fprintf(file,"------------------------------------\n",EXTENSION_VERSION);
		fprintf(file,"Any ISXMr diagnostic information here\n");
		fprintf(file,"\n");// extra spacing for making the crash log look nice
		/**/
	}
}

void __cdecl HTTPService(bool Broadcast, unsigned int MSG, void *lpData)
{
	switch (MSG)
	{
#define pReq ((HttpFile *)lpData)
	case HTTPSERVICE_FAILURE:
		// HTTP request failed to retrieve document
		printf("ISXMr URL %s failed", pReq->URL);
		break;
	case HTTPSERVICE_SUCCESS:
		// HTTP request successfully retrieved document
		printf("ISXMr URL %s -- %d bytes", pReq->URL, pReq->Size);
		// Retrieved data buffer is pReq->pBuffer and is null-terminated
		break;
#undef pReq
	}
}
