using NUnit.Framework;
using NUnit.Framework.Internal;
using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Vanara.Extensions;
using Vanara.InteropServices;
using static Vanara.PInvoke.WinBio;
using TestContext = System.Console;

namespace Vanara.PInvoke.Tests
{
	[TestFixture]
	[Apartment(System.Threading.ApartmentState.MTA)]
	public class WinBioTests
	{
		const uint compMsg = 0x8EEF;

		//[MTAThread]
		//public static void Main()
		//{
		//	bool setup = false;
		//	WinBioTests test = new();
		//	try
		//	{
		//		test._Setup();

		//		test.WinBioAsyncEnumBiometricUnitsCallbackTest();

		//		setup = true;
		//	}
		//	catch (Exception ex)
		//	{
		//		TestContext.WriteLine(ex.ToString());
		//	}
		//	finally
		//	{
		//		if (setup)
		//			test._TearDown();
		//	}
		//}

		WINBIO_SESSION_HANDLE sessionHandle;

		[OneTimeSetUp]
		public void _Setup()
		{
			WinBioOpenSession(WINBIO_BIOMETRIC_TYPE.WINBIO_TYPE_FINGERPRINT, // Service provider
				WINBIO_POOL_TYPE.WINBIO_POOL_SYSTEM, // Pool type
				WINBIO_SESSION_FLAGS.WINBIO_FLAG_RAW | WINBIO_SESSION_FLAGS.WINBIO_FLAG_DEFAULT, // Access: Capture raw data
				default, // Array of biometric unit IDs
				0, // Count of biometric unit IDs
				default, // Default database
				out sessionHandle).ThrowIfFailed(); // [out] Session handle
		}

		[OneTimeTearDown]
		public void _TearDown()
		{
			WinBioCloseSession(sessionHandle);
		}

		[Test]
		public void StructTest() => TestHelper.GetNestedStructSizes(typeof(Vanara.PInvoke.WinBio)).WriteValues();

		//[Test]
		public void WinBioAsyncEnumBiometricUnitsCallbackTest()
		{
			ManualResetEvent evt = new(false);
			var cbPtr = new PWINBIO_ASYNC_COMPLETION_CALLBACK(Callback);
			Assert.That(WinBioAsyncOpenFramework(WINBIO_ASYNC_NOTIFICATION_METHOD.WINBIO_ASYNC_NOTIFY_CALLBACK, default, default,
				Marshal.GetFunctionPointerForDelegate(cbPtr), default, false, out var hFramework), ResultIs.Successful);
			try
			{
				Assert.That(WinBioAsyncEnumBiometricUnits(hFramework, WINBIO_BIOMETRIC_TYPE.WINBIO_TYPE_FINGERPRINT), ResultIs.Successful);
				evt.WaitOne(5000);
			}
			finally
			{
				WinBioCloseFramework(hFramework);
			}

			void Callback(IntPtr pRes)
			{
				try
				{
					WINBIO_ASYNC_RESULT war = pRes.ToStructure<WINBIO_ASYNC_RESULT>();
					if (war.Operation == WINBIO_OPERATION_TYPE.WINBIO_OPERATION_ENUM_BIOMETRIC_UNITS)
					{
						foreach (var s in war.Parameters.EnumBiometricUnits.UnitSchemaArray)
							TestContext.WriteLine($"{s.UnitId} : {s.Model} : {s.Description}");
						evt.Set();
					}
					if (war.Operation == WINBIO_OPERATION_TYPE.WINBIO_OPERATION_CLOSE_FRAMEWORK)
						WinBioFree(pRes);
				}
				catch { }
			}
		}

		[Test]
		public void WinBioAsyncEnumBiometricUnitsTest()
		{
			ManualResetEvent evt = new(false);
			using var wnd = new MsgWnd(Callback);
			Assert.That(WinBioAsyncOpenFramework(WINBIO_ASYNC_NOTIFICATION_METHOD.WINBIO_ASYNC_NOTIFY_MESSAGE, wnd.MessageWindowHandle, compMsg, default, default, false, out var hFramework), ResultIs.Successful);
			try
			{
				Assert.That(WinBioAsyncEnumBiometricUnits(hFramework, WINBIO_BIOMETRIC_TYPE.WINBIO_TYPE_FINGERPRINT), ResultIs.Successful);
				Assert.IsTrue(evt.WaitOne(5000));
			}
			finally
			{
				WinBioCloseFramework(hFramework);
			}

			IntPtr Callback(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam)
			{
				try
				{
					WINBIO_ASYNC_RESULT war = lParam.ToStructure<WINBIO_ASYNC_RESULT>();
					switch (war.Operation)
					{
						case WINBIO_OPERATION_TYPE.WINBIO_OPERATION_ENUM_BIOMETRIC_UNITS:
							foreach (var s in war.Parameters.EnumBiometricUnits.UnitSchemaArray)
								TestContext.WriteLine($"{s.UnitId} : {s.Model} : {s.Description}");
							evt.Set();
							break;

						case WINBIO_OPERATION_TYPE.WINBIO_OPERATION_CLOSE_FRAMEWORK:
							WinBioFree(lParam);
							break;
					}
				}
				catch { }
				return default;
			}
		}

