#region Header
//               _,-'/-'/
//   .      __,-; ,'( '/
//    \.    `-.__`-._`:_,-._       _ , . ``
//     `:-._,------' ` _,`--` -: `_ , ` ,' :
//        `---..__,,--'  (C) 2023  ` -'. -'
//        #  Vita-Nex [http://core.vita-nex.com]  #
//  {o)xxx|===============-   #   -===============|xxx(o}
//        #                                       #
#endregion

#region References
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;

using Server;
using Server.Commands;
using Server.Gumps;

using VitaNex.IO;
using VitaNex.Network;
using VitaNex.SuperGumps;
using VitaNex.SuperGumps.UI;
#endregion

namespace VitaNex
{
	public static class TaskPriority
	{
		public const int Highest = 0, High = 250000, Medium = 500000, Low = 750000, Lowest = 1000000;
	}

	/// <summary>
	///     Exposes an interface for managing VitaNexCore and its' sub-systems.
	/// </summary>
	public static partial class VitaNexCore
	{
		public const AccessLevel Access = AccessLevel.Administrator;

		public static VersionInfo Version { get; } = new VersionInfo(5, 3, 1, 0)
		{
			Name = "VitaNexCore",
			Description = "Represents the local version value of Vita-Nex: Core"
		};

		private static readonly CallPriorityComparer _PriorityComparer = new CallPriorityComparer();

		public static readonly object ConsoleLock = new object();
		public static readonly object IOLock = new object();

		private static readonly DateTime _Started = DateTime.UtcNow;

		public static long Ticks
		{
			get
			{
				if (Stopwatch.IsHighResolution && !Core.Unix)
				{
					return (long)(Stopwatch.GetTimestamp() * (1000.0 / Stopwatch.Frequency));
				}

				return (long)(DateTime.UtcNow.Ticks * (1000.0 / TimeSpan.TicksPerSecond));
			}
		}

		private static long _Tick = Ticks;

		public static long Tick => _Tick;

		private static readonly List<ICorePluginInfo> _Plugins = new List<ICorePluginInfo>(0x20);

		public static IEnumerable<ICorePluginInfo> Plugins
		{
			get
			{
				var idx = _Plugins.Count;

				while (--idx >= 0)
				{
					if (_Plugins.InBounds(idx))
					{
						yield return _Plugins[idx];
					}
				}
			}
		}

		public static int PluginCount => _Plugins.Count;

		/// <summary>
		///     Gets the amount of time that has passed since VitaNexCore was first initialized.
		/// </summary>
		public static TimeSpan UpTime => DateTime.UtcNow - _Started;

		/// <summary>
		///     Gets the root directory for VitaNexCore.
		///     This is the directory where the core scripts reside.
		/// </summary>
		public static DirectoryInfo RootDirectory { get; private set; }

		/// <summary>
		///     Gets the working directory for VitaNexCore.
		/// </summary>
		public static DirectoryInfo BaseDirectory { get; private set; }

		/// <summary>
		///     Gets the build directory for VitaNexCore
		/// </summary>
		public static DirectoryInfo BuildDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Build/");

		/// <summary>
		///     Gets the data directory for VitaNexCore.
		/// </summary>
		public static DirectoryInfo DataDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Data/");

		/// <summary>
		///     Gets the cache directory for VitaNexCore
		/// </summary>
		public static DirectoryInfo CacheDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Cache/");

		/// <summary>
		///     Gets the services directory for VitaNexCore
		/// </summary>
		public static DirectoryInfo ServicesDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Services/");

		/// <summary>
		///     Gets the modules directory for VitaNexCore
		/// </summary>
		public static DirectoryInfo ModulesDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Modules/");

		/// <summary>
		///     Gets the saves backup directory for VitaNexCore
		/// </summary>
		public static DirectoryInfo BackupDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Backups/");

		/// <summary>
		///     Gets the saves directory for VitaNexCore
		/// </summary>
		public static DirectoryInfo SavesDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Saves/");

		/// <summary>
		///     Gets the logs directory for VitaNexCore
		/// </summary>
		public static DirectoryInfo LogsDirectory => IOUtility.EnsureDirectory(BaseDirectory + "/Logs/");

		/// <summary>
		///     Gets a file used for unhandled and generec exception logging.
		/// </summary>
		public static FileInfo LogFile => IOUtility.EnsureFile(LogsDirectory + "/Logs (" + DateTime.Now.ToSimpleString("D d M y") + ").log");

		/// <summary>
		///     Gets a value representing whether VitaNexCore is busy performing a save or load action
		/// </summary>
		public static bool Busy { get; private set; }

