using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace QCRPG.Framework;

public static class TclWrapper
{
	[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
	private delegate TclResult CommandHandlerDelegate(IntPtr clientData, IntPtr interpreterPointer, int argc, IntPtr argvPtr);

	public delegate void StartedRunningTclCodeHandler();

	public delegate void FinishedRunningTclCodeHandler(TclResult result, string errorInfo);

	private static string tclInstallPath = "";

	private static string postInitTclCode = "";

	private static IntPtr interpreterPointer = IntPtr.Zero;

	private static Dictionary<string, TclCommandDelegate> commandNameToDelegateMap = new Dictionary<string, TclCommandDelegate>();

	private static CommandHandlerDelegate commandHandler = CommandHandler;

	private static object lockObject = new object();

	private static Thread runCodeThread;

	public static event StartedRunningTclCodeHandler startedRunningTclCodeHandler;

	public static event FinishedRunningTclCodeHandler finishedRunningTclCodeHandler;

	public static void RunScript(string filename, string workingDirectory = "")
	{
		string text = File.ReadAllText(filename, Encoding.UTF8);
		text = text.Replace("\\\r\n", " ");
		RunCode(text, workingDirectory);
	}

	public static void RunCode(string code, string workingDirectory = "")
	{
		if (TclWrapper.startedRunningTclCodeHandler != null)
		{
			TclWrapper.startedRunningTclCodeHandler();
		}
		lock (lockObject)
		{
			if (interpreterPointer != IntPtr.Zero)
			{
				throw new Exception("Already running Tcl code/script");
			}
			Startup(workingDirectory);
			runCodeThread = new Thread(TclEvalThread);
			runCodeThread.IsBackground = true;
			runCodeThread.Start(code);
		}
	}

	public static void StopCodeOrScript()
	{
		lock (lockObject)
		{
			if (!(interpreterPointer == IntPtr.Zero))
			{
				runCodeThread.Abort();
				interpreterPointer = IntPtr.Zero;
				if (TclWrapper.finishedRunningTclCodeHandler != null)
				{
					TclWrapper.finishedRunningTclCodeHandler(TclResult.ERROR, "Tcl code/script aborted");
				}
			}
		}
	}

	public static bool IsRunning()
	{
		lock (lockObject)
		{
			if (interpreterPointer != IntPtr.Zero)
			{
				return true;
			}
			return false;
		}
	}

	private static void TclEvalThread(object o)
	{
		string scriptContents = (string)o;
		TclResult tclResult = TclAPI.Tcl_Eval(interpreterPointer, scriptContents);
		lock (lockObject)
		{
			string errorInfo = "";
			if (tclResult != 0)
			{
				IntPtr ptr = TclAPI.Tcl_GetStringResult(interpreterPointer);
				errorInfo = Marshal.PtrToStringAnsi(ptr);
			}
			Shutdown(tclResult, errorInfo);
		}
	}

	public static void SetTclInstallPath(string tclInstallPath)
	{
		TclWrapper.tclInstallPath = tclInstallPath;
	}

	public static void SetPostInitTclCode(string postInitTclCode)
	{
		TclWrapper.postInitTclCode = postInitTclCode;
	}

	private static void Startup(string workingDirectory)
	{
		if (interpreterPointer != IntPtr.Zero)
		{
			throw new Exception("Interpreter already created");
		}
		interpreterPointer = TclAPI.Tcl_CreateInterp();
		if (interpreterPointer == IntPtr.Zero)
		{
			throw new Exception("Got null pointer creating interpreter");
		}
		try
		{
			TclResult tclResult;
			if (tclInstallPath != "")
			{
				string text = tclInstallPath;
				if (!text.EndsWith("\\"))
				{
					text += "\\";
				}
				text += "tcl8.3";
				text = text.Replace("\\", "\\\\");
				tclResult = TclAPI.Tcl_Eval(interpreterPointer, "set tcl_library " + text);
				if (tclResult != 0)
				{
					IntPtr ptr = TclAPI.Tcl_GetStringResult(interpreterPointer);
					string arg = Marshal.PtrToStringAnsi(ptr);
					TclAPI.Tcl_DeleteInterp(interpreterPointer);
					interpreterPointer = IntPtr.Zero;
					throw new Exception($"Tcl_Eval() returned {tclResult} when setting install path: {arg}");
				}
			}
			tclResult = TclAPI.Tcl_Init(interpreterPointer);
			if (tclResult != 0)
			{
				IntPtr ptr2 = TclAPI.Tcl_GetStringResult(interpreterPointer);
				string arg2 = Marshal.PtrToStringAnsi(ptr2);
				TclAPI.Tcl_DeleteInterp(interpreterPointer);
				interpreterPointer = IntPtr.Zero;
				throw new Exception($"Tcl_Init() returned {tclResult}: {arg2}");
			}
			if (workingDirectory != "")
			{
				Directory.SetCurrentDirectory(workingDirectory);
				TclAPI.Tcl_Eval(interpreterPointer, "cd " + workingDirectory.Replace("\\", "\\\\"));
			}
			foreach (string key in commandNameToDelegateMap.Keys)
			{
				IntPtr functionPointerForDelegate = Marshal.GetFunctionPointerForDelegate((Delegate)commandHandler);
				TclAPI.Tcl_CreateCommand(interpreterPointer, key, functionPointerForDelegate, IntPtr.Zero, IntPtr.Zero);
			}
			if (postInitTclCode != "")
			{
				tclResult = TclAPI.Tcl_Eval(interpreterPointer, postInitTclCode);
				if (tclResult != 0)
				{
					IntPtr ptr3 = TclAPI.Tcl_GetStringResult(interpreterPointer);
					string arg3 = Marshal.PtrToStringAnsi(ptr3);
					TclAPI.Tcl_DeleteInterp(interpreterPointer);
					interpreterPointer = IntPtr.Zero;
					throw new Exception($"Tcl_Eval() returned {tclResult} when evaluating post init code: {arg3}");
				}
			}
		}
		catch (Exception ex)
		{
			if (interpreterPointer != IntPtr.Zero)
			{
				TclAPI.Tcl_DeleteInterp(interpreterPointer);
				interpreterPointer = IntPtr.Zero;
			}
			throw ex;
		}
	}

	private static void Shutdown(TclResult result, string errorInfo)
	{
		if (interpreterPointer == IntPtr.Zero)
		{
			throw new Exception("No interpreter");
		}
		TclAPI.Tcl_DeleteInterp(interpreterPointer);
		interpreterPointer = IntPtr.Zero;
		if (TclWrapper.finishedRunningTclCodeHandler != null)
		{
			TclWrapper.finishedRunningTclCodeHandler(result, errorInfo);
		}
	}

	public static void AddCommand(string commandName, TclCommandDelegate commandProc)
	{
		lock (lockObject)
		{
			if (interpreterPointer != IntPtr.Zero)
			{
				throw new Exception($"Refusing to add command {commandName} while Tcl code is running");
			}
			foreach (string key in commandNameToDelegateMap.Keys)
			{
				if (key == commandName)
				{
					throw new Exception($"A mapping for command {commandName} already exists");
				}
			}
			commandNameToDelegateMap.Add(commandName, commandProc);
		}
	}

	public static void RemoveCommand(string commandName)
	{
		lock (lockObject)
		{
			if (interpreterPointer != IntPtr.Zero)
			{
				throw new Exception($"Refusing to remove command {commandName} while Tcl code is running");
			}
			commandNameToDelegateMap.Remove(commandName);
		}
	}

	public static string[] GetCommands()
	{
		lock (lockObject)
		{
			List<string> list = new List<string>();
			foreach (string key in commandNameToDelegateMap.Keys)
			{
				list.Add(key);
			}
			return list.ToArray();
		}
	}

	private static TclResult CommandHandler(IntPtr clientData, IntPtr interpreterPointer, int argc, IntPtr argvPtr)
	{
		string[] array = IntPtrToStringArray(argc, argvPtr);
		return commandNameToDelegateMap[array[0]](array);
	}

	public static void SetResult(string result, TclFreeProcSpecialValue tclFreeProcSpecialValue = TclFreeProcSpecialValue.VOLATILE)
	{
		lock (lockObject)
		{
			if (!(interpreterPointer == IntPtr.Zero))
			{
				TclAPI.Tcl_SetResult(interpreterPointer, result, tclFreeProcSpecialValue);
			}
		}
	}

	public static void AppendResult(string result)
	{
		lock (lockObject)
		{
			if (!(interpreterPointer == IntPtr.Zero))
			{
				TclAPI.Tcl_AppendResult(interpreterPointer, result, IntPtr.Zero);
			}
		}
	}

	public static void AppendResult(string[] results)
	{
		lock (lockObject)
		{
			if (!(interpreterPointer == IntPtr.Zero))
			{
				foreach (string result in results)
				{
					TclAPI.Tcl_AppendResult(interpreterPointer, result, IntPtr.Zero);
				}
			}
		}
	}

	public static string[] SplitList(string list)
	{
		lock (lockObject)
		{
			if (interpreterPointer == IntPtr.Zero)
			{
				throw new Exception("No interpreter");
			}
			int argc;
			IntPtr argvPtr;
			TclResult tclResult = TclAPI.Tcl_SplitList(interpreterPointer, list, out argc, out argvPtr);
			if (tclResult != 0)
			{
				throw new Exception($"Tcl_SplitList() returned {tclResult}");
			}
			string[] result = IntPtrToStringArray(argc, argvPtr);
			TclAPI.Tcl_Free(argvPtr);
			return result;
		}
	}

	public static bool GetBoolean(string s)
	{
		lock (lockObject)
		{
			if (interpreterPointer == IntPtr.Zero)
			{
				throw new Exception("No interpreter");
			}
			int value;
			TclResult tclResult = TclAPI.Tcl_GetBoolean(interpreterPointer, s, out value);
			if (tclResult != 0)
			{
				throw new Exception($"Tcl_GetBoolean() returned {tclResult}");
			}
			if (value == 0)
			{
				return false;
			}
			return true;
		}
	}

	public static double GetDouble(string s)
	{
		lock (lockObject)
		{
			if (interpreterPointer == IntPtr.Zero)
			{
				throw new Exception("No interpreter");
			}
			double value;
			TclResult tclResult = TclAPI.Tcl_GetDouble(interpreterPointer, s, out value);
			if (tclResult != 0)
			{
				throw new Exception($"Tcl_GetDouble() returned {tclResult}");
			}
			return value;
		}
	}

	public static int GetInt(string s)
	{
		lock (lockObject)
		{
			if (interpreterPointer == IntPtr.Zero)
			{
				throw new Exception("No interpreter");
			}
			int value;
			TclResult tclResult = TclAPI.Tcl_GetInt(interpreterPointer, s, out value);
			if (tclResult != 0)
			{
				throw new Exception($"Tcl_GetInt() returned {tclResult}");
			}
			return value;
		}
	}

	private static string[] IntPtrToStringArray(int stringCount, IntPtr pointer)
	{
		IntPtr[] array = new IntPtr[stringCount];
		string[] array2 = new string[stringCount];
		Marshal.Copy(pointer, array, 0, stringCount);
		for (int i = 0; i < stringCount; i++)
		{
			array2[i] = Marshal.PtrToStringAnsi(array[i]);
		}
		return array2;
	}
}
