/*
 * Console control handler support.
 *
 */
#include "Rts.h"
#include <windows.h>
#include "ConsoleHandler.h"
#include "Schedule.h"
#include "RtsUtils.h"
#include "AsyncIO.h"
#include "RtsSignals.h"

extern int stg_InstallConsoleEvent(int action, StgStablePtr *handler);

static BOOL WINAPI shutdown_handler(DWORD dwCtrlType);
static BOOL WINAPI generic_handler(DWORD dwCtrlType);

static rtsBool deliver_event = rtsTrue;
StgInt console_handler = STG_SIG_DFL;

#if !defined(THREADED_RTS)

static HANDLE hConsoleEvent = INVALID_HANDLE_VALUE;

#define N_PENDING_EVENTS 16
StgInt stg_pending_events = 0;           /* number of undelivered events */
DWORD stg_pending_buf[N_PENDING_EVENTS]; /* their associated event numbers. */

#endif

/*
 * Function: initUserSignals()
 *
 * Initialize the console handling substrate.
 */
void
initUserSignals(void)
{
    console_handler = STG_SIG_DFL;
#if !defined (THREADED_RTS)
    stg_pending_events = 0;
    if (hConsoleEvent == INVALID_HANDLE_VALUE) {
	hConsoleEvent = 
	    CreateEvent ( NULL,  /* default security attributes */
			  TRUE,  /* manual-reset event */
			  FALSE, /* initially non-signalled */
			  NULL); /* no name */
    }
#endif
    return;
}

void
freeSignalHandlers(void) {
    /* Do nothing */
}

/* Seems to be a bit of an orphan...where used? */
void
finiUserSignals(void)
{
#if !defined (THREADED_RTS)
    if (hConsoleEvent != INVALID_HANDLE_VALUE) {
        CloseHandle(hConsoleEvent);
    }
#endif
}

/*
 * Function: shutdown_handler()
 *
 * Local function that performs the default handling of Ctrl+C kind
 * events; gently shutting down the RTS
 *
 * To repeat Signals.c remark -- user code may choose to override the
 * default handler. Which is fine, assuming they put back the default
 * handler when/if they de-install the custom handler.
 * 
 */
static BOOL WINAPI shutdown_handler(DWORD dwCtrlType)
{
    switch (dwCtrlType) {
    
    case CTRL_CLOSE_EVENT:
	/* see generic_handler() comment re: this event */
	return FALSE;
    case CTRL_C_EVENT:
    case CTRL_BREAK_EVENT:

	// If we're already trying to interrupt the RTS, terminate with
	// extreme prejudice.  So the first ^C tries to exit the program
	// cleanly, and the second one just kills it.
	if (sched_state >= SCHED_INTERRUPTING) {
	    stg_exit(EXIT_INTERRUPTED);
	} else {
	    interruptStgRts();
	}
	return TRUE;

	/* shutdown + logoff events are not handled here. */
    default:
	return FALSE;
    }
}


/*
 * Function: initDefaultHandlers()
 *
 * Install any default signal/console handlers. Currently we install a
 * Ctrl+C handler that shuts down the RTS in an orderly manner.
 */
void initDefaultHandlers(void)
{
    if ( !SetConsoleCtrlHandler(shutdown_handler, TRUE) ) {
	errorBelch("warning: failed to install default console handler");
    }
}

void resetDefaultHandlers(void)
{
    if ( !SetConsoleCtrlHandler(shutdown_handler, FALSE) ) {
	errorBelch("warning: failed to uninstall default console handler");
    }
}

/*
 * Function: blockUserSignals()
 *
 * Temporarily block the delivery of further console events. Needed to
 * avoid race conditions when GCing the stack of outstanding handlers or
 * when emptying the stack by running the handlers.
 * 
 */
void
blockUserSignals(void)
{
    deliver_event = rtsFalse;
}


/*
 * Function: unblockUserSignals()
 *
 * The inverse of blockUserSignals(); re-enable the deliver of console events.
 */
void
unblockUserSignals(void)
{
    deliver_event = rtsTrue;
}


/*
 * Function: awaitUserSignals()
 *
 * Wait for the next console event. Currently a NOP (returns immediately.)
 */
void awaitUserSignals(void)
{
    return;
}


#if !defined (THREADED_RTS)
/*
 * Function: startSignalHandlers()
 *
 * Run the handlers associated with the stacked up console events. Console
 * event delivery is blocked for the duration of this call.
 */
void startSignalHandlers(Capability *cap)
{
    StgStablePtr handler;

    if (console_handler < 0) {
	return;
    }

    blockUserSignals();
    ACQUIRE_LOCK(&sched_mutex);
    
    handler = deRefStablePtr((StgStablePtr)console_handler);
    while (stg_pending_events > 0) {
	stg_pending_events--;
	scheduleThread(cap,
	    createIOThread(cap,
			   RtsFlags.GcFlags.initialStkSize, 
			   rts_apply(cap,
				     (StgClosure *)handler,
				     rts_mkInt(cap,
					       stg_pending_buf[stg_pending_events]))));
    }
    
    RELEASE_LOCK(&sched_mutex);
    unblockUserSignals();
}
#endif /* !THREADED_RTS */