		[Test]
		public void WinBioAsyncEnumDatabasesTest()
		{
			ManualResetEvent evt = new(false);
			using var wnd = new MsgWnd(Callback);
			Assert.That(WinBioAsyncOpenFramework(WINBIO_ASYNC_NOTIFICATION_METHOD.WINBIO_ASYNC_NOTIFY_MESSAGE, wnd.MessageWindowHandle, compMsg, default, default, false, out var hFramework), ResultIs.Successful);
			try
			{
				Assert.That(WinBioAsyncEnumDatabases(hFramework, WINBIO_BIOMETRIC_TYPE.WINBIO_TYPE_FINGERPRINT), ResultIs.Successful);
				Assert.IsTrue(evt.WaitOne(5000));
			}
			finally
			{
				WinBioCloseFramework(hFramework);
			}

			IntPtr Callback(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam)
			{
				try
				{
					WINBIO_ASYNC_RESULT war = lParam.ToStructure<WINBIO_ASYNC_RESULT>();
					switch (war.Operation)
					{
						case WINBIO_OPERATION_TYPE.WINBIO_OPERATION_ENUM_DATABASES:
							foreach (var s in war.Parameters.EnumDatabases.StorageSchemaArray)
								TestContext.WriteLine($"{s.FilePath} : {s.Attributes}");
							evt.Set();
							break;

						case WINBIO_OPERATION_TYPE.WINBIO_OPERATION_CLOSE_FRAMEWORK:
							WinBioFree(lParam);
							break;
					}
				}
				catch { }
				return default;
			}
		}

		[Test]
		public void WinBioAsyncEnumServiceProvidersTest()
		{
			ManualResetEvent evt = new(false);
			using var wnd = new MsgWnd(Callback);
			Assert.That(WinBioAsyncOpenFramework(WINBIO_ASYNC_NOTIFICATION_METHOD.WINBIO_ASYNC_NOTIFY_MESSAGE, wnd.MessageWindowHandle, compMsg, default, default, false, out var hFramework), ResultIs.Successful);
			try
			{
				Assert.That(WinBioAsyncEnumServiceProviders(hFramework, WINBIO_BIOMETRIC_TYPE.WINBIO_TYPE_FINGERPRINT), ResultIs.Successful);
				Assert.IsTrue(evt.WaitOne(5000));
			}
			finally
			{
				WinBioCloseFramework(hFramework);
			}

			IntPtr Callback(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam)
			{
				try
				{
					WINBIO_ASYNC_RESULT war = lParam.ToStructure<WINBIO_ASYNC_RESULT>();
					switch (war.Operation)
					{
						case WINBIO_OPERATION_TYPE.WINBIO_OPERATION_ENUM_SERVICE_PROVIDERS:
							foreach (var s in war.Parameters.EnumServiceProviders.BspSchemaArray)
								TestContext.WriteLine($"{s.Vendor} : {s.Version} : {s.Description}");
							evt.Set();
							break;

						case WINBIO_OPERATION_TYPE.WINBIO_OPERATION_CLOSE_FRAMEWORK:
							WinBioFree(lParam);
							break;
					}
				}
				catch { }
				return default;
			}
		}

		//[Test]
		public void WinBioAsyncMonitorFrameworkChangesTest()
		{
			ManualResetEvent evt = new(false);
			using var wnd = new MsgWnd(Callback);
			Assert.That(WinBioAsyncOpenFramework(WINBIO_ASYNC_NOTIFICATION_METHOD.WINBIO_ASYNC_NOTIFY_MESSAGE, wnd.MessageWindowHandle, compMsg, default, default, false, out var hFramework), ResultIs.Successful);
			try
			{
				Assert.That(WinBioAsyncMonitorFrameworkChanges(hFramework, WINBIO_FRAMEWORK_CHANGE_TYPE.WINBIO_FRAMEWORK_CHANGE_UNIT), ResultIs.Successful);
				Assert.IsTrue(evt.WaitOne(5000));
			}
			finally
			{
				WinBioCloseFramework(hFramework);
			}

			IntPtr Callback(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam)
			{
				try
				{
					WINBIO_ASYNC_RESULT war = lParam.ToStructure<WINBIO_ASYNC_RESULT>();
					switch (war.Operation)
					{
						case WINBIO_OPERATION_TYPE.WINBIO_OPERATION_UNIT_ARRIVAL:
							TestContext.WriteLine($"Arrived");
							evt.Set();
							break;

						case WINBIO_OPERATION_TYPE.WINBIO_OPERATION_UNIT_REMOVAL:
							TestContext.WriteLine($"Removed");
							evt.Set();
							break;

						case WINBIO_OPERATION_TYPE.WINBIO_OPERATION_CLOSE_FRAMEWORK:
							WinBioFree(lParam);
							break;
					}
				}
				catch { }
				return default;
			}
		}

