//
// Test/simulation program for the extended device queue
//

#include "raidport.h"
#include "exqueue.h"

#define	SECONDS (1000)

typedef struct _TQ_ITEM {
	ULONG Thread;
	ULONG64 Data;
	ULONG64 Sequence;
	KDEVICE_QUEUE_ENTRY DeviceEntry;
	LIST_ENTRY DpcEntry;
} TQ_ITEM, *PTQ_ITEM;


typedef struct _DPC_QUEUE {
	HANDLE Available;
	HANDLE Mutex;
	LIST_ENTRY Queue;
	ULONG Count;
} DPC_QUEUE, *PDPC_QUEUE;


BOOL Verbose = FALSE;
BOOL Cerbose = TRUE;
DPC_QUEUE DpcQueue;
EXTENDED_DEVICE_QUEUE DeviceQueue;
BOOLEAN Pause = FALSE;
BOOLEAN PauseProducer = FALSE;
ULONG64 GlobalSequence = 0;
DWORD Owner = 0;
BOOL Available = FALSE;


VOID
GetNext(
	OUT PULONG64 Sequence,
	OUT PULONG Data
	)
{
	*Sequence = ++GlobalSequence;
	*Data = (ULONG)rand();
}

#pragma warning (disable:4715)

BOOL
WINAPI
UpdateThread(
	PVOID Unused
	)
{
	for (;;) {

		while (Pause) {
			Sleep (500);
		}
		
		printf ("Seq: %I64d, dev %d bypass %d dpc %d                       \r",
				 GlobalSequence,
				 DeviceQueue.DeviceRequests,
				 DeviceQueue.ByPassRequests,
				 DpcQueue.Count);
		Sleep (2000);
	}

	return FALSE;
}

VOID
CreateDpcQueue(
	PDPC_QUEUE DpcQueue
	)
{
	DpcQueue->Available = CreateEvent (NULL, FALSE, FALSE, NULL);
	DpcQueue->Mutex = CreateMutex (NULL, FALSE, NULL);
	InitializeListHead (&DpcQueue->Queue);
	DpcQueue->Count = 0;
}


VOID
InsertDpcItem(
	IN PDPC_QUEUE DpcQueue,
	IN PLIST_ENTRY Entry
	)
{
	WaitForSingleObject (DpcQueue->Mutex, INFINITE);
	InsertTailList (&DpcQueue->Queue, Entry);
	DpcQueue->Count++;
	Owner = GetCurrentThreadId ();
	Available = TRUE;
	SetEvent (DpcQueue->Available);
	ReleaseMutex (DpcQueue->Mutex);
}

PLIST_ENTRY
RemoveDpcItem(
	IN PDPC_QUEUE DpcQueue
	)
{
	PLIST_ENTRY Entry;
	HANDLE Tuple[2];

	Tuple[0] = DpcQueue->Mutex;
	Tuple[1] = DpcQueue->Available;

	WaitForMultipleObjects (2, Tuple, TRUE, INFINITE);
	Available = FALSE;
	Owner = GetCurrentThreadId ();
	ASSERT (!IsListEmpty (&DpcQueue->Queue));
	Entry = RemoveHeadList (&DpcQueue->Queue);
	DpcQueue->Count--;
	if (!IsListEmpty (&DpcQueue->Queue)) {
		Available = TRUE;
		SetEvent (DpcQueue->Available);
	}
	ReleaseMutex (DpcQueue->Mutex);

	return Entry;
}


BOOL
WINAPI
ProducerThread(
	PVOID Unused
	)
{
	BOOLEAN Inserted;
	PTQ_ITEM Item;
	ULONG Data;
	ULONG64 Sequence;

	srand (GetCurrentThreadId());
	
	for (;;) {

		Sleep (10);

		while (Pause) {
			Sleep (100);
		}

		while (PauseProducer) {
			Sleep (100);
		}
			
		Item = malloc (sizeof (TQ_ITEM));
		ZeroMemory (Item, sizeof (*Item));
		
		GetNext (&Sequence, &Data);
		Item->Thread = GetCurrentThreadId ();
		Item->Sequence = Sequence;
		Item->Data = Data;

		Inserted = RaidInsertExDeviceQueue (&DeviceQueue,
										    &Item->DeviceEntry,
										    FALSE,
											(ULONG)Item->Data);

		if (!Inserted) {

			if (Verbose) {
				printf ("started %x.%I64d\n", Item->Thread, Item->Data);
			}

			InsertDpcItem (&DpcQueue, &Item->DpcEntry);

		} else {
			if (Verbose) {
				printf ("queued %x.%I64d\n", Item->Thread, Item->Data);
			}
		}
	}

	return FALSE;
}

