using NUnit.Framework;
using NUnit.Framework.Internal;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using Vanara.Extensions;
using static Vanara.PInvoke.DbgHelp;
using static Vanara.PInvoke.ImageHlp;

namespace Vanara.PInvoke.Tests
{
	[TestFixture]
	public class DbgHelpTests
	{
		const string imgName = "imagehlp.dll";
		const string testAppName = "TestDbgApp";
		static readonly string testAppPath = TestCaseSources.TempDirWhack + testAppName + ".exe";
		private Process testApp;
		private ProcessSymbolHandler hProc;

		[OneTimeSetUp]
		public void _Setup()
		{
			testApp = Process.Start(new ProcessStartInfo(testAppPath) { WindowStyle = ProcessWindowStyle.Minimized });
			hProc = new ProcessSymbolHandler(testApp.Handle);
		}

		[OneTimeTearDown]
		public void _TearDown()
		{
			hProc.Dispose();
			testApp.Kill();
		}

		[Test]
		public void EnumDirTreeTest()
		{
			var output = EnumDirTree(HPROCESS.NULL, Environment.GetFolderPath(Environment.SpecialFolder.Windows), imgName);
			Assert.That(output, Is.Not.Empty);
			TestContext.WriteLine($"Count: {output.Count}");
			output.WriteValues();
		}

		[Test]
		public void EnumerateLoadedModulesTest()
		{
			var output = EnumerateLoadedModules(Process.GetCurrentProcess().Handle);
			Assert.That(output, Is.Not.Empty);
			TestContext.WriteLine($"Count: {output.Count}");
			output.WriteValues();
		}

		[Test]
		public void EnumerateLoadedModulesExTest()
		{
			var output = EnumerateLoadedModulesEx(Process.GetCurrentProcess().Handle);
			Assert.That(output, Is.Not.Empty);
			TestContext.WriteLine($"Count: {output.Count}");
			output.WriteValues();
		}

		[Test]
		public void FindExecutableImageExTest()
		{
			Assert.That(FindExecutableImageEx(imgName, new[] { Environment.GetFolderPath(Environment.SpecialFolder.System), TestCaseSources.TempDir }), ResultIs.Not.Value(null));
		}

		[Test]
		public void ImagehlpApiVersionTest()
		{
			Assert.That(ImagehlpApiVersion().MajorVersion, Is.GreaterThan((ushort)0));
		}

		[Test]
		public void GetImageConfigInformationTest()
		{
			Assert.That(MapAndLoad(imgName, null, out var LoadedImage, true, true), ResultIs.Successful);
			try
			{
				var data = ImageDirectoryEntryToData(LoadedImage.MappedAddress, false, IMAGE_DIRECTORY_ENTRY.IMAGE_DIRECTORY_ENTRY_EXPORT, out var cDirSize); // (_IMAGE_EXPORT_DIRECTORY*)
				Assert.That(data, ResultIs.ValidHandle);
				var ImageExportDirectory = data.ToStructure<IMAGE_EXPORT_DIRECTORY>(cDirSize);
				ImageExportDirectory.WriteValues();
				var addr = ImageRvaToVa(LoadedImage.FileHeader, LoadedImage.MappedAddress, ImageExportDirectory.AddressOfNames, out _); // (uint*)
				Assert.That(addr, ResultIs.ValidHandle);
				foreach (var rva in addr.ToArray<uint>((int)ImageExportDirectory.NumberOfNames))
				{
					var sName = ImageRvaToVa(LoadedImage.FileHeader, LoadedImage.MappedAddress, rva, out _);
					TestContext.WriteLine(Marshal.PtrToStringAnsi(sName) ?? "(null)");
				}
			}
			finally
			{
				UnMapAndLoad(ref LoadedImage);
			}
		}

		[Test]
		public void SymEnumerateModulesTest()
		{
			var output = SymEnumerateModules(hProc, true);
			TestContext.WriteLine($"Count: {output.Count}");
			output.WriteValues();
		}

