//
// Copyright 2020 Staysail Systems, Inc. <info@staysail.tech>
// Copyright 2018 Capitar IT Group BV <info@capitar.com>
//
// This software is supplied under the terms of the MIT License, a
// copy of which should be located in the distribution where this
// file was obtained (LICENSE.txt).  A copy of the license may also be
// found online at https://opensource.org/licenses/MIT.
//

#include "core/nng_impl.h"

#ifdef NNG_PLATFORM_WINDOWS

#include <stdio.h>

// Windows IO Completion Port support.  We basically create a single
// IO completion port, then start threads on it.  Handles are added
// to the port on an as needed basis.  We use a single IO completion
// port for pretty much everything.

static int      win_io_nthr = 0;
static HANDLE   win_io_h    = NULL;
static nni_thr *win_io_thrs;

static void
win_io_handler(void *arg)
{
	NNI_ARG_UNUSED(arg);

	for (;;) {
		DWORD       cnt;
		BOOL        ok;
		nni_win_io *item;
		OVERLAPPED *olpd = NULL;
		ULONG_PTR   key  = 0;
		int         rv;

		ok = GetQueuedCompletionStatus(
		    win_io_h, &cnt, &key, &olpd, INFINITE);

		if (olpd == NULL) {
			// Completion port closed...
			NNI_ASSERT(ok == FALSE);
			break;
		}

		item = CONTAINING_RECORD(olpd, nni_win_io, olpd);
		rv   = ok ? 0 : nni_win_error(GetLastError());
		item->cb(item, rv, (size_t) cnt);
	}
}

int
nni_win_io_register(HANDLE h)
{
	if (CreateIoCompletionPort(h, win_io_h, 0, 0) == NULL) {
		return (nni_win_error(GetLastError()));
	}
	return (0);
}

int
nni_win_io_init(nni_win_io *io, nni_win_io_cb cb, void *ptr)
{
	ZeroMemory(&io->olpd, sizeof(io->olpd));

	io->cb          = cb;
	io->ptr         = ptr;
	io->aio         = NULL;
	io->olpd.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	if (io->olpd.hEvent == NULL) {
		return (nni_win_error(GetLastError()));
	}
	return (0);
}

void
nni_win_io_fini(nni_win_io *io)
{
	if (io->olpd.hEvent != NULL) {
		CloseHandle((HANDLE) io->olpd.hEvent);
	}
}

int
nni_win_io_sysinit(void)
{
	HANDLE h;
	int    i;
	int    rv;
	int    nthr = nni_plat_ncpu() * 2;

	// Limits on the thread count.  This is fairly arbitrary.
	if (nthr < 4) {
		nthr = 4;
	}
	if (nthr > 64) {
		nthr = 64;
	}
	if ((win_io_thrs = NNI_ALLOC_STRUCTS(win_io_thrs, nthr)) == NULL) {
		return (NNG_ENOMEM);
	}
	win_io_nthr = nthr;

	h = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, nthr);
	if (h == NULL) {
		return (nni_win_error(GetLastError()));
	}
	win_io_h = h;

	for (i = 0; i < win_io_nthr; i++) {
		rv = nni_thr_init(&win_io_thrs[i], win_io_handler, NULL);
		if (rv != 0) {
			goto fail;
		}
		nni_thr_set_name(&win_io_thrs[i], "nng:iocp");
	}
	for (i = 0; i < win_io_nthr; i++) {
		nni_thr_run(&win_io_thrs[i]);
	}
	return (0);

fail:
	nni_win_io_sysfini();
	return (rv);
}

void
nni_win_io_sysfini(void)
{
	int    i;
	HANDLE h;

	if ((h = win_io_h) != NULL) {
		CloseHandle(h);
		win_io_h = NULL;
	}
	for (i = 0; i < win_io_nthr; i++) {
		nni_thr_fini(&win_io_thrs[i]);
	}

        NNI_FREE_STRUCTS(win_io_thrs, win_io_nthr);
}

#endif // NNG_PLATFORM_WINDOWS
