using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Windows.Forms;

namespace QCRPG.Framework;

public class ScripterBase
{
	private const string PARAMS_INIT_FILE = "wdsParamsInit.tcl";

	protected Dictionary<string, TclCommandDelegate> tclCommandMap = new Dictionary<string, TclCommandDelegate>();

	protected ModeAgnosticDataReceiver modeAgnosticDataReceiver;

	private GenericNotifier<ScriptStartedInfo> scriptStartedNotifier = new GenericNotifier<ScriptStartedInfo>();

	private GenericNotifier<ScriptFinishedInfo> scriptFinishedNotifier = new GenericNotifier<ScriptFinishedInfo>();

	private static GenericNotifier<LogMessage> logMessageNotifier = new GenericNotifier<LogMessage>();

	private static GenericNotifier<AteDelayInfo> ateDelayInfoNotifier = new GenericNotifier<AteDelayInfo>();

	private AutoResetEvent scriptFinishedEvent = new AutoResetEvent(initialState: false);

	private TclResult result;

	private string errorInfo;

	private object lockObject = new object();

	private string tclInstallPath = "";

	private string postInitTclCode = "";

	private bool scriptRunningFromThisScripter;

	private static bool scriptRunningFromAnyScripter = false;

	private bool btDevices;

	private bool leDevices;

	private bool overrideParamVariables;

	protected Dictionary<string, string> overrideParamVariableList = new Dictionary<string, string>();

	public Dictionary<string, string> OverrideParamVariableList => overrideParamVariableList;

	public void Initialize(string tclInstallPath, string postInitTclCode)
	{
		this.tclInstallPath = tclInstallPath;
		this.postInitTclCode = postInitTclCode;
		TclWrapper.finishedRunningTclCodeHandler += ScriptFinishedHandler;
		EnsureCommonCommandsAreAdded();
	}

	public void RunScriptAsync(string filename, string workingDirectory = "")
	{
		InitializeWrapper();
		scriptFinishedEvent.Reset();
		StartupReceiver();
		scriptStartedNotifier.NotifyObservers(new ScriptStartedInfo(filename));
		if (!File.Exists(filename))
		{
			scriptFinishedNotifier.NotifyObservers(new ScriptFinishedInfo(TclResult.ERROR, "File not found: " + filename));
			RemoveCommands();
			return;
		}
		try
		{
			TclWrapper.RunScript(filename, workingDirectory);
		}
		catch (Exception ex)
		{
			RemoveCommands();
			throw ex;
		}
		lock (lockObject)
		{
			scriptRunningFromThisScripter = true;
			scriptRunningFromAnyScripter = true;
		}
	}

	public bool RunScriptSync(string filename, string workingDirectory = "")
	{
		InitializeWrapper();
		scriptFinishedEvent.Reset();
		StartupReceiver();
		scriptStartedNotifier.NotifyObservers(new ScriptStartedInfo(filename));
		try
		{
			TclWrapper.RunScript(filename, workingDirectory);
		}
		catch (Exception ex)
		{
			RemoveCommands();
			throw ex;
		}
		lock (lockObject)
		{
			scriptRunningFromThisScripter = true;
			scriptRunningFromAnyScripter = true;
		}
		scriptFinishedEvent.WaitOne();
		if (result != 0)
		{
			return false;
		}
		return true;
	}

	public bool RunScriptSync(string filename, int timeoutMs, string workingDirectory = "")
	{
		InitializeWrapper();
		scriptFinishedEvent.Reset();
		StartupReceiver();
		scriptStartedNotifier.NotifyObservers(new ScriptStartedInfo(filename));
		try
		{
			TclWrapper.RunScript(filename, workingDirectory);
		}
		catch (Exception ex)
		{
			RemoveCommands();
			throw ex;
		}
		lock (lockObject)
		{
			scriptRunningFromThisScripter = true;
			scriptRunningFromAnyScripter = true;
		}
		if (!scriptFinishedEvent.WaitOne(timeoutMs))
		{
			TclWrapper.StopCodeOrScript();
			throw new TimeoutException("Timeout");
		}
		if (result != 0)
		{
			return false;
		}
		return true;
	}

	public void StopScript()
	{
		lock (lockObject)
		{
			if (!scriptRunningFromAnyScripter)
			{
				return;
			}
			if (!scriptRunningFromThisScripter)
			{
				throw new Exception("Can only stop the script from the object that started it");
			}
		}
		TclWrapper.StopCodeOrScript();
	}

	public string GetErrorInfo()
	{
		lock (lockObject)
		{
			return errorInfo;
		}
	}