		//[Test]
		public void WinBioAsyncOpenFrameworkCallbackTest()
		{
			var cbPtr = new PWINBIO_ASYNC_COMPLETION_CALLBACK(Callback);
			Assert.That(WinBioAsyncOpenFramework(WINBIO_ASYNC_NOTIFICATION_METHOD.WINBIO_ASYNC_NOTIFY_CALLBACK, default, default,
				Marshal.GetFunctionPointerForDelegate(cbPtr), default, true, out var hFramework), ResultIs.Successful);
			Assert.That((uint)hFramework, Is.Zero);

			static void Callback(IntPtr AsyncResult)
			{
			}
		}

		[Test]
		public void WinBioAsyncOpenFrameworkHwnd2Test()
		{
			using var wnd = new MsgWnd();
			Assert.That(WinBioAsyncOpenFramework(WINBIO_ASYNC_NOTIFICATION_METHOD.WINBIO_ASYNC_NOTIFY_MESSAGE, wnd.MessageWindowHandle, compMsg, default, default, false, out var hFramework), ResultIs.Successful);
			try
			{
				Assert.That((uint)hFramework, Is.Not.Zero);
			}
			finally
			{
				WinBioCloseFramework(hFramework);
			}
		}

		[Test]
		public void WinBioAsyncOpenFrameworkHwndTest()
		{
			using var wnd = new MsgWnd();
			Assert.That(WinBioAsyncOpenFramework(WINBIO_ASYNC_NOTIFICATION_METHOD.WINBIO_ASYNC_NOTIFY_MESSAGE, wnd.MessageWindowHandle, compMsg, default, default, true, out var hFramework), ResultIs.Successful);
			Assert.That((uint)hFramework, Is.Zero);
		}

		//[Test]
		public void WinBioAsyncOpenSessionTest()
		{
			Assert.That(WinBioAsyncOpenSession(WINBIO_BIOMETRIC_TYPE.WINBIO_TYPE_FINGERPRINT,
				WINBIO_POOL_TYPE.WINBIO_POOL_SYSTEM,
				WINBIO_SESSION_FLAGS.WINBIO_FLAG_RAW,
				NotificationMethod: WINBIO_ASYNC_NOTIFICATION_METHOD.WINBIO_ASYNC_NOTIFY_CALLBACK,
				DatabaseId: WINBIO_DB_DEFAULT, CallbackRoutine: Callback, AsynchronousOpen: true,
				SessionHandle: out var hSess), ResultIs.Successful);
			try
			{

			}
			finally
			{
				WinBioCloseSession(hSess);
			}

			static void Callback(IntPtr AsyncResult)
			{
				using var str = new SafeWinBioStruct<WINBIO_ASYNC_RESULT>(AsyncResult, true);
				str.Value.WriteValues();
			}
		}

		//[Test]
		public void WinBioCaptureSampleTest()
		{
			// Capture a biometric sample.
			var hr = WinBioCaptureSample(sessionHandle,
				WINBIO_BIR_PURPOSE.WINBIO_NO_PURPOSE_AVAILABLE,
				WINBIO_BIR_DATA_FLAGS.WINBIO_DATA_FLAG_RAW,
				out var unitId,
				out var sample,
				out var sampleSize,
				out var rejectDetail);
			Assert.That(hr, ResultIs.Successful);
			Assert.IsFalse(sample.IsInvalid);

			var value = sample.ToStructure<WINBIO_BIR>(sampleSize);
			WINBIO_BIR_HEADER BirHeader = sample.ToStructure<WINBIO_BIR_HEADER>(sampleSize, value.HeaderBlock.Offset);
			BirHeader.WriteValues();
			WINBIO_BDB_ANSI_381_HEADER AnsiBdbHeader = sample.ToStructure<WINBIO_BDB_ANSI_381_HEADER>(sampleSize, value.StandardDataBlock.Offset);
			AnsiBdbHeader.WriteValues();
			WINBIO_BDB_ANSI_381_RECORD AnsiBdbRecord = sample.ToStructure<WINBIO_BDB_ANSI_381_RECORD>(sampleSize, value.StandardDataBlock.Offset + (uint)Marshal.SizeOf(typeof(WINBIO_BDB_ANSI_381_HEADER)));
			AnsiBdbRecord.WriteValues();

			//int width = AnsiBdbRecord.HorizontalLineLength; // Width of image in pixels
			//int height = AnsiBdbRecord.VerticalLineLength; // Height of image in pixels

			//IntPtr firstPixel = ((IntPtr)sample).Offset(sample.Value.StandardDataBlock.Offset + Marshal.SizeOf(typeof(WINBIO_BDB_ANSI_381_HEADER)) + Marshal.SizeOf(typeof(WINBIO_BDB_ANSI_381_RECORD)));

			//Bitmap bmp = new Bitmap(width, height, 3 * width, System.Drawing.Imaging.PixelFormat.Format24bppRgb, firstPixel);
			//using var tmp = new TempFile(null);
			//bmp.Save(tmp.FullName);
		}

