#include "ThreadPool.h"
#include "PriorityQueueDLL.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <malloc.h>

#ifdef __cplusplus
#error This performance harness must be compiled as C, not C++.
#endif
#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 201710L
#error This performance harness requires C17.
#endif

#define REPEATS 7
#define WARMUPS 2
#define MAX_PRODUCERS 16
#define REQUIRE(condition) do { if (!(condition)) { \
	fprintf(stderr, "FAIL line %d: %s (Win32 error %lu)\n", __LINE__, #condition, GetLastError()); \
	goto Cleanup; } } while (0)

/* 01 / Resolve the existing DLL ABI before timing. No C++ adapter or copied pool code.
   ThreadPool exports MSVC-decorated C++ names. Look up the unique public name in
   this build's loaded export table; do not change the production DLL's ABI. */
typedef BOOL (*INITIALIZE_POOL)(PTHREADPOOL, DWORD, DWORD, PVOID, PFN_POOL_ENQUEUE, PFN_POOL_DEQUEUE, DWORD);
typedef BOOL (*SUBMIT_WORK)(PTHREADPOOL, PFN_WORK_CALLBACK, PVOID, ULONG, PFN_WORK_COMPLETE);
typedef VOID (*SHUTDOWN_POOL)(PTHREADPOOL);
static INITIALIZE_POOL PoolInitialize;
static SUBMIT_WORK PoolSubmit;
static SHUTDOWN_POOL PoolShutdown;