	public void RegisterScriptStartedObserver(IObserver<ScriptStartedInfo> observer)
	{
		scriptStartedNotifier.RegisterObserver(observer);
	}

	public void UnregisterScriptStartedObserver(IObserver<ScriptStartedInfo> observer)
	{
		scriptStartedNotifier.UnregisterObserver(observer);
	}

	public void RegisterScriptFinishedObserver(IObserver<ScriptFinishedInfo> observer)
	{
		scriptFinishedNotifier.RegisterObserver(observer);
	}

	public void UnregisterScriptFinishedObserver(IObserver<ScriptFinishedInfo> observer)
	{
		scriptFinishedNotifier.UnregisterObserver(observer);
	}

	public static void RegisterLogMessageObserver(IObserver<LogMessage> observer)
	{
		logMessageNotifier.RegisterObserver(observer);
	}

	public static void UnregisterLogMessageObserver(IObserver<LogMessage> observer)
	{
		logMessageNotifier.UnregisterObserver(observer);
	}

	public static void RegisterAteDelayObserver(IObserver<AteDelayInfo> observer)
	{
		ateDelayInfoNotifier.RegisterObserver(observer);
	}

	public static void UnregisterAteDelayObserver(IObserver<AteDelayInfo> observer)
	{
		ateDelayInfoNotifier.UnregisterObserver(observer);
	}

	public string[] GetTclCommandList()
	{
		List<string> list = new List<string>();
		foreach (string key in tclCommandMap.Keys)
		{
			list.Add(key);
		}
		return list.ToArray();
	}

	public Dictionary<string, TclCommandDelegate> GetTclCommandToDelegateMap()
	{
		return tclCommandMap;
	}

	private void AddCommands()
	{
		new List<string>();
		foreach (string key in tclCommandMap.Keys)
		{
			TclWrapper.AddCommand(key, tclCommandMap[key]);
		}
	}

	private void RemoveCommands()
	{
		new List<string>();
		foreach (string key in tclCommandMap.Keys)
		{
			TclWrapper.RemoveCommand(key);
		}
	}

	public virtual void StartupReceiver()
	{
		if (modeAgnosticDataReceiver != null)
		{
			modeAgnosticDataReceiver.Startup();
		}
	}

	public virtual void ShutdownReceiver()
	{
		if (modeAgnosticDataReceiver != null)
		{
			modeAgnosticDataReceiver.Shutdown();
		}
	}

	protected void ScriptFinishedHandler(TclResult result, string errorInfo)
	{
		if (scriptRunningFromThisScripter)
		{
			lock (lockObject)
			{
				this.result = result;
				this.errorInfo = errorInfo;
			}
			RemoveCommands();
			lock (lockObject)
			{
				scriptRunningFromAnyScripter = false;
				scriptRunningFromThisScripter = false;
			}
			ShutdownReceiver();
			scriptFinishedNotifier.NotifyObservers(new ScriptFinishedInfo(result, errorInfo));
			scriptFinishedEvent.Set();
		}
	}

	public void EnableOverrideParamVariables(bool overrideParamVariables)
	{
		this.overrideParamVariables = overrideParamVariables;
	}

	public void SetOverrideParamVariable(string param, string value)
	{
		overrideParamVariableList[param] = value;
	}

	private void InitializeWrapper()
	{
		TclWrapper.SetTclInstallPath(tclInstallPath);
		TclWrapper.SetPostInitTclCode(postInitTclCode);
		AddCommands();
		if (overrideParamVariables)
		{
			WriteOverrideParamsInitFile(tclInstallPath);
		}
	}

	private void EnsureCommonCommandsAreAdded()
	{
		List<string> list = new List<string>();
		list.AddRange(TclWrapper.GetCommands());
		if (!list.Contains("HCI_Wait_Tm"))
		{
			TclWrapper.AddCommand("HCI_Wait_Tm", HCI_Wait_Tm_Handler);
		}
		if (!list.Contains("Wait"))
		{
			TclWrapper.AddCommand("Wait", HCI_Wait_Tm_Handler);
		}
		if (!list.Contains("Log"))
		{
			TclWrapper.AddCommand("Log", Log_Handler);
		}
		if (!list.Contains("LogATEDelayMicroseconds"))
		{
			TclWrapper.AddCommand("LogATEDelayMicroseconds", LogATEDelayMicroseconds_Handler);
		}
		if (!list.Contains("pause"))
		{
			TclWrapper.AddCommand("pause", Pause_Handler);
		}
	}