		[Test]
		public void SymEnumLinesTest()
		{
			var (_, BaseOfDll) = SymEnumerateModules(hProc, true).First();
			var output = SymEnumLines(hProc, unchecked((ulong)BaseOfDll.ToInt64()));
			TestContext.WriteLine($"Count: {output.Count}");
			output.WriteValues();
		}

		[Test]
		public void SymEnumProcessesTest()
		{
			Assert.That(SymEnumProcesses(), Is.Not.Empty);
		}

		[Test]
		public unsafe void SymGetOmapsTest()
		{
			var (_, BaseOfDll) = SymEnumerateModules(hProc, true).First();
			Assert.That(SymGetOmaps(hProc, unchecked((ulong)BaseOfDll.ToInt64()), out var to, out var cto, out var from, out var cfrom), ResultIs.Successful);
		}

		[Test]
		public void SymGetSymFromNameTest()
		{
			using var sym = new SafeIMAGEHLP_SYMBOL();
			Assert.That(SymGetSymFromName(hProc, "strcat", sym), ResultIs.Successful);
			sym.Value.WriteValues();
		}

		[Test]
		public void SymGetSymFromName64Test()
		{
			ulong addr = 0;
			using (var sym = new SafeIMAGEHLP_SYMBOL64())
			{
				Assert.That(SymGetSymFromName64(hProc, "strcat", sym), ResultIs.Successful);
				sym.Value.WriteValues();
				Assert.That(sym.Value.Name, Is.EqualTo("strcat"));
				addr = sym.Value.Address;
			}

			using (var sym = new SafeIMAGEHLP_SYMBOL64())
			{
				Assert.That(SymGetSymFromAddr64(hProc, addr, out var displ, sym), ResultIs.Successful);
				sym.Value.WriteValues();
				Assert.That(sym.Value.Name, Is.EqualTo("strcat"));
			}
		}

		[Test]
		public void MimicDllExp()
		{
			Assert.That(MapAndLoad(imgName, null, out var LoadedImage, true, true), ResultIs.Successful);
			try
			{
				var data = ImageDirectoryEntryToData(LoadedImage.MappedAddress, false, IMAGE_DIRECTORY_ENTRY.IMAGE_DIRECTORY_ENTRY_EXPORT, out var cDirSize); // (_IMAGE_EXPORT_DIRECTORY*)
				Assert.That(data, ResultIs.ValidHandle);
				var ImageExportDirectory = data.ToStructure<IMAGE_EXPORT_DIRECTORY>(cDirSize);
				ImageExportDirectory.WriteValues();

				var addr = ImageRvaToVa(LoadedImage.FileHeader, LoadedImage.MappedAddress, ImageExportDirectory.AddressOfNames, out _); // (uint*)
				Assert.That(addr, ResultIs.ValidHandle);
				var rnameaddrs = addr.ToArray<uint>((int)ImageExportDirectory.NumberOfNames);

				addr = ImageRvaToVa(LoadedImage.FileHeader, LoadedImage.MappedAddress, ImageExportDirectory.AddressOfNameOrdinals, out _); // (uint*)
				Assert.That(addr, ResultIs.ValidHandle);
				var rordaddrs = addr.ToArray<uint>((int)ImageExportDirectory.NumberOfNames);

				for (int i = 0; i < rnameaddrs.Length; i++)
				{
					var sName = Marshal.PtrToStringAnsi(ImageRvaToVa(LoadedImage.FileHeader, LoadedImage.MappedAddress, rnameaddrs[i], out _));
					var ord = ImageRvaToVa(LoadedImage.FileHeader, LoadedImage.MappedAddress, rordaddrs[i], out _).ToNullableStructure<uint>();
					TestContext.WriteLine($"{sName ?? (null)}\t0x{ord ?? 0}");
				}
			}
			finally
			{
				UnMapAndLoad(ref LoadedImage);
			}
		}
	}
}