BOOL
WINAPI
ProducerThread100(
	PVOID Unused
	)
{
	BOOLEAN Inserted;
	PTQ_ITEM Item;
	ULONG Data;
	ULONG64 Sequence;

	srand (GetCurrentThreadId());
	
	for (;;) {

		Sleep (0);

		while (Pause) {
			Sleep (100);
		}

		while (PauseProducer) {
			Sleep (100);
		}
			
		Item = malloc (sizeof (TQ_ITEM));
		ZeroMemory (Item, sizeof (*Item));
		
		GetNext (&Sequence, &Data);
		Item->Thread = GetCurrentThreadId ();
		Item->Sequence = Sequence;
		Item->Data = 100;

		Inserted = RaidInsertExDeviceQueue (&DeviceQueue,
										    &Item->DeviceEntry,
										    FALSE,
											(ULONG)Item->Data);

		if (!Inserted) {

			if (Verbose) {
				printf ("started %x.%I64d\n", Item->Thread, Item->Data);
			}

			InsertDpcItem (&DpcQueue, &Item->DpcEntry);

		} else {
			if (Verbose) {
				printf ("queued %x.%I64d\n", Item->Thread, Item->Data);
			}
		}
	}

	return FALSE;
}

BOOL
WINAPI
ErrorProducerThread(
	PVOID Unused
	)
{
	BOOLEAN Inserted;
	PTQ_ITEM Item;
	ULONG64 Sequence;
	ULONG Data;

	for (;;) {

		Sleep (500);
		
		while (Pause) {
			Sleep (10);
		}
			
		Item = malloc (sizeof (TQ_ITEM));
		ZeroMemory (Item, sizeof (*Item));

		GetNext (&Sequence, &Data);
		Item->Thread = GetCurrentThreadId ();
		Item->Sequence = Sequence;
		Item->Data = Data;
		
		Inserted = RaidInsertExDeviceQueue (&DeviceQueue,
										    &Item->DeviceEntry,
										    TRUE,
											(ULONG)Item->Data);

		if (!Inserted) {

			if (Verbose) {
				printf ("started %x.%I64d\n", Item->Thread, Item->Data);
			}

			InsertDpcItem (&DpcQueue, &Item->DpcEntry);

		} else {
			if (Verbose) {
				printf ("queued %x.%I64d\n", Item->Thread, Item->Data);
			}
		}
	}

	return FALSE;
}

VOID
DpcRoutine(
	IN PTQ_ITEM Item
	)
{
	BOOLEAN RestartQueue;
	PKDEVICE_QUEUE_ENTRY Entry;

	if (Verbose || Cerbose) {
		printf ("completed %x.%I64d\n", Item->Thread, Item->Data);
	}

	free (Item);

	Entry = RaidRemoveExDeviceQueue (&DeviceQueue, &RestartQueue);

	if (Entry) {
		Item = CONTAINING_RECORD (Entry, TQ_ITEM, DeviceEntry);
		InsertDpcItem (&DpcQueue, &Item->DpcEntry);
		if (Verbose) {
			printf ("dpc started %x.%I64d\n", Item->Thread, Item->Data);
		}

		//
		// NB: in the port driver, we actually only do this when necessary.
		// The only problem with doing this always is a speed issue, and
		// we're not measuring speed in the simulation program.
		//
		
		for (Entry = RaidNormalizeExDeviceQueue (&DeviceQueue);
			 Entry != NULL;
			 Entry = RaidNormalizeExDeviceQueue (&DeviceQueue)) {

			Item = CONTAINING_RECORD (Entry, TQ_ITEM, DeviceEntry);
			InsertDpcItem (&DpcQueue, &Item->DpcEntry);
		}
		
		
	}
}


BOOL
WINAPI
DpcThread(
	PVOID Unused
	)
{
	PLIST_ENTRY Entry;
	PTQ_ITEM Item;

#if 1
	SetThreadPriority (GetCurrentThread (),
					   THREAD_PRIORITY_ABOVE_NORMAL);
#endif

	for (;;) {

		while (Pause) {
			Sleep (10);
		}

		//
		// Wait for DPC queue to have an item in it
		//

		Entry = RemoveDpcItem (&DpcQueue);
		Item = CONTAINING_RECORD (Entry, TQ_ITEM, DpcEntry);

		DpcRoutine (Item);
	}

	return FALSE;
}

enum {
	NoControl = 0,
	FreezeQueue,
	ResumeQueue
};

volatile ULONG Control = NoControl;

BOOL
WINAPI
ControlThread(
	PVOID Unused
	)
{
	for (;;) {

		Sleep (500);

		switch (Control) {
			case FreezeQueue:
				RaidFreezeExDeviceQueue (&DeviceQueue);
				Control = NoControl;
				break;

			case ResumeQueue:
				RaidResumeExDeviceQueue (&DeviceQueue);
				Control = NoControl;
				break;

			case NoControl:
				break;

			default:
				ASSERT (FALSE);
		}
	}

	return FALSE;
}
	