		[Test]
		public void WinBioEnumBiometricUnitsTest()
		{
			Assert.That(WinBioEnumBiometricUnits(WINBIO_BIOMETRIC_TYPE.WINBIO_TYPE_FINGERPRINT, out var schemas), ResultIs.Successful);
			schemas.WriteValues();
		}

		[Test]
		public void WinBioEnumDatabasesTest()
		{
			Assert.That(WinBioEnumDatabases(WINBIO_BIOMETRIC_TYPE.WINBIO_TYPE_FINGERPRINT, out var schemas), ResultIs.Successful);
			schemas.WriteValues();
		}

		[Test]
		public void WinBioEnumEnrollmentsTest()
		{
			Assert.That(WinBioIdentify(sessionHandle, out var unitId, out var id, out _, out _), ResultIs.Successful);
			Assert.That(WinBioEnumEnrollments(sessionHandle, unitId, id, out var subs), ResultIs.Successful);
			subs.WriteValues();
		}

		[Test]
		public void WinBioEnumServiceProvidersTest()
		{
			Assert.That(WinBioEnumServiceProviders(WINBIO_BIOMETRIC_TYPE.WINBIO_TYPE_FINGERPRINT, out var schemas), ResultIs.Successful);
			schemas.WriteValues();
		}

		[Test]
		public void WinBioGetLogonSettingTest()
		{
			WinBioGetLogonSetting(out var val, out var src);
			TestContext.Write($"{val}:{src}");
		}

		[Test]
		public void WinBioGetPropertyTest()
		{
			Assert.That(WinBioLocateSensor(sessionHandle, out var unitId), ResultIs.Successful);

			Assert.That(WinBioGetProperty(sessionHandle, WINBIO_PROPERTY_TYPE.WINBIO_PROPERTY_TYPE_UNIT, WINBIO_PROPERTY_ID.WINBIO_PROPERTY_EXTENDED_SENSOR_INFO,
				unitId, default, WINBIO_BIOMETRIC_SUBTYPE.WINBIO_SUBTYPE_NO_INFORMATION, out var propBuf, out var propSize), ResultIs.Successful);

			Assert.That((long)propSize, Is.GreaterThan(0L));
		}

		[Test]
		public void WinBioIdentifyTest()
		{
			Assert.That(WinBioIdentify(sessionHandle, out var unitId, out var id, out var sub, out var det), ResultIs.Successful);
			TestContext.Write($"Session={sessionHandle}, UnitId={unitId}, Id={id.Type}, Sub={sub}, RejDet={det}");
		}

		[Test]
		public void WinBioIdentifyWithCallbackTest()
		{
			Assert.That(WinBioIdentifyWithCallback(sessionHandle, Callback), ResultIs.Successful);

			void Callback(IntPtr IdentifyCallbackContext, HRESULT OperationStatus, uint UnitId, in WINBIO_IDENTITY Identity, WINBIO_BIOMETRIC_SUBTYPE SubFactor, WINBIO_REJECT_DETAIL RejectDetail)
			{
				Assert.That(UnitId, Is.Not.Zero);
				TestContext.WriteLine($"{UnitId} : {SubFactor} : {RejectDetail}");
				Identity.WriteValues();
			}
		}

		private class MsgWnd : SystemEventHandler
		{
			private readonly User32.WindowProc callback;

			public MsgWnd(User32.WindowProc callback = null) => this.callback = callback;

			protected override bool MessageFilter(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam, out IntPtr lReturn)
			{
				if (msg == compMsg)
				{
					lReturn = callback?.Invoke(hwnd, msg, wParam, lParam) ?? default;
					return true;
				}
				lReturn = default;
				return false;
			}
		}
	}
}