using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ICSharpCode.ILSpy.AddIn
{
	class ILSpyParameters
	{
		public ILSpyParameters(IEnumerable<string> assemblyFileNames, params string[] arguments)
		{
			this.AssemblyFileNames = assemblyFileNames.ToArray();
			this.Arguments = arguments;
		}

		public string[] AssemblyFileNames { get; private set; }
		public string[] Arguments { get; private set; }
	}

	class ILSpyInstance
	{
		readonly ILSpyParameters parameters;

		public ILSpyInstance(ILSpyParameters parameters = null)
		{
			this.parameters = parameters;
		}

		static string GetILSpyPath()
		{
			var basePath = Path.GetDirectoryName(typeof(ILSpyAddInPackage).Assembly.Location);
			return Path.Combine(basePath, "ILSpy", "ILSpy.exe");
		}

		public void Start()
		{
			var commandLineArguments = parameters?.AssemblyFileNames?.Concat(parameters.Arguments);
			string ilSpyExe = GetILSpyPath();
			var process = new Process() {
				StartInfo = new ProcessStartInfo() {
					FileName = ilSpyExe,
					UseShellExecute = false,
					Arguments = "/navigateTo:none"
				}
			};
			process.Start();

			if ((commandLineArguments != null) && commandLineArguments.Any())
			{
				// Only need a message to started process if there are any parameters to pass
				SendMessage(ilSpyExe, "ILSpy:\r\n" + string.Join(Environment.NewLine, commandLineArguments), true);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "VSTHRD110:Observe result of async calls", Justification = "<Pending>")]
		void SendMessage(string ilSpyExe, string message, bool activate)
		{
			string expectedProcessName = Path.GetFileNameWithoutExtension(ilSpyExe);
			// We wait asynchronously until target window can be found and try to find it multiple times
			Task.Run(async () => {
				bool success = false;
				int remainingAttempts = 20;
				do
				{
					NativeMethods.EnumWindows(
						(hWnd, lParam) => {
							string windowTitle = NativeMethods.GetWindowText(hWnd, 100);
							if (windowTitle.StartsWith("ILSpy", StringComparison.Ordinal))
							{
								string processName = NativeMethods.GetProcessNameFromWindow(hWnd);
								Debug.WriteLine("Found {0:x4}: '{1}' in '{2}'", hWnd, windowTitle, processName);
								if (string.Equals(processName, expectedProcessName, StringComparison.OrdinalIgnoreCase))
								{
									IntPtr result = Send(hWnd, message);
									Debug.WriteLine("WM_COPYDATA result: {0:x8}", result);
									if (result == (IntPtr)1)
									{
										if (activate)
											NativeMethods.SetForegroundWindow(hWnd);
										success = true;
										return false; // stop enumeration
									}
								}
							}
							return true; // continue enumeration
						}, IntPtr.Zero);

					// Wait some time before next attempt
					await Task.Delay(500);
					remainingAttempts--;
				} while (!success && (remainingAttempts > 0));
			});
		}

		unsafe static IntPtr Send(IntPtr hWnd, string message)
		{
			const uint SMTO_NORMAL = 0;

			CopyDataStruct lParam;
			lParam.Padding = IntPtr.Zero;
			lParam.Size = message.Length * 2;
			fixed (char* buffer = message)
			{
				lParam.Buffer = (IntPtr)buffer;
				IntPtr result;
				// SendMessage with 3s timeout (e.g. when the target process is stopped in the debugger)
				if (NativeMethods.SendMessageTimeout(
					hWnd, NativeMethods.WM_COPYDATA, IntPtr.Zero, ref lParam,
					SMTO_NORMAL, 3000, out result) != IntPtr.Zero)
				{
					return result;
				}
				else
				{
					return IntPtr.Zero;
				}
			}
		}
	}
}