BOOL
WINAPI
ControlHandler(
	DWORD Val
	)
{
	PLIST_ENTRY NextEntry;
	PTQ_ITEM Item;
	int ch;

	if (Val != CTRL_C_EVENT) {
		return FALSE;
	}

	Pause = TRUE;
	Sleep (1000);
	printf ("\n");

	do {
		printf ("tq> ");
		ch = getchar ();
		printf (" %c\n", ch);

		switch (tolower (ch)) {

			case 'd':

				printf ("Dump of DeviceQueue: %p\n", &DeviceQueue);
				printf ("    Depth %d\n", DeviceQueue.Depth);
				printf ("    Outstanding %d\n", DeviceQueue.OutstandingRequests);
				printf ("    Device %d\n", DeviceQueue.DeviceRequests);
				printf ("    ByPass %d\n", DeviceQueue.ByPassRequests);
				printf ("    Frozen %d\n", DeviceQueue.Frozen);
				break;

			case 'l':
				__try {

					ULONG Count;

					Count = 0;
					for ( NextEntry  = DeviceQueue.DeviceListHead.Flink;
						NextEntry != &DeviceQueue.DeviceListHead;
						NextEntry = NextEntry->Flink ) {
						Count++;

						Item = CONTAINING_RECORD (NextEntry, TQ_ITEM, DeviceEntry);
						printf ("    item %d.%I64d [seq=%d]\n",
								 Item->Thread,
								 Item->Data,
								 Item->Sequence);
					}

					printf ("DeviceList: %d entries\n", Count);
				}

				__except (EXCEPTION_EXECUTE_HANDLER) {
					printf ("ERROR: Inconsistent device list!\n");
				}
				break;

			case 'r':
				Control = ResumeQueue;
				break;

			case 'f':
				Control = FreezeQueue;
				break;

			case 'q':
				exit (0);
				break;

			case 'c':
			case 'g':
				break;

			case 'p':
				if (PauseProducer) {
					printf ("unpausing producers\n");
					PauseProducer = FALSE;
				} else {
					printf ("unpausing producers\n");
					PauseProducer = TRUE;
				}
				break;

			case 'v':
				if (Verbose) {
					Verbose = FALSE;
					printf ("verbose mode off\n");
				} else {
					Verbose = TRUE;
					printf ("verbose mode enabled\n");
				}

			case 'x': {
				ULONG NewDepth;
				printf ("depth> ");
				scanf ("%d", &NewDepth);
				RaidSetExDeviceQueueDepth (&DeviceQueue, NewDepth);
				printf ("stack depth set to %d\n", NewDepth);
				break;
			}

			case '?':
				printf ("    d - dump queue\n");
				printf ("    f - freeze queue\n");
				printf ("    r - resume queue\n");
				printf ("    p - toggle pause of producer threads\n");
				printf ("    u - resume producer threads\n");
				printf ("    g - go\n");
				printf ("    x - set stack depth to new value\n");
				printf ("    v - toggle verbose mode\n");
				printf ("    q - stop\n");
				break;
				
			default:
				printf ("unrecognized operation '%c'\n", ch);
		}

	} while (ch != 'c' && ch != 'g');
				

	Pause = FALSE;
	return TRUE;
}


VOID
__cdecl
main(
	)
{
	DWORD ThreadId;
	ULONG Depth;
	ULONG ProducerThreads;
	ULONG DpcThreads;
	ULONG i;
	SCHEDULING_ALGORITHM SchedulingAlgorithm;
	RAID_ADAPTER_QUEUE AdapterQueue;
	QUEUING_MODEL QueuingModel;

	//
	// Generic adapter queue.
	// 

	QueuingModel.Algorithm = BackOffFullQueue;
	QueuingModel.BackOff.HighWaterPercent = 120;
	QueuingModel.BackOff.LowWaterPercent = 40;

	RaidCreateAdapterQueue (&AdapterQueue, &QueuingModel);
	
	//
	// NB: these should all be parameters
	//
	
	Depth = 1;
	ProducerThreads = 8;
	DpcThreads = 1;
	SchedulingAlgorithm = CScanScheduling;

	printf ("DeviceQueue Test: Depth %d Producers %d DPC Threads %d\n",
			 Depth,
			 ProducerThreads,
			 DpcThreads);

	if (Verbose) {
		printf ("Verbose\n");
	}
	printf ("\n");
	
	CreateDpcQueue (&DpcQueue);
	RaidInitializeExDeviceQueue (&DeviceQueue,
								 &AdapterQueue,
								 Depth,
								 SchedulingAlgorithm);

	SetConsoleCtrlHandler (ControlHandler, TRUE);
	
	for (i = 0; i < DpcThreads; i++) {
		CreateThread (NULL,
					  0,
					  DpcThread,
					  0,
					  0,
					  &ThreadId);
	}
				  
	for (i = 0; i < ProducerThreads; i++) {

		CreateThread (NULL,
					  0,
					  ProducerThread,
					  0,
					  0,
					  &ThreadId);
	}

	CreateThread (NULL,
				  0,
				  ControlThread,
				  0,
				  0,
				  &ThreadId);


	CreateThread (NULL,
				 0,
				 ProducerThread100,
				 0,
				 0,
				 &ThreadId);
#if 0
	CreateThread (NULL,
				  0,
				  ErrorProducerThread,
				  0,
				  0,
				  &ThreadId);

	CreateThread (NULL,
				  0,
				  UpdateThread,
				  0,
				  0,
				  &ThreadId);
				 
#endif
	Sleep (INFINITE);
}