/* 
 * tclWinNotify.c --
 *
 *	This file contains Windows-specific procedures for the notifier,
 *	which is the lowest-level part of the Tcl event loop.  This file
 *	works together with ../generic/tclNotify.c.
 *
 * Copyright (c) 1995-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tclWinNotify.c,v 1.2 2001/09/14 01:44:26 zlaski Exp $
 */

#include "tclInt.h"
#include "tclPort.h"
#include <winsock.h>

/*
 * The follwing static indicates whether this module has been initialized.
 */

static int initialized = 0;

#define INTERVAL_TIMER 1		/* Handle of interval timer. */

/*
 * The following static structure contains the state information for the
 * Windows implementation of the Tcl notifier.
 */

static struct {
    HWND hwnd;			/* Messaging window. */
    int timeout;		/* Current timeout value. */
    int timerActive;		/* 1 if interval timer is running. */
} notifier;

/*
 * Static routines defined in this file.
 */

static void		InitNotifier(void);
static void		NotifierExitHandler(ClientData clientData);
static LRESULT CALLBACK	NotifierProc(HWND hwnd, UINT message,
			    WPARAM wParam, LPARAM lParam);
static void		UpdateTimer(int timeout);

/*
 *----------------------------------------------------------------------
 *
 * InitNotifier --
 *
 *	Initializes the notifier window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Creates a new notifier window and window class.
 *
 *----------------------------------------------------------------------
 */

static void
InitNotifier(void)
{
    WNDCLASS class;

    initialized = 1;
    notifier.timerActive = 0;
    class.style = 0;
    class.cbClsExtra = 0;
    class.cbWndExtra = 0;
    class.hInstance = TclWinGetTclInstance();
    class.hbrBackground = NULL;
    class.lpszMenuName = NULL;
    class.lpszClassName = "TclNotifier";
    class.lpfnWndProc = NotifierProc;
    class.hIcon = NULL;
    class.hCursor = NULL;

    if (!RegisterClass(&class)) {
	panic("Unable to register TclNotifier window class");
    }
    notifier.hwnd = CreateWindow("TclNotifier", "TclNotifier", WS_TILED,
	    0, 0, 0, 0, NULL, NULL, TclWinGetTclInstance(), NULL);
    Tcl_CreateExitHandler(NotifierExitHandler, NULL);
}

/*
 *----------------------------------------------------------------------
 *
 * NotifierExitHandler --
 *
 *	This function is called to cleanup the notifier state before
 *	Tcl is unloaded.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Destroys the notifier window.
 *
 *----------------------------------------------------------------------
 */

static void
NotifierExitHandler(
    ClientData clientData)	/* Old window proc */
{
    initialized = 0;
    if (notifier.hwnd) {
	KillTimer(notifier.hwnd, INTERVAL_TIMER);
	DestroyWindow(notifier.hwnd);
	UnregisterClass("TclNotifier", TclWinGetTclInstance());
	notifier.hwnd = NULL;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * UpdateTimer --
 *
 *	This function starts or stops the notifier interval timer.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
UpdateTimer(
    int timeout)		/* ms timeout, 0 means cancel timer */
{
    notifier.timeout = timeout;
    if (timeout != 0) {
	notifier.timerActive = 1;
	SetTimer(notifier.hwnd, INTERVAL_TIMER,
		    (unsigned long) notifier.timeout, NULL);
    } else {
	notifier.timerActive = 0;
	KillTimer(notifier.hwnd, INTERVAL_TIMER);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_SetTimer --
 *
 *	This procedure sets the current notifier timer value.  The
 *	notifier will ensure that Tcl_ServiceAll() is called after
 *	the specified interval, even if no events have occurred.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Replaces any previous timer.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_SetTimer(
    Tcl_Time *timePtr)		/* Maximum block time, or NULL. */
{
    UINT timeout;

    if (!initialized) {
	InitNotifier();
    }

    if (!timePtr) {
	timeout = 0;
    } else {
	/*
	 * Make sure we pass a non-zero value into the timeout argument.
	 * Windows seems to get confused by zero length timers.
	 */
	timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
	if (timeout == 0) {
	    timeout = 1;
	}
    }
    UpdateTimer(timeout);
}

/*
 *----------------------------------------------------------------------
 *
 * NotifierProc --
 *
 *	This procedure is invoked by Windows to process the timer
 *	message whenever we are using an external dispatch loop.
 *
 * Results:
 *	A standard windows result.
 *
 * Side effects:
 *	Services any pending events.
 *
 *----------------------------------------------------------------------
 */

static LRESULT CALLBACK
NotifierProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{

    if (message != WM_TIMER) {
	return DefWindowProc(hwnd, message, wParam, lParam);
    }
	
    /*
     * Process all of the runnable events.
     */

    Tcl_ServiceAll();
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_WaitForEvent --
 *
 *	This function is called by Tcl_DoOneEvent to wait for new
 *	events on the message queue.  If the block time is 0, then
 *	Tcl_WaitForEvent just polls the event queue without blocking.
 *
 * Results:
 *	Returns -1 if a WM_QUIT message is detected, returns 1 if
 *	a message was dispatched, otherwise returns 0.
 *
 * Side effects:
 *	Dispatches a message to a window procedure, which could do
 *	anything.
 *
 *----------------------------------------------------------------------
 */

int
Tcl_WaitForEvent(
    Tcl_Time *timePtr)		/* Maximum block time, or NULL. */
{
    MSG msg;
    int timeout;

    if (!initialized) {
	InitNotifier();
    }

    /*
     * Only use the interval timer for non-zero timeouts.  This avoids
     * generating useless messages when we really just want to poll.
     */

    if (timePtr) {
	timeout = timePtr->sec * 1000 + timePtr->usec / 1000;
    } else {
	timeout = 0;
    }
    UpdateTimer(timeout);
	
    if (!timePtr || (timeout != 0)
	    || PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
	if (!GetMessage(&msg, NULL, 0, 0)) {

	    /*
	     * The application is exiting, so repost the quit message
	     * and start unwinding.
	     */

	    PostQuitMessage(msg.wParam);
	    return -1;
	}

	/*
	 * Handle timer expiration as a special case so we don't
	 * claim to be doing work when we aren't.
	 */

	if (msg.message == WM_TIMER && msg.hwnd == notifier.hwnd) {
	    return 0;
	}

	TranslateMessage(&msg);
	DispatchMessage(&msg);
	return 1;
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * Tcl_Sleep --
 *
 *	Delay execution for the specified number of milliseconds.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Time passes.
 *
 *----------------------------------------------------------------------
 */

void
Tcl_Sleep(ms)
    int ms;			/* Number of milliseconds to sleep. */
{
    Sleep(ms);
}