	private static TclResult HCI_Wait_Tm_Handler(string[] args)
	{
		int num = 0;
		if (args.Length != 2)
		{
			TclWrapper.SetResult($"Wrong # of arguments.  Usage: {args[0]} ?delayMs?");
			return TclResult.ERROR;
		}
		try
		{
			num = Conversions.PrefixedStringToInt(args[1]);
		}
		catch (Exception)
		{
			TclWrapper.SetResult($"Error converting delayMs {args[1]} to an int");
			return TclResult.ERROR;
		}
		Thread.Sleep(num);
		TclWrapper.SetResult("");
		return TclResult.OK;
	}

	private static TclResult Log_Handler(string[] args)
	{
		if (args.Length != 3)
		{
			TclWrapper.SetResult($"Wrong # of arguments.  Usage: {args[0]} ?messageType? ?message?");
			return TclResult.ERROR;
		}
		string text = args[1].ToUpper();
		LogMessageType type;
		if (text.StartsWith("INFO"))
		{
			type = LogMessageType.Info;
		}
		else if (text.StartsWith("WARN"))
		{
			type = LogMessageType.Warning;
		}
		else if (text.StartsWith("ERR"))
		{
			type = LogMessageType.Error;
		}
		else
		{
			if (!text.StartsWith("VER"))
			{
				TclWrapper.SetResult("Invalid log message type: " + text);
				return TclResult.ERROR;
			}
			type = LogMessageType.Verbose;
		}
		LogMessage arg = new LogMessage(type, args[2]);
		logMessageNotifier.NotifyObservers(arg);
		return TclResult.OK;
	}

	private static TclResult LogATEDelayMicroseconds_Handler(string[] args)
	{
		if (args.Length != 2 && args.Length != 3)
		{
			TclWrapper.SetResult(string.Format("Wrong # of arguments.  Usage: {0} ?delayInMicroseconds? or {0} ?delayInMicroseconds? ?message?", args[0]));
			return TclResult.ERROR;
		}
		int delayMicroseconds;
		try
		{
			delayMicroseconds = Conversions.PrefixedStringToInt(args[1]);
		}
		catch (Exception)
		{
			TclWrapper.SetResult($"Error converting delayInMicroseconds {args[1]} to an int");
			return TclResult.ERROR;
		}
		string message = "";
		if (args.Length == 3)
		{
			message = args[2];
		}
		AteDelayInfo arg = new AteDelayInfo(delayMicroseconds, message);
		ateDelayInfoNotifier.NotifyObservers(arg);
		return TclResult.OK;
	}

	private static TclResult Pause_Handler(string[] args)
	{
		if (args.Length != 1 && args.Length != 2)
		{
			TclWrapper.SetResult(string.Format("Wrong # of arguments.  Usage: {0} or {0} ?message?", args[0]));
			return TclResult.ERROR;
		}
		string text = ((args.Length == 2) ? $"Mesg '{args[1]}', " : "") + "Press 'Yes' to continue, 'No' to stop script";
		DialogResult dialogResult = MessageBox.Show(text, "Script Pause", MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1);
		if (dialogResult == DialogResult.No)
		{
			TclWrapper.AppendResult("Script aborted");
			return TclResult.ERROR;
		}
		return TclResult.OK;
	}

	private bool WriteOverrideParamsInitFile(string workingDirectory = "")
	{
		StreamWriter streamWriter = new StreamWriter(workingDirectory + "\\wdsParamsInit.tcl");
		string[] array = new string[12]
		{
			"## Generated at: " + DateTime.Now.ToString("MMMM dd, YYYY - HH:mm:ss"),
			"##############################################################",
			"## wdsParamsInit.tcl - Do not edit, created by application",
			"##############################################################",
			"",
			"##############################################################",
			"## Variables used in the scripts",
			"##############################################################",
			"",
			"set tempLine    \"***********************\"",
			"Log WARN \"$tempLine Overriding following parameters $tempLine\"",
			""
		};
		string[] array2 = new string[4] { "Log WARN \"$tempLine End of Overriding parameters $tempLine\"", "##############################################################", "## End of wdsParamsInit.tcl", "##############################################################" };
		for (int i = 0; i < array.Length; i++)
		{
			streamWriter.WriteLine(array[i]);
		}
		foreach (KeyValuePair<string, string> overrideParamVariable in overrideParamVariableList)
		{
			streamWriter.WriteLine($"set {overrideParamVariable.Key} {overrideParamVariable.Value}");
			streamWriter.WriteLine(string.Format("Log WARN \"{0} override : {0}: ${0}\" \n", overrideParamVariable.Key));
		}
		for (int j = 0; j < array2.Length; j++)
		{
			streamWriter.WriteLine(array2[j]);
		}
		streamWriter.Close();
		return true;
	}
}
