using NUnit.Framework;
using System;
using System.Diagnostics;
using Vanara.Extensions;
using static Vanara.PInvoke.Kernel32;

namespace Vanara.PInvoke.Tests
{
	[TestFixture]
	public class ThreadPoolApiSetTests
	{
		[Test]
		public void QuerySetThreadpoolStackInformationTest()
		{
			using (var pool = CreateThreadpool())
			{
				Assert.That(pool, ResultIs.ValidHandle);
				Assert.That(QueryThreadpoolStackInformation(pool, out var si), ResultIs.Successful);
				Assert.That(si.StackReserve.Value, Is.Not.Zero);
				Assert.That(SetThreadpoolStackInformation(pool, si), ResultIs.Successful);
			}
		}

		[Test]
		public void ThreadpoolIoWorkTimerTest()
		{
			InitializeThreadpoolEnvironment(out var CallBackEnviron);

			// Create a custom, dedicated thread pool.
			using (var pool = CreateThreadpool())
			{
				Assert.That(pool, ResultIs.ValidHandle);

				// The thread pool is made persistent simply by setting both the minimum and maximum threads to 1.
				SetThreadpoolThreadMaximum(pool, 1);
				Assert.That(SetThreadpoolThreadMinimum(pool, 1), ResultIs.Successful);

				// Create a cleanup group for this thread pool.
				using (var cleanupgroup = CreateThreadpoolCleanupGroup())
				{
					Assert.That(cleanupgroup, ResultIs.ValidHandle);
					cleanupgroup.AutoCloseMembers = true;

					// Associate the callback environment with our thread pool.
					CallBackEnviron.SetThreadpoolCallbackPool(pool);

					// Associate the cleanup group with our thread pool. Objects created with the same callback environment as the cleanup
					// group become members of the cleanup group.
					CallBackEnviron.SetThreadpoolCallbackCleanupGroup(cleanupgroup, null);

					// Create work with the callback environment.
					var work = cleanupgroup.CreateWork(MyWorkCallback, default, CallBackEnviron);
					Assert.That(work, ResultIs.ValidHandle);

					// Submit the work to the pool. Because this was a pre-allocated work item (using CreateThreadpoolWork), it is guaranteed
					// to execute.
					SubmitThreadpoolWork(work);

					// Create a timer with the same callback environment.
					var timer = cleanupgroup.CreateTimer(MyTimerCallback, default, CallBackEnviron);
					Assert.That(timer, ResultIs.ValidHandle);

					// Set the timer to fire in one second.
					var FileDueTime = TimeSpan.FromSeconds(-1).ToFileTimeStruct();
					Assert.That(SetThreadpoolTimerEx(timer, FileDueTime, 0, 0), Is.False);
					Assert.That(IsThreadpoolTimerSet(timer), Is.True);

					using (var hFile = CreateFile(TestCaseSources.SmallFile, FileAccess.FILE_GENERIC_READ, System.IO.FileShare.Read, null, System.IO.FileMode.Open, FileFlagsAndAttributes.FILE_FLAG_OVERLAPPED))
					using (var io = CreateThreadpoolIo(hFile, MyIoCallback, default, CallBackEnviron))
					{
						Assert.That(io, ResultIs.ValidHandle);
						StartThreadpoolIo(io);
						WaitForThreadpoolIoCallbacks(io, true);
					}

					// Delay for the timer to be fired
					Sleep(1500);
				}
			}

			void MyIoCallback(PTP_CALLBACK_INSTANCE Instance, IntPtr Context, IntPtr Overlapped, uint IoResult, UIntPtr NumberOfBytesTransferred, PTP_IO Io)
			{
				Debug.Write("MyIoCallback: I/O has fired.\n");
				CancelThreadpoolIo(Io);
			}

			// Thread pool timer callback function template
			void MyTimerCallback(PTP_CALLBACK_INSTANCE a, IntPtr b, PTP_TIMER c) => Debug.Write("MyTimerCallback: timer has fired.\n");

			// This is the thread pool work callback function.
			void MyWorkCallback(PTP_CALLBACK_INSTANCE a, IntPtr b, PTP_WORK c)
			{
				CallbackMayRunLong(a);
				Debug.Write("MyWorkCallback: Task performed.\n");
			}
		}

		[Test]
		public void ThreadpoolWaitTest()
		{
			SafeEventHandle retEvent;
			// Create an auto-reset event.
			using (var hEvent = CreateEvent(null, false, false, null))
			using (retEvent = CreateEvent(null, false, false, null))
			{
				Assert.That(hEvent, ResultIs.ValidHandle);

				using (var Wait = CreateThreadpoolWait(MyWaitCallback))
				{
					Assert.That(Wait, ResultIs.ValidHandle);

					// Need to re-register the event with the wait object each time before signaling the event to trigger the wait callback.
					for (var i = 0; i < 5; i++)
					{
						SetThreadpoolWait(Wait, hEvent);

						SetEvent(hEvent);

						// Delay for the waiter thread to act if necessary.
						Sleep(500);

						// Block here until the callback function is done executing.
						WaitForThreadpoolWaitCallbacks(Wait, false);

						// Ensure that callback return event is signaled.
						WaitForSingleObject(retEvent, INFINITE);
					}

					SetThreadpoolWait(Wait);
				}
			}

			// Thread pool wait callback function template
			void MyWaitCallback(PTP_CALLBACK_INSTANCE a, IntPtr b, PTP_WAIT c, uint d)
			{
				Debug.Write("MyWaitCallback: wait is over.\n");
				Sleep(200);
				SetEventWhenCallbackReturns(a, retEvent);
			}
		}

		[Test]
		public void TrySubmitThreadpoolCallbackTest()
		{
			Assert.That(TrySubmitThreadpoolCallback((i, c) =>
			{
				Debug.WriteLine("SimpleCallback from TrySubmitThreadpoolCallback");
				DisassociateCurrentThreadFromCallback(i);
			}), Is.True); ;
		}
	}
}