		/// <summary>
		///     Gets a value representing the compile state of VitaNexCore
		/// </summary>
		public static bool Compiled { get; private set; }

		/// <summary>
		///     Gets a value representing the configure state of VitaNexCore
		/// </summary>
		public static bool Configured { get; private set; }

		/// <summary>
		///     Gets a value representing the initialize state of VitaNexCore
		/// </summary>
		public static bool Initialized { get; private set; }

		/// <summary>
		///     Gets a value representing whether this run is the first boot of VitaNexCore
		/// </summary>
		public static bool FirstBoot { get; private set; }

		public static bool Disposing { get; private set; }
		public static bool Disposed { get; private set; }

		public static bool Crashed { get; private set; }

		public static TimeSpan BackupExpireAge { get; set; }

		public static event Action OnCompiled;
		public static event Action OnConfigured;
		public static event Action OnInitialized;
		public static event Action OnBackup;
		public static event Action OnSaved;
		public static event Action OnLoaded;
		public static event Action OnDispose;
		public static event Action OnDisposed;
		public static event Action<Exception> OnExceptionThrown;

		/// <summary>
		///     Configure method entry point, called by ScriptCompiler during compile of 'Scripts' directory.
		///     Performs a global invoke action, processing all Services and Modules that support CSConfig() and CMConfig()
		/// </summary>
		[CallPriority(Int32.MaxValue)]
		public static void Configure()
		{
			if (Configured)
			{
				return;
			}

			/// DisplayRetroBoot();

			CommandUtility.Register("VNC", AccessLevel.Administrator, OnCoreCommand);

			OutgoingPacketOverrides.Init();
			ExtendedOPL.Init();

			var now = DateTime.UtcNow;

			ToConsole(String.Empty);
			ToConsole("Compile action started...");

			TryCatch(CompileServices, ToConsole);
			TryCatch(CompileModules, ToConsole);

			Compiled = true;

			InvokeByPriority(OnCompiled);

			var time = (DateTime.UtcNow - now).TotalSeconds;

			ToConsole("Compile action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty);

			now = DateTime.UtcNow;

			ToConsole(String.Empty);
			ToConsole("Configure action started...");

			TryCatch(ConfigureServices, ToConsole);
			TryCatch(ConfigureModules, ToConsole);

			Configured = true;

			InvokeByPriority(OnConfigured);

			time = (DateTime.UtcNow - now).TotalSeconds;

			ToConsole("Configure action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty);

			ProcessINIT();

			EventSink.ServerStarted += () =>
			{
				EventSink.WorldSave += e =>
				{
					TryCatch(Backup, ToConsole);
					TryCatch(Save, ToConsole);
				};
				EventSink.Shutdown += e => TryCatch(Dispose, ToConsole);
				EventSink.Crashed += e => TryCatch(Dispose, ToConsole);
			};

			try
			{
				var crashed = typeof(EventSink).GetEventDelegates("Crashed");

				foreach (var m in crashed.OfType<CrashedEventHandler>())
				{
					EventSink.Crashed -= m;
				}

				EventSink.Crashed += e => Crashed = true;

				foreach (var m in crashed.OfType<CrashedEventHandler>())
				{
					EventSink.Crashed += m;
				}
			}
			catch (Exception x)
			{
				ToConsole(x);

				EventSink.Crashed += e => Crashed = true;
			}
		}

		/// <summary>
		///     Initialize method entry point, called by ScriptCompiler after compile of 'Scripts' directory.
		///     Performs a global invoke action, processing all Services and Modules that support CSInvoke() and CMInvoke()
		/// </summary>
		[CallPriority(Int32.MaxValue)]
		public static void Initialize()
		{
			if (Initialized)
			{
				return;
			}

			TryCatch(Load, ToConsole);

			var now = DateTime.UtcNow;

			ToConsole(String.Empty);
			ToConsole("Invoke action started...");

			TryCatch(InvokeServices, ToConsole);
			TryCatch(InvokeModules, ToConsole);

			Initialized = true;

			InvokeByPriority(OnInitialized);

			var time = (DateTime.UtcNow - now).TotalSeconds;

			ToConsole("Invoke action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty);
		}

		/// <summary>
		///     Performs a global save action, processing all Services and Modules that support CSSave() and CMSave()
		/// </summary>
		public static void Save()
		{
			if (Busy)
			{
				ToConsole("Could not perform save action, the service is busy.");
				return;
			}

			Busy = true;

			var now = DateTime.UtcNow;

			try
			{
				ToConsole(String.Empty);
				ToConsole("Save action started...");

				TryCatch(SaveServices, ToConsole);
				TryCatch(SaveModules, ToConsole);

				InvokeByPriority(OnSaved);
			}
			finally
			{
				Busy = false;
			}

			var time = (DateTime.UtcNow - now).TotalSeconds;

			ToConsole("Save action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty);
		}

		/// <summary>
		///     Performs a global load action, processing all Services and Modules that support CSLoad() and CMLoad()
		/// </summary>
		public static void Load()
		{
			if (Busy)
			{
				ToConsole("Could not perform load action, the service is busy.");
				return;
			}

			Busy = true;

			var now = DateTime.UtcNow;

			try
			{
				ToConsole(String.Empty);
				ToConsole("Load action started...");

				TryCatch(LoadServices, ToConsole);
				TryCatch(LoadModules, ToConsole);

				InvokeByPriority(OnLoaded);
			}
			finally
			{
				Busy = false;
			}

			var time = (DateTime.UtcNow - now).TotalSeconds;

			ToConsole("Load action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty);
		}

		/// <summary>
		///     Performs a global dispose action, processing all Services and Modules that support CSDispose() and CMDispose()
		/// </summary>
		public static void Dispose()
		{
			if (Busy || Disposing || Disposed)
			{
				ToConsole("Could not perform dispose action, the service is busy.");
				return;
			}

			Busy = Disposing = Disposed = true;

			var now = DateTime.UtcNow;

			try
			{
				ToConsole(String.Empty);
				ToConsole("Dispose action started...");

				InvokeByPriority(OnDispose);

				TryCatch(DisposeServices, ToConsole);
				TryCatch(DisposeModules, ToConsole);

				InvokeByPriority(OnDisposed);
			}
			finally
			{
				Busy = Disposing = false;
			}

			var time = (DateTime.UtcNow - now).TotalSeconds;

			ToConsole("Dispose action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty);
		}

		/// <summary>
		///     Performs a global backup action, copying all files in the SavesDirectory to the BackupDirectory.
		/// </summary>
		public static void Backup()
		{
			if (Busy)
			{
				ToConsole("Could not perform backup action, the service is busy.");
				return;
			}

			Busy = true;

			var now = DateTime.UtcNow;

			try
			{
				ToConsole(String.Empty);
				ToConsole("Backup action started...");

				if (BackupExpireAge > TimeSpan.Zero)
				{
					ToConsole("Backup Expire Age: {0}", BackupExpireAge);

					lock (IOLock)
					{
						BackupDirectory.EmptyDirectory(BackupExpireAge);
					}
				}

				lock (IOLock)
				{
					SavesDirectory.CopyDirectory(
						IOUtility.EnsureDirectory(BackupDirectory + "/" + DateTime.Now.ToSimpleString("D d M y"), true));
				}

				InvokeByPriority(OnBackup);
			}
			finally
			{
				Busy = false;
			}

			var time = (DateTime.UtcNow - now).TotalSeconds;

			ToConsole("Backup action completed in {0:F2} second{1}", time, (time != 1) ? "s" : String.Empty);
		}

		private static void OnCoreCommand(CommandEventArgs e)
		{
			if (e == null || e.Mobile == null || e.Mobile.Deleted)
			{
				return;
			}

			var cmd = e.GetString(0);
			var search = e.GetString(1);

			switch (cmd.ToLower())
			{
				case "srv":
				{
					var cs = !String.IsNullOrWhiteSpace(search)
						? Services.FirstOrDefault(o => Insensitive.Contains(o.Name, search))
						: null;

					VitaNexCoreUI.DisplayTo(e.Mobile, cs);
				}
				break;
				case "mod":
				{
					var cm = !String.IsNullOrWhiteSpace(search)
						? Modules.FirstOrDefault(o => Insensitive.Contains(o.Name, search))
						: null;

					VitaNexCoreUI.DisplayTo(e.Mobile, cm);
				}
				break;
				case "plg":
				{
					var cp = !String.IsNullOrWhiteSpace(search)
						? Plugins.FirstOrDefault(o => Insensitive.Contains(o.Name, search))
						: null;

					VitaNexCoreUI.DisplayTo(e.Mobile, cp);
				}
				break;
				default:
					VitaNexCoreUI.DisplayTo(e.Mobile);
					break;
			}
		}

		private static void InvokeByPriority(Delegate action)
		{
			if (action == null)
			{
				return;
			}

			var list = action.GetInvocationList();

			foreach (var d in list.OrderBy(d => d.Method, _PriorityComparer))
			{
				TryCatch(d, ToConsole);
			}
		}

		public static T TryCatchGet<T>(Func<T> func)
		{
			return TryCatchGet(func, ToConsole);
		}

		public static T TryCatchGet<T>(Func<T> func, Action<Exception> handler)
		{
			if (func == null)
			{
				return default(T);
			}

			try
			{
				return func();
			}
			catch (Exception e)
			{
				if (handler == null)
				{
					ToConsole("{0} at {1}:", e.GetType(), Trace(func));
				}
				else
				{
					handler(e);
				}
			}

			return default(T);
		}

		public static T TryCatchGet<T, TState>(Func<TState, T> func, TState state)
		{
			return TryCatchGet(func, state, ToConsole);
		}

		public static T TryCatchGet<T, TState>(Func<TState, T> func, TState state, Action<Exception> handler)
		{
			if (func == null)
			{
				return default(T);
			}

			try
			{
				return func(state);
			}
			catch (Exception e)
			{
				if (handler == null)
				{
					ToConsole("{0} at {1}:", e.GetType(), Trace(func));
				}
				else
				{
					handler(e);
				}
			}

			return default(T);
		}

		public static void TryCatch(Delegate action)
		{
			TryCatch(action, ToConsole);
		}

		public static void TryCatch(Delegate action, Action<Exception> handler)
		{
			if (action == null)
			{
				return;
			}

			try
			{
				if (action.Method.IsStatic)
				{
					action.Method.Invoke(null, null);
				}
				else
				{
					action.Method.Invoke(action.Target, null);
				}
			}
			catch (Exception e)
			{
				if (handler == null)
				{
					ToConsole("{0} at {1}:", e.GetType(), Trace(action));
				}
				else
				{
					handler(e);
				}
			}
		}

		public static void TryCatch(Action action)
		{
			TryCatch(action, ToConsole);
		}

		public static void TryCatch(Action action, Action<Exception> handler)
		{
			if (action == null)
			{
				return;
			}

			try
			{
				action();
			}
			catch (Exception e)
			{
				if (handler == null)
				{
					ToConsole("{0} at {1}:", e.GetType(), Trace(action));
				}
				else
				{
					handler(e);
				}
			}
		}

		public static void TryCatch<T>(Action<T> action, T state)
		{
			TryCatch(action, state, ToConsole);
		}

		public static void TryCatch<T>(Action<T> action, T state, Action<Exception> handler)
		{
			if (action == null)
			{
				return;
			}

			try
			{
				action(state);
			}
			catch (Exception e)
			{
				if (handler == null)
				{
					ToConsole("{0} at {1}:", e.GetType(), Trace(action));
				}
				else
				{
					handler(e);
				}
			}
		}

		public static void WaitWhile(Func<bool> func)
		{
			WaitWhile(func, TimeSpan.MaxValue);
		}

		public static void WaitWhile(Func<bool> func, TimeSpan timeOut)
		{
			if (func == null)
			{
				return;
			}

			var expire = DateTime.UtcNow.Add(timeOut);

			var exit = false;

			while (!exit)
			{
				exit = !func();

				if (DateTime.UtcNow >= expire)
				{
					break;
				}

				Thread.Sleep(1);
			}
		}

		public static void ToConsole(string format, params object[] args)
		{
			lock (ConsoleLock)
			{
				Console.Write('[');
				Utility.PushColor(ConsoleColor.Yellow);
				Console.Write("VitaNexCore");
				Utility.PopColor();
				Console.Write("]: ");
				Utility.PushColor(ConsoleColor.DarkYellow);

				if (args.Length > 0)
				{
					Console.WriteLine(format, args);
				}
				else
				{
					Console.WriteLine(format);
				}

				Utility.PopColor();
			}
		}

		public static void ToConsole(Exception e)
		{
			lock (ConsoleLock)
			{
				Console.Write('[');
				Utility.PushColor(ConsoleColor.Yellow);
				Console.Write("VitaNexCore");
				Utility.PopColor();
				Console.Write("]: ");
				Utility.PushColor(ConsoleColor.Red);
				Console.WriteLine(e);
				Utility.PopColor();
			}

			e.Log(LogFile);

			InvokeByPriority(OnExceptionThrown);
		}

		public static string Trace(this Delegate d)
		{
			return Trace(d, false);
		}

		public static string Trace(this Delegate d, bool output)
		{
			if (d == null)
			{
				return "NULL";
			}

			var value = "";

			var dt = d.Method.DeclaringType;

			while (dt != null)
			{
				value = dt.Name + '.' + value;

				dt = dt.DeclaringType;
			}

			value += d.Method.Name;

			if (output)
			{
				ToConsole("Trace: {0}", value);
			}

			return value;
		}

		private const ConsoleColor _BackgroundColor = ConsoleColor.Black;
		private const ConsoleColor _BorderColor = ConsoleColor.Green;
		private const ConsoleColor _TextColor = ConsoleColor.White;

		private static void DrawLine(string text = "", int align = 0)
		{
			text = text ?? String.Empty;
			align = Math.Max(0, Math.Min(2, align));

			var defBG = Console.BackgroundColor;

			const int borderWidth = 2;
			const int indentWidth = 1;

			var maxWidth = Math.Max(80, Console.WindowWidth) - ((borderWidth + indentWidth) * 2);

			var lines = new List<string>();

			if (text.Length > maxWidth)
			{
				var words = text.Split(' ');

				if (words.Length == 0)
				{
					for (int i = 0, offset = 0, count; i < (text.Length / maxWidth); i++)
					{
						lines.Add(text.Substring(offset, (count = Math.Min(text.Length - offset, maxWidth))));
						offset += count;
					}
				}
				else
				{
					var rebuild = String.Empty;

					for (var wi = 0; wi < words.Length; wi++)
					{
						if (rebuild.Length + (words[wi].Length + 1) <= maxWidth)
						{
							rebuild += words[wi] + ' ';
						}
						else
						{
							lines.Add(rebuild);
							rebuild = words[wi] + ' ';
						}

						if (wi + 1 >= words.Length)
						{
							lines.Add(rebuild);
						}
					}
				}
			}
			else
			{
				lines.Add(text);
			}

			lock (ConsoleLock)
			{
				Utility.PushColor(_TextColor);

				foreach (var line in lines)
				{
					Console.BackgroundColor = _BorderColor;
					Console.Write(new string(' ', borderWidth));
					Console.BackgroundColor = _BackgroundColor;
					Console.Write(new string(' ', indentWidth));

					var len = maxWidth - line.Length;
					var str = line;

					switch (align)
					{
						//Center
						case 1:
							str = new string(' ', len / 2) + str + new string(' ', len / 2);
							break;
						//Right
						case 2:
							str = new string(' ', len) + str;
							break;
					}

					if (str.Length < maxWidth)
					{
						str += new string(' ', maxWidth - str.Length);
					}

					Console.Write(str);
					Console.Write(new string(' ', indentWidth));
					Console.BackgroundColor = _BorderColor;
					Console.Write(new string(' ', borderWidth));
				}

				lines.Free(true);

				Console.BackgroundColor = defBG;
				Utility.PopColor();
			}
		}

		private static void DisplayRetroBoot()
		{
			ConsoleColor defBG;

			lock (ConsoleLock)
			{
				defBG = Console.BackgroundColor;

				Console.WriteLine();

				Console.BackgroundColor = _BorderColor;
				Console.CursorLeft = 0;

				Console.Write(new string(' ', Math.Max(80, Console.WindowWidth)));
			}

			DrawLine();
			DrawLine("**** VITA-NEX: CORE " + Version + " ****", 1);
			DrawLine();

			DrawLine("Root Directory:     " + RootDirectory.FullName.Replace(Core.BaseDirectory, "."));
			DrawLine("Working Directory:  " + BaseDirectory.FullName.Replace(Core.BaseDirectory, "."));
			DrawLine();

			DrawLine("http://core.vita-nex.com", 1);
			DrawLine();

			if (FirstBoot)
			{
				try
				{
					var readme = IOUtility.GetSafeFilePath(RootDirectory + "/LICENSE.md", true);

					var lines = File.ReadAllLines(readme);

					lines.ForEach(line => DrawLine(line));

					DrawLine();
				}
				catch 
				{ }
			}

			if (Core.Debug)
			{
				DrawLine("Server is running in DEBUG mode.");
				DrawLine();
			}

			lock (ConsoleLock)
			{
				Console.BackgroundColor = _BorderColor;
				Console.Write(new string(' ', Console.WindowWidth));

				Console.BackgroundColor = defBG;
				Utility.PopColor();
				Console.WriteLine();
			}
		}

		public static void RegisterPlugin(ICorePluginInfo cp)
		{
			if (cp == null)
			{
				return;
			}

			_Plugins.Update(cp);

			TryCatch(cp.OnRegistered, cp.ToConsole);
		}
	}

	public sealed class VitaNexCoreUI : TreeGump
	{
		public static void DisplayTo(Mobile user, CoreServiceInfo cs)
		{
			var node = "Plugins|Services";

			if (cs != null)
			{
				node += "|" + cs.FullName;
			}

			DisplayTo(user, false, node);
		}

		public static void DisplayTo(Mobile user, CoreModuleInfo cm)
		{
			var node = "Plugins|Modules";

			if (cm != null)
			{
				node += "|" + cm.FullName;
			}

			DisplayTo(user, false, node);
		}

		public static void DisplayTo(Mobile user, ICorePluginInfo cp)
		{
			if (cp is CoreServiceInfo)
			{
				DisplayTo(user, (CoreServiceInfo)cp);
				return;
			}

			if (cp is CoreModuleInfo)
			{
				DisplayTo(user, (CoreModuleInfo)cp);
				return;
			}

			var node = "Plugins";

			if (cp != null)
			{
				node += "|" + cp.FullName;
			}

			DisplayTo(user, false, node);
		}

		public static void DisplayTo(Mobile user)
		{
			DisplayTo(user, String.Empty);
		}

		public static void DisplayTo(Mobile user, string node)
		{
			DisplayTo(user, false, node);
		}

		public static void DisplayTo(Mobile user, bool refreshOnly)
		{
			DisplayTo(user, refreshOnly, String.Empty);
		}

		public static void DisplayTo(Mobile user, bool refreshOnly, string node)
		{
			if (user == null)
			{
				return;
			}

			if (user.AccessLevel < VitaNexCore.Access)
			{
				user.SendMessage(0x22, "You do not have access to this feature.");
				return;
			}

			var info = EnumerateInstances<VitaNexCoreUI>(user).FirstOrDefault(g => g != null && !g.IsDisposed && g.IsOpen);

			if (info == null)
			{
				if (refreshOnly)
				{
					return;
				}

				info = new VitaNexCoreUI(user);
			}

			if (!String.IsNullOrWhiteSpace(node))
			{
				info.SelectedNode = node;
			}

			info.Refresh(true);
		}

		private List<ICorePluginInfo> _Buffer;
		private int[,] _Indicies;

		private VitaNexCoreUI(Mobile user)
			: base(user)
		{
			_Buffer = new List<ICorePluginInfo>();
			_Indicies = new int[3, 2];

			Width = 800;
			Height = 600;

			Title = "Vita-Nex: Core Control Panel";
		}

		protected override void OnDispose()
		{
			_Buffer.Free(true);
			_Buffer = null;

			_Indicies = null;

			base.OnDispose();
		}

		protected override bool OnBeforeSend()
		{
			if (base.OnBeforeSend() && User.AccessLevel >= VitaNexCore.Access)
			{
				return true;
			}

			User.SendMessage(0x22, "You do not have access to this feature.");
			return false;
		}

		protected override void CompileNodes(Dictionary<TreeGumpNode, Action<Rectangle, int, TreeGumpNode>> list)
		{
			list.Clear();

			list["Core"] = CompileOverview;

			list["Plugins"] = CompilePlugins;
			list["Plugins|Services"] = CompileServices;
			list["Plugins|Modules"] = CompileModules;

			foreach (var p in VitaNexCore.Plugins)
			{
				var name = p.Name;

				if (String.IsNullOrWhiteSpace(name))
				{
					name = p.TypeOf.Name;
				}

				if (p is CoreServiceInfo)
				{
					list["Plugins|Services|" + name] = CompileService;
				}
				else if (p is CoreModuleInfo)
				{
					list["Plugins|Modules|" + name] = CompileModule;
				}
				else
				{
					list["Plugins|" + name] = CompilePlugin;
				}
			}

			base.CompileNodes(list);
		}

		protected override void CompileEmptyNodeLayout(
			SuperGumpLayout layout,
			int x,
			int y,
			int w,
			int h,
			int index,
			TreeGumpNode node)
		{
			base.CompileEmptyNodeLayout(layout, x, y, w, h, index, node);

			layout.Add("node/page/" + index, () => CompileOverview(x, y, w, h));
		}

		private void CompileOverview(Rectangle b, int i, TreeGumpNode n)
		{
			CompileOverview(b.X, b.Y, b.Width, b.Height);
		}

		private void CompileOverview(int x, int y, int w, int h)
		{
			var html = new StringBuilder();

			html.AppendLine();
			html.AppendLine("Vita-Nex: Core " + "{0}".WrapUOHtmlColor(Color.LawnGreen, HtmlColor), VitaNexCore.Version);
			html.AppendLine("A dynamic extension library for RunUO");
			html.AppendLine("<a href=\"http://core.vita-nex.com\">http://core.vita-nex.com</a>");
			html.AppendLine();
			html.AppendLine("Services: " + "{0:#,0}".WrapUOHtmlColor(Color.LawnGreen, HtmlColor), VitaNexCore.ServiceCount);
			html.AppendLine("Modules:  " + "{0:#,0}".WrapUOHtmlColor(Color.LawnGreen, HtmlColor), VitaNexCore.ModuleCount);
			html.AppendLine();

			AddHtml(x + 5, y, w - 10, h, html.ToString().WrapUOHtmlColor(HtmlColor, false), false, true);
		}

		private void CompileService(Rectangle b, int i, TreeGumpNode n)
		{
			var cs = VitaNexCore.Services.FirstOrDefault(o => Insensitive.Equals(n.Name, o.Name));

			if (cs != null)
			{
				CompileService(b.X, b.Y, b.Width, b.Height, cs);
			}
		}

		private void CompileService(int x, int y, int w, int h, CoreServiceInfo cs)
		{
			CompileBufferEntry(x, y, w, h, 0, cs);

			y += 75;
			h -= 75;

			cs.CompileControlPanel(this, x, y, w, h);
		}

		private void CompileModule(Rectangle b, int i, TreeGumpNode n)
		{
			var cm = VitaNexCore.Modules.FirstOrDefault(o => Insensitive.Equals(n.Name, o.Name));

			if (cm != null)
			{
				CompileModule(b.X, b.Y, b.Width, b.Height, cm);
			}
		}

		private void CompileModule(int x, int y, int w, int h, CoreModuleInfo cm)
		{
			CompileBufferEntry(x, y, w, h, 0, cm);

			y += 75;
			h -= 75;

			cm.CompileControlPanel(this, x, y, w, h);
		}

		private void CompilePlugin(Rectangle b, int i, TreeGumpNode n)
		{
			var cp = VitaNexCore.Plugins.FirstOrDefault(o => Insensitive.Equals(n.Name, o.Name));

			if (cp != null)
			{
				CompilePlugin(b.X, b.Y, b.Width, b.Height, cp);
			}
		}

		private void CompilePlugin(int x, int y, int w, int h, ICorePluginInfo cp)
		{
			CompileBufferEntry(x, y, w, h, 0, cp);

			y += 75;
			h -= 75;

			cp.CompileControlPanel(this, x, y, w, h);
		}

		private void CompileServices(Rectangle b, int i, TreeGumpNode n)
		{
			CompileBuffer(0, b.X, b.Y, b.Width, b.Height, VitaNexCore.Services);
		}

		private void CompileModules(Rectangle b, int i, TreeGumpNode n)
		{
			CompileBuffer(1, b.X, b.Y, b.Width, b.Height, VitaNexCore.Modules);
		}

		private void CompilePlugins(Rectangle b, int i, TreeGumpNode n)
		{
			CompileBuffer(2, b.X, b.Y, b.Width, b.Height, VitaNexCore.Plugins);
		}

		private void CompileBuffer(int i, int x, int y, int w, int h, IEnumerable<ICorePluginInfo> plugins)
		{
			_Buffer.Clear();
			_Buffer.AddRange(plugins);
			_Buffer.Sort();

			_Indicies[i, 1] = _Buffer.Count;
			_Indicies[i, 0] = Math.Max(0, Math.Min(_Indicies[i, 1] - 1, _Indicies[i, 0]));

			var count = (int)Math.Floor(h / 55.0);

			var idx = 0;

			foreach (var cp in _Buffer.Skip(_Indicies[i, 0]).Take(count))
			{
				CompileBufferEntry(x, y, w - 20, h, idx++, cp);
			}

			_Buffer.Clear();

			AddScrollbarVM(x + (w - 15), y, h, _Indicies[i, 1], _Indicies[i, 0], (b, n) =>
				{
					_Indicies[i, 0] -= n;
					Refresh(true);
				},
				(b, n) =>
				{
					_Indicies[i, 0] += n;
					Refresh(true);
				});
		}

		private void CompileBufferEntry(int x, int y, int w, int h, int i, ICorePluginInfo cp)
		{
			if (w * h <= 0 || cp == null)
			{
				return;
			}

			var xx = x;
			var yy = y + (i * 57);

			AddRectangle(xx, yy, w, 55, Color.Black, cp.Active ? Color.PaleGoldenrod : Color.Silver, 1);

			xx += 5;
			yy += 5;

			var label = cp.Name.WrapUOHtmlColor(HtmlColor, false);

			AddHtml(xx + 5, yy, w - 80, 40, label, false, false);

			xx = x + (w - 60);

			label = cp.Version.ToString().WrapUOHtmlColor(HtmlColor, false);

			AddHtml(xx, yy, 55, 40, label, false, false);

			xx = x + 5;
			yy += 25;

			Color color, border, fill;

			if (cp.Disposed)
			{
				label = "DISPOSED".WrapUOHtmlBold().WrapUOHtmlCenter();
				color = border = Color.OrangeRed;
				fill = Color.Black;

				AddRectangle(xx, yy, w - 10, 20, fill, border, 1);
				AddHtml(xx, yy, w, 20, label.WrapUOHtmlColor(color, false), false, false);

				return;
			}

			var bw = (w - 10) / 4;

			label = "CONFIG".WrapUOHtmlBold().WrapUOHtmlCenter();
			color = border = Color.PaleGoldenrod;
			fill = Color.Black;

			AddHtmlButton(xx, yy, bw, 20, o => HandleConfig(cp), label, color, fill, border, 1);

			xx += bw;

			label = "DEBUG".WrapUOHtmlBold().WrapUOHtmlCenter();
			color = border = cp.Debug ? Color.LawnGreen : Color.OrangeRed;

			AddHtmlButton(xx, yy, bw, 20, o => HandleDebug(cp), label, color, fill, border, 1);

			xx += bw;

			label = "QUIET".WrapUOHtmlBold().WrapUOHtmlCenter();
			color = border = cp.Quiet ? Color.LawnGreen : Color.OrangeRed;

			AddHtmlButton(xx, yy, bw, 20, o => HandleQuiet(cp), label, color, fill, border, 1);

			xx += bw;

			label = "ACTIVE".WrapUOHtmlBold().WrapUOHtmlCenter();
			color = border = cp.Active ? Color.LawnGreen : Color.OrangeRed;

			AddHtmlButton(xx, yy, bw, 20, o => HandleActive(cp), label, color, fill, border, 1);
		}

		private void HandleConfig(ICorePluginInfo cp)
		{
			Refresh(true);

			if (cp != null && !cp.Disposed)
			{
				User.SendGump(new PropertiesGump(User, cp));
			}
		}

		private void HandleDebug(ICorePluginInfo cp)
		{
			if (cp != null && !cp.Disposed)
			{
				var old = cp.Debug;

				cp.Debug = !cp.Debug;

				if (cp.Debug != old)
				{
					User.SendMessage(85, "[{0}]: Debugging {1}", cp.Name, cp.Debug ? "enabled" : "disabled");
				}
			}

			Refresh(true);
		}

		private void HandleQuiet(ICorePluginInfo cp)
		{
			if (cp != null && !cp.Disposed)
			{
				var old = cp.Quiet;

				cp.Quiet = !cp.Quiet;

				if (cp.Quiet != old)
				{
					User.SendMessage(85, "[{0}]: Using {1} output", cp.Name, cp.Quiet ? "simple" : "extended");
				}
			}

			Refresh(true);
		}

		private void HandleActive(ICorePluginInfo cp)
		{
			if (cp != null && !cp.Disposed)
			{
				var old = cp.Active;

				cp.Active = !cp.Active;

				if (cp.Active != old)
				{
					User.SendMessage(85, "[{0}]: Now {1}", cp.Name, cp.Active ? "active" : "inactive");
				}
			}

			Refresh(true);
		}
	}

	public interface ICorePluginInfo : IEquatable<ICorePluginInfo>, IComparable<ICorePluginInfo>
	{
		bool Active { get; set; }

		bool Configured { get; }
		bool Invoked { get; }
		bool Disposed { get; }

		Assembly DynamicAssembly { get; }
		FileInfo DynamicAssemblyFile { get; }

		bool Dynamic { get; }
		int Priority { get; }
		Type TypeOf { get; }
		VersionInfo Version { get; }

		string Name { get; }
		string FullName { get; }

		bool Debug { get; set; }
		bool Quiet { get; set; }

		void OnRegistered();

		void SaveState();
		void LoadState();

		void SaveOptions();
		void LoadOptions();

		void ToConsole(string[] lines);
		void ToConsole(string format, params object[] args);
		void ToConsole(Exception[] errors);
		void ToConsole(Exception e);

		void CompileControlPanel(SuperGump g, int x, int y, int w, int h);
	}
}