static FARPROC PoolExport(HMODULE module, const char* name)
{
	FARPROC result = GetProcAddress(module, name);
	const BYTE* base = (const BYTE*)module;
	const IMAGE_DOS_HEADER* dos = (const IMAGE_DOS_HEADER*)base;
	const IMAGE_NT_HEADERS* nt;
	const IMAGE_EXPORT_DIRECTORY* exports;
	const DWORD* names;
	char prefix[128];
	size_t length;
	DWORD i, rva;
	if (result) return result;
	if (dos->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
	nt = (const IMAGE_NT_HEADERS*)(base + dos->e_lfanew);
	if (nt->Signature != IMAGE_NT_SIGNATURE) return NULL;
	rva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
	if (!rva) return NULL;
	exports = (const IMAGE_EXPORT_DIRECTORY*)(base + rva);
	names = (const DWORD*)(base + exports->AddressOfNames);
	if (sprintf_s(prefix, sizeof(prefix), "?%s@@", name) < 0) return NULL;
	length = strlen(prefix);
	for (i = 0; i < exports->NumberOfNames; ++i)
	{
		const char* candidate = (const char*)(base + names[i]);
		if (strncmp(candidate, prefix, length) == 0)
		{
			if (result) return NULL; /* Reject ambiguous overloads instead of guessing. */
			result = GetProcAddress(module, candidate);
		}
	}
	return result;
}

static LONGLONG Now(void)
{
	LARGE_INTEGER value;
	QueryPerformanceCounter(&value);
	return value.QuadPart;
}

/* 02 / Deterministic workload. No Sleep in any timed callback. */
__declspec(noinline) static uint64_t Compute(uint64_t id, unsigned iterations)
{
	uint64_t value = id + UINT64_C(0x9e3779b97f4a7c15);
	unsigned i;
	for (i = 0; i < iterations; ++i)
	{
		value ^= value >> 12;
		value ^= value << 25;
		value ^= value >> 27;
		value *= UINT64_C(2685821657736338717);
	}
	return value;
}

static BOOL Enqueue(PVOID queue, PNODE node, ULONG key)
{
	return EnqueueWithPriority((PPRIORITY_QUEUE)queue, node, (PRIORITY)key);
}
static PNODE Dequeue(PVOID queue)
{
	return DequeueHighest((PPRIORITY_QUEUE)queue, NULL);
}

typedef struct BATCH BATCH;
typedef struct __declspec(align(64)) TASK
{
	BATCH* batch;
	unsigned id, iterations, priority;
	volatile LONG accepted, executions, completions;
	volatile LONG succeeded, cancelled, invalid;
	volatile LONG64 result;
} TASK;
typedef struct PRODUCER
{
	BATCH* batch;
	unsigned index;
} PRODUCER;
struct BATCH
{
	THREADPOOL pool;
	PRIORITY_QUEUE queue;
	BOOL queueLive, poolLive;
	HANDLE start, ready, done;
	volatile LONG producersReady, completed;
	unsigned producerCount, taskCount, threadCount;
	TASK* tasks;
	PRODUCER producers[MAX_PRODUCERS];
	HANDLE threads[MAX_PRODUCERS];
};
typedef struct OUTPUT
{
	FILE* file;
	LONGLONG frequency;
} OUTPUT;

/* 03 / Explicit C cleanup: join producers, cancel queued work, join workers,
   then release queue/events/contexts. This also handles partial setup failures. */
static void BatchStop(BATCH* batch)
{
	unsigned i;
	if (batch->start) SetEvent(batch->start);
	for (i = 0; i < batch->threadCount; ++i)
	{
		WaitForSingleObject(batch->threads[i], INFINITE);
		CloseHandle(batch->threads[i]);
	}
	batch->threadCount = 0;
	if (batch->poolLive) { PoolShutdown(&batch->pool); batch->poolLive = FALSE; }
}
static void BatchCleanup(BATCH* batch)
{
	BatchStop(batch);
	if (batch->queueLive) CleanupPriorityQueue(&batch->queue);
	if (batch->start) CloseHandle(batch->start);
	if (batch->ready) CloseHandle(batch->ready);
	if (batch->done) CloseHandle(batch->done);
	_aligned_free(batch->tasks);
	memset(batch, 0, sizeof(*batch));
}
static BOOL BatchInitialize(BATCH* batch, unsigned workers)
{
	batch->queueLive = InitializePriorityQueue(&batch->queue);
	if (!batch->queueLive) return FALSE;
	batch->poolLive = PoolInitialize(&batch->pool, workers, workers, &batch->queue, Enqueue, Dequeue, 60000);
	return batch->poolLive;
}
static VOID Work(PVOID context)
{
	TASK* task = (TASK*)context;
	InterlockedIncrement(&task->executions);
	InterlockedExchange64(&task->result, (LONG64)Compute(task->id, task->iterations));
}
static VOID Complete(PVOID context, WORK_STATUS status)
{
	TASK* task = (TASK*)context;
	InterlockedIncrement(&task->completions);
	if (status == WORK_STATUS_SUCCEEDED) InterlockedIncrement(&task->succeeded);
	else if (status == WORK_STATUS_CANCELLED) InterlockedIncrement(&task->cancelled);
	else InterlockedIncrement(&task->invalid);
	if (InterlockedIncrement(&task->batch->completed) == (LONG)task->batch->taskCount)
		SetEvent(task->batch->done);
}
static DWORD WINAPI Produce(PVOID context)
{
	PRODUCER* producer = (PRODUCER*)context;
	BATCH* batch = producer->batch;
	unsigned i;
	if (InterlockedIncrement(&batch->producersReady) == (LONG)batch->producerCount)
		SetEvent(batch->ready);
	WaitForSingleObject(batch->start, INFINITE);
	for (i = producer->index; i < batch->taskCount; i += batch->producerCount)
	{
		TASK* task = &batch->tasks[i];
		if (PoolSubmit(&batch->pool, Work, task, task->priority, Complete))
			InterlockedIncrement(&task->accepted);
	}
	return 0;
}
static BOOL Validate(const TASK* tasks, unsigned count)
{
	unsigned i;
	for (i = 0; i < count; ++i)
	{
		const TASK* task = &tasks[i];
		if (task->accepted != 1 || task->executions != 1 || task->completions != 1 ||
			task->succeeded != 1 || task->cancelled != 0 || task->invalid != 0 ||
			(uint64_t)task->result != Compute(task->id, task->iterations))
		{
			fprintf(stderr, "FAIL task %u: acceptance/execution/completion/status/checksum mismatch\n", task->id);
			return FALSE;
		}
	}
	return TRUE;
}

/* Exact priority counts and the same seeded Fisher-Yates permutation as capture v1. */
static BOOL Prepare(BATCH* batch, unsigned count, unsigned iterations, unsigned distribution)
{
	unsigned i;
	unsigned* priorities = (unsigned*)malloc(count * sizeof(*priorities));
	uint32_t seed = 0x5eed1234;
	batch->taskCount = count;
	batch->tasks = (TASK*)_aligned_malloc(count * sizeof(*batch->tasks), 64);
	if (!priorities || !batch->tasks) { free(priorities); return FALSE; }
	memset(batch->tasks, 0, count * sizeof(*batch->tasks));
	for (i = 0; i < count; ++i)
		priorities[i] = distribution == 0 ? i % 3 :
			(i % 20 < 18 ? (distribution == 1 ? 0u : 2u) : (i % 20 == 18 ? 1u : (distribution == 1 ? 2u : 0u)));
	for (i = count - 1; i > 0; --i)
	{
		unsigned other, value;
		seed = seed * 1664525u + 1013904223u;
		other = seed % (i + 1);
		value = priorities[i]; priorities[i] = priorities[other]; priorities[other] = value;
	}
	for (i = 0; i < count; ++i)
	{
		batch->tasks[i].batch = batch;
		batch->tasks[i].id = i;
		batch->tasks[i].iterations = iterations;
		batch->tasks[i].priority = priorities[i];
	}
	free(priorities);
	return TRUE;
}

/* 04 / CSV rows are emitted only after every task passes validation. */
static BOOL OutputOpen(OUTPUT* output)
{
	LARGE_INTEGER frequency;
	FILE* metadata = NULL;
	BOOL ok = FALSE;
#ifdef NATIVE_BENCHMARK_QUIET
	const char* instrumentation = "trace-info-call-sites-disabled";
#else
	const char* instrumentation = "ordinary-diagnostics-enabled";
#endif
	REQUIRE(QueryPerformanceFrequency(&frequency));
	output->frequency = frequency.QuadPart;
	REQUIRE(fopen_s(&metadata, "native-metadata.json", "w") == 0);
	REQUIRE(fprintf(metadata, "{\"qpcFrequency\":%lld,\"compiler\":%d,\"instrumentation\":\"%s\",\"warmups\":%d,\"repeats\":%d,\"language\":\"C\",\"cStandard\":%ld,\"artifactVersion\":2}\n",
		output->frequency, _MSC_FULL_VER, instrumentation, WARMUPS, REPEATS, (long)__STDC_VERSION__) > 0);
	REQUIRE(fopen_s(&output->file, "native-runs.csv", "w") == 0);
	REQUIRE(fprintf(output->file, "kind,workers,producers,distribution,iterations,tasks,run,qpc_ticks,ms,tasks_per_second,accepted,executions,completions,succeeded,cancelled,invalid,checksum_matches,high,medium,low\n") > 0);
	ok = TRUE;
Cleanup:
	if (metadata && fclose(metadata) != 0) ok = FALSE;
	return ok;
}
static BOOL Row(OUTPUT* output, const char* kind, unsigned workers, unsigned producers,
	const char* distribution, unsigned iterations, unsigned count, int run, LONGLONG ticks,
	unsigned high, unsigned medium, unsigned low)
{
	double ms;
	if (run < 0) return TRUE;
	if (ticks <= 0) return FALSE;
	ms = 1000.0 * ticks / output->frequency;
	if (fprintf(output->file, "%s,%u,%u,%s,%u,%u,%d,%lld,%.9f,%.6f,%u,%u,%u,%u,0,0,%u,%u,%u,%u\n",
		kind, workers, producers, distribution, iterations, count, run + 1, ticks, ms,
		count * 1000.0 / ms, count, count, count, count, count, high, medium, low) < 0) return FALSE;
	return fflush(output->file) == 0;
}

/* 05 / Throughput: release pre-created producers -> producer joins + completion event.
   Setup, pool/producer creation, shutdown and checksum recomputation are excluded. */
static BOOL Throughput(OUTPUT* output, unsigned workers, unsigned producerCount,
	unsigned iterations, unsigned distribution, int run)
{
	const char* names[] = { "uniform", "high90", "low90" };
	BATCH batch = { 0 };
	unsigned count = producerCount == 1 ? 12000 : 48000;
	unsigned i, counts[3] = { 0 };
	ULONGLONG deadline;
	BOOL idle = FALSE, completed, ok = FALSE;
	LONGLONG start, ticks;
	REQUIRE(producerCount >= 1 && producerCount <= MAX_PRODUCERS);
	REQUIRE(Prepare(&batch, count, iterations, distribution));
	REQUIRE(BatchInitialize(&batch, workers));
	batch.producerCount = producerCount;
	batch.start = CreateEvent(NULL, TRUE, FALSE, NULL);
	batch.ready = CreateEvent(NULL, TRUE, FALSE, NULL);
	batch.done = CreateEvent(NULL, TRUE, FALSE, NULL);
	REQUIRE(batch.start && batch.ready && batch.done);
	for (i = 0; i < producerCount; ++i)
	{
		HANDLE thread;
		batch.producers[i].batch = &batch;
		batch.producers[i].index = i;
		thread = CreateThread(NULL, 0, Produce, &batch.producers[i], 0, NULL);
		REQUIRE(thread != NULL);
		batch.threads[batch.threadCount++] = thread;
	}
	REQUIRE(WaitForSingleObject(batch.ready, 60000) == WAIT_OBJECT_0);
	deadline = GetTickCount64() + 60000;
	while (!idle && GetTickCount64() < deadline)
	{
		EnterCriticalSection(&batch.pool.Lock);
		idle = batch.pool.IdleThreads == (LONG)workers;
		LeaveCriticalSection(&batch.pool.Lock);
		if (!idle) Sleep(1);
	}
	REQUIRE(idle);
	start = Now();
	REQUIRE(SetEvent(batch.start));
	for (i = 0; i < batch.threadCount; ++i)
		REQUIRE(WaitForSingleObject(batch.threads[i], INFINITE) == WAIT_OBJECT_0);
	completed = WaitForSingleObject(batch.done, 60000) == WAIT_OBJECT_0;
	ticks = Now() - start;
	BatchStop(&batch);
	REQUIRE(completed);
	REQUIRE(Validate(batch.tasks, count));
	REQUIRE(GetPriorityQueueCount(&batch.queue) == 0);
	for (i = 0; i < count; ++i) ++counts[batch.tasks[i].priority];
	REQUIRE(Row(output, producerCount == 1 ? "throughput" : "many-producer", workers, producerCount,
		names[distribution], iterations, count, run, ticks, counts[0], counts[1], counts[2]));
	ok = TRUE;
Cleanup:
	BatchCleanup(&batch);
	return ok;
}

/* 06 / Serial thread and pool lifecycle baselines; not bare CreateThread API latency. */
static DWORD WINAPI OneThread(PVOID context)
{
	TASK* task = (TASK*)context;
	Work(context);
	InterlockedIncrement(&task->completions);
	InterlockedIncrement(&task->succeeded);
	return 0;
}
static BOOL ThreadBaseline(OUTPUT* output, unsigned iterations, int run)
{
	BATCH batch = { 0 };
	unsigned i;
	LONGLONG start, ticks;
	BOOL ok = FALSE;
	REQUIRE(Prepare(&batch, 256, iterations, 0));
	start = Now();
	for (i = 0; i < batch.taskCount; ++i)
	{
		HANDLE thread = CreateThread(NULL, 0, OneThread, &batch.tasks[i], 0, NULL);
		REQUIRE(thread != NULL);
		batch.threads[0] = thread;
		batch.threadCount = 1;
		InterlockedIncrement(&batch.tasks[i].accepted);
		REQUIRE(WaitForSingleObject(thread, INFINITE) == WAIT_OBJECT_0);
		CloseHandle(thread);
		batch.threadCount = 0;
	}
	ticks = Now() - start;
	REQUIRE(Validate(batch.tasks, batch.taskCount));
	REQUIRE(Row(output, "serial-create-work-join-close", 1, 1, "none", iterations, 256, run, ticks, 0, 0, 0));
	ok = TRUE;
Cleanup:
	BatchCleanup(&batch);
	return ok;
}
static BOOL Lifecycle(OUTPUT* output, unsigned workers, int run)
{
	BATCH batch = { 0 };
	BOOL initialized, ok = FALSE;
	LONGLONG start, ticks;
	batch.queueLive = InitializePriorityQueue(&batch.queue);
	REQUIRE(batch.queueLive);
	start = Now();
	batch.poolLive = PoolInitialize(&batch.pool, workers, workers, &batch.queue, Enqueue, Dequeue, 60000);
	initialized = batch.poolLive;
	BatchStop(&batch);
	ticks = Now() - start;
	REQUIRE(initialized);
	REQUIRE(Row(output, "pool-initialize-shutdown", workers, 0, "none", 0, 0, run, ticks, 0, 0, 0));
	ok = TRUE;
Cleanup:
	BatchCleanup(&batch);
	return ok;
}
static BOOL PriorityOrder(void)
{
	NODE nodes[90] = { 0 };
	BATCH batch = { 0 };
	unsigned i, priority;
	BOOL ok = FALSE;
	batch.queueLive = InitializePriorityQueue(&batch.queue);
	REQUIRE(batch.queueLive);
	for (i = 0; i < 90; ++i) REQUIRE(Enqueue(&batch.queue, &nodes[i], i % 3));
	for (priority = 0; priority < 3; ++priority)
		for (i = priority; i < 90; i += 3)
			REQUIRE(Dequeue(&batch.queue) == &nodes[i]);
	REQUIRE(Dequeue(&batch.queue) == NULL);
	ok = TRUE;
Cleanup:
	BatchCleanup(&batch);
	return ok;
}

/* 07 / Standalone C runner. All module resolution and output setup precede timing. */
int main(void)
{
	OUTPUT output = { 0 };
	HMODULE module = NULL;
	wchar_t path[32768];
	wchar_t* separator;
	DWORD length;
	int run, exitCode = EXIT_FAILURE;
	unsigned offset, distribution;
	const unsigned workerCounts[] = { 1, 4, 8 };
	length = GetModuleFileNameW(NULL, path, _countof(path));
	REQUIRE(length > 0 && length < _countof(path));
	separator = wcsrchr(path, L'\\');
	REQUIRE(separator != NULL);
	separator[1] = L'\0';
	REQUIRE(wcscat_s(path, _countof(path), L"ThreadPool.dll") == 0);
	module = LoadLibraryExW(path, NULL, LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
	REQUIRE(module != NULL);
	PoolInitialize = (INITIALIZE_POOL)PoolExport(module, "InitializeThreadPool");
	PoolSubmit = (SUBMIT_WORK)PoolExport(module, "SubmitWork");
	PoolShutdown = (SHUTDOWN_POOL)PoolExport(module, "ShutdownThreadPool");
	REQUIRE(PoolInitialize && PoolSubmit && PoolShutdown);
	REQUIRE(OutputOpen(&output));
	REQUIRE(PriorityOrder());
	for (run = -WARMUPS; run < REPEATS; ++run)
	{
		REQUIRE(ThreadBaseline(&output, 0, run));
		REQUIRE(ThreadBaseline(&output, 2048, run));
		for (offset = 0; offset < 3; ++offset)
		{
			unsigned workers = workerCounts[(offset + run + WARMUPS) % 3];
			REQUIRE(Lifecycle(&output, workers, run));
			for (distribution = 0; distribution < 3; ++distribution)
			{
				REQUIRE(Throughput(&output, workers, 1, 0, distribution, run));
				REQUIRE(Throughput(&output, workers, 1, 2048, distribution, run));
			}
		}
		for (distribution = 0; distribution < 3; ++distribution)
			REQUIRE(Throughput(&output, 8, 16, 2048, distribution, run));
	}
	exitCode = EXIT_SUCCESS;
Cleanup:
	if (output.file && fclose(output.file) != 0) exitCode = EXIT_FAILURE;
	if (module) FreeLibrary(module);
	if (exitCode == EXIT_SUCCESS)
		puts("NATIVE C BENCHMARKS PASSED: per-task acceptance/execution/completion/status/checksum and priority/FIFO checks.");
	return exitCode;
}
