/* --- Copyright University of Sussex 1996. All rights reserved. ----------
 * File:			C.win32/extern/src/handle.c
 * Purpose:			Operations on HANDLEs
 * Author:			Robert Duncan, May 24 1996
 */

#include "popcore.h"

/*
 *	Handling Asynchronous Objects (ASOs)
 */

	/* This new thread waits for any ASO handle to be signaled and
	   raises an AST within Poplog */
HANDLE aso_thread;

	/* Synchronisation objects for main thread & waiting thread */
HANDLE aso_change_pending, aso_change_done;	/* events */
CRITICAL_SECTION aso_section;

	/* ASO handles to wait for, plus spare slot for aso_change_pending */
HANDLE aso_handle_set[POP_MAX_HANDLES+1];

	/* Count of objects to wait for */
DWORD aso_count;

	/* Tell Poplog that an ASO has been signaled */
static void aso_raise(HANDLE obj) {
	_pop_add_ast(POP_AST_HANDLE, (DWORD)obj);
}

	/* Find an object in the handle set */
static DWORD aso_find_object(HANDLE obj) {
	DWORD i;
	for (i = 1; i <= aso_count; ++i)
		if (aso_handle_set[i] == obj)
			return i;
	return 0;
}

	/* Remove the i'th object from the handle set */
static void aso_rem_object_at(DWORD i) {
	for (--aso_count; i <= aso_count; ++i)
		aso_handle_set[i] = aso_handle_set[i+1];
}

	/* Start procedure for the ASO thread */
static DWORD aso_wait(DWORD n) {
	EnterCriticalSection(&aso_section);
	__try {
		for (;;) {
			DWORD res = WaitForMultipleObjects(aso_count+1, aso_handle_set,
							FALSE, INFINITE);
			if (res == WAIT_OBJECT_0) {
				/* change pending; release c/s until it's done */
				LeaveCriticalSection(&aso_section);
				WaitForSingleObject(aso_change_done, INFINITE);
				EnterCriticalSection(&aso_section);
			}
			else if (res == WAIT_FAILED) {
				/* abandon all pending waits -- what else? */
				aso_count = 0;
				aso_raise(NULL);	/* NULL => error */
			}
			else {
				if (res > WAIT_OBJECT_0 && res <= WAIT_OBJECT_0 + aso_count)
					res -= WAIT_OBJECT_0;
				else if (res > WAIT_ABANDONED_0 && res <= WAIT_ABANDONED_0 + aso_count)
					res -= WAIT_ABANDONED_0;
				else /* uh? */
					continue;
				aso_raise(aso_handle_set[res]);
				aso_rem_object_at(res);
			}
		}
	}
	__finally {
		aso_thread = NULL;
		/* don't know if this gets done anyway, but it doesn't hurt... */
		LeaveCriticalSection(&aso_section);
	}
	return 0;
}

	/* Initialize global data */
static BOOL aso_setup() {
	if (aso_change_pending == NULL)
		if (!(aso_change_pending = CreateEvent(NULL, TRUE, FALSE, NULL)))
			return FALSE;
	if (aso_change_done == NULL)
		if (!(aso_change_done = CreateEvent(NULL, TRUE, FALSE, NULL)))
			return FALSE;
	InitializeCriticalSection(&aso_section);
	if (aso_thread == NULL) {
		DWORD thread_id;
		if (!(aso_thread = CreateThread(NULL, 4096,
				(LPTHREAD_START_ROUTINE)aso_wait, 0, 0, &thread_id)))
			return FALSE;
	}
	aso_handle_set[0] = aso_change_pending;
	return TRUE;
}

	/* Add/remove an object from the handle set */
static BOOL aso_change_object_set(BOOL add, HANDLE obj) {
	DWORD i;
	if (aso_thread == NULL && !aso_setup())
		return FALSE;
	/* tell ASO thread we'd like to enter the c/s */
	if (!ResetEvent(aso_change_done) || !SetEvent(aso_change_pending))
		return FALSE;
	/* wait for it */
	EnterCriticalSection(&aso_section);
	/* OK -- unset the indicator */
	ResetEvent(aso_change_pending);
	/* update ASO handle set */
	i = aso_find_object(obj);
	if (add) {
		if (i == 0) {
			++aso_count;
			aso_handle_set[aso_count] = obj;
		}
	}
	else /* remove */
		if (i != 0)
			aso_rem_object_at(i);
	/* release critical section */
	LeaveCriticalSection(&aso_section);
	/* allow ASO thread to proceed */
	SetEvent(aso_change_done);
}

	/* Public function to add an object to the handle set */
BOOL pop_add_async_object(HANDLE obj) {
	if (aso_count >= POP_MAX_HANDLES)
		return FALSE;
	/* check for bogus handle */
	switch (WaitForSingleObject(obj, 0)) {
		case WAIT_OBJECT_0:
			/* already signaled */
			aso_raise(obj);
			return TRUE;
		case WAIT_TIMEOUT:
			/* OK -- add to list */
			return aso_change_object_set(TRUE, obj);
		case WAIT_FAILED:
		default:
			/* bogus object */
			return FALSE;
	}
}

	/* Public function to remove an object from the handle set */
BOOL pop_rem_async_object(HANDLE obj) {
	return aso_count == 0 || aso_change_object_set(FALSE, obj);
}

/*
 *	Synchronous wait
 */

int pop_wait_for_objects(DWORD n, HANDLE *hndv[], DWORD timeout) {
	static HANDLE handles[POP_MAX_HANDLES+1];
	DWORD i, res;
	if (n > POP_MAX_HANDLES) n = POP_MAX_HANDLES;
	for (i = 0; i < n; i++) {
		handles[i] = *hndv[i];
	}
	/* put AST event last so that it will be chosen only if nothing else
	   is ready */
	handles[n] = pop_ast_queue_non_empty;
	res = WaitForMultipleObjects(n+1, handles, FALSE, timeout);
	if (res == WAIT_FAILED)
		/* error */
		return -1;
	else if (res == WAIT_OBJECT_0 + n)
		/* AST */
		return 0;
	else if (res >= WAIT_OBJECT_0 && res < WAIT_OBJECT_0 + n)
		/* object 1..n */
		return (res - WAIT_OBJECT_0) + 1;
	else if (res >= WAIT_ABANDONED_0 && res < WAIT_ABANDONED_0 + n)
		/* result will not distinguish this case */
		return (res - WAIT_ABANDONED_0) + 1;
	else
		/* timeout */
		return n+1;
}

/*
 *	Miscellaneous handle operations
 */

	/* Close a HANDLE */
BOOL pop_close_handle(HANDLE handle) {
	return CloseHandle(handle);
}