/*
 * Function: markSignalHandlers()
 *
 * Evacuate the handler stack. _Assumes_ that console event delivery
 * has already been blocked.
 */
void markSignalHandlers (evac_fn evac STG_UNUSED, void *user STG_UNUSED)
{
    // nothing to mark; the console handler is a StablePtr which is
    // already treated as a root by the GC.
}


/* 
 * Function: generic_handler()
 *
 * Local function which handles incoming console event (done in a sep OS thread),
 * recording the event in stg_pending_events. 
 */
static BOOL WINAPI generic_handler(DWORD dwCtrlType)
{
    /* Ultra-simple -- up the counter + signal a switch. */
    switch(dwCtrlType) {
    case CTRL_CLOSE_EVENT:
	/* Don't support the delivery of this event; if we
	 * indicate that we've handled it here and the Haskell handler
	 * doesn't take proper action (e.g., terminate the OS process),
	 * the user of the app will be unable to kill/close it. Not
	 * good, so disable the delivery for now.
	 */
	return FALSE;
    default:
	if (!deliver_event) return TRUE;

#if defined(THREADED_RTS)
        sendIOManagerEvent((StgWord8) ((dwCtrlType<<1) | 1));
#else
	if ( stg_pending_events < N_PENDING_EVENTS ) {
	    stg_pending_buf[stg_pending_events] = dwCtrlType;
	    stg_pending_events++;
	}

        // we need to wake up awaitEvent()
        abandonRequestWait();
#endif
	return TRUE;
    }
}


/*
 * Function: rts_InstallConsoleEvent()
 *
 * Install/remove a console event handler.
 */
int
rts_InstallConsoleEvent(int action, StgStablePtr *handler)
{
    StgInt previous_hdlr = console_handler;

    switch (action) {
    case STG_SIG_IGN:
	console_handler = STG_SIG_IGN;
	if ( !SetConsoleCtrlHandler(NULL, TRUE) ) {
	    errorBelch("warning: unable to ignore console events");
	}
	break;
    case STG_SIG_DFL:
	console_handler = STG_SIG_IGN;
	if ( !SetConsoleCtrlHandler(NULL, FALSE) ) {
	    errorBelch("warning: unable to restore default console event handling");
	}
	break;
    case STG_SIG_HAN:
#ifdef THREADED_RTS
        // handler is stored in an MVar in the threaded RTS
	console_handler = STG_SIG_HAN;
#else
	console_handler = (StgInt)*handler;
#endif
	if (previous_hdlr < 0 || previous_hdlr == STG_SIG_HAN) {
	  /* Only install generic_handler() once */
	  if ( !SetConsoleCtrlHandler(generic_handler, TRUE) ) {
	    errorBelch("warning: unable to install console event handler");
	  }
	}
	break;
    }
    
    if (previous_hdlr == STG_SIG_DFL || 
	previous_hdlr == STG_SIG_IGN ||
        previous_hdlr == STG_SIG_HAN) {
	return previous_hdlr;
    } else {
	if (handler != NULL) {
            *handler = (StgStablePtr)previous_hdlr;
        }
	return STG_SIG_HAN;
    }
}

/*
 * Function: rts_HandledConsoleEvent()
 *
 * Signal that a Haskell console event handler has completed its run.
 * The explicit notification that a Haskell handler has completed is 
 * required to better handle the delivery of Ctrl-C/Break events whilst
 * an async worker thread is handling a read request on stdin. The 
 * Win32 console implementation will abort such a read request when Ctrl-C
 * is delivered. That leaves the worker thread in a bind: should it 
 * abandon the request (the Haskell thread reading from stdin has been 
 * thrown an exception to signal the delivery of Ctrl-C & hence have 
 * aborted the I/O request) or simply ignore the aborted read and retry?
 * (the Haskell thread reading from stdin isn't concerned with the
 * delivery and handling of Ctrl-C.) With both scenarios being
 * possible, the worker thread needs to be told -- that is, did the
 * console event handler cause the IO request to be abandoned? 
 *
 */
void
rts_ConsoleHandlerDone (int ev USED_IF_NOT_THREADS)
{
#if !defined(THREADED_RTS)
    if ( (DWORD)ev == CTRL_BREAK_EVENT ||
	 (DWORD)ev == CTRL_C_EVENT ) {
	/* only these two cause stdin system calls to abort.. */
	SetEvent(hConsoleEvent); /* event is manual-reset */
	Sleep(0); /* yield */
	ResetEvent(hConsoleEvent); /* turn it back off again */
        // SDM: yeuch, this can't possibly work reliably.
        // I'm not having it in THREADED_RTS.
    }
#endif
}

#if !defined(THREADED_RTS)
/*
 * Function: rts_waitConsoleHandlerCompletion()
 *
 * Esoteric entry point used by worker thread that got woken
 * up as part Ctrl-C delivery.
 */
int
rts_waitConsoleHandlerCompletion()
{
    /* As long as the worker doesn't need to do a multiple wait,
     * let's keep this HANDLE private to this 'module'.
     */
    return (WaitForSingleObject(hConsoleEvent, INFINITE) == WAIT_OBJECT_0);
}
#endif
