/*
 * termproc.c
 *
 * Copyright (c) 1994 Software Research Associates, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  Software Research
 * Associates makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 */

#include "xlibInt.h"

XErrorHandler errorHandler;

extern int DefaultErrorHandler(Display *, XErrorEvent *);

#define BUFFER_LENGTH 1024

/*
 *----------------------------------------------------------------------
 *
 * RegisterTerminalClass --
 *
 *	Register the 'Terminal' window class so that
 *	TerminalProc gets called when something happens to the
 *	window.  Under NT, nothing should happen to the window
 *	after it is created since we use a standard console.
 *	Under Win32s, we would need to maintain our own console.
 *
 * Result:
 *	None
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
void
RegisterTerminalClass(HINSTANCE hInstance)
{		    
    WNDCLASS wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = TerminalProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 4;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = "Terminal";

    RegisterClass(&wndclass);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateTerminal --
 *
 *	Creates a window that we use to post certain messages to.
 *	Under NT, is has no other purpose.  If we support a console
 *	in Win32s, this window would be the terminal, and it would
 *	not be disabled.
 *
 * Result:
 *	A handle to the window
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
HWND 
CreateTerminal(HANDLE hInstance)
{
    return  CreateWindow("Terminal", "wish", 
			 WS_DISABLED,
			 CW_USEDEFAULT,
			 CW_USEDEFAULT,
			 0,
			 0,
			 NULL, NULL, hInstance, NULL);
}

/*
 *----------------------------------------------------------------------
 *
 * TerminalProc --
 *
 *	Handles messages for the terminal window.
 *	Under NT, is doesn't do much.
 *
 * Result:
 *	A handle to the window
 *
 * Side Effects:
 *	None
 *
 *----------------------------------------------------------------------
 */
static LRESULT CALLBACK
TerminalProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    XErrorEvent ee;
    static HGLOBAL hGlobal;

    switch(message) {
    case WM_TIMER:
	/* Really just a noop for testing purposes */
	break;

    case WM_CREATE:
	errorHandler = DefaultErrorHandler;

	hGlobal = GlobalAlloc(GHND, BUFFER_LENGTH+1);
	SetWindowLong(hwnd, 0, (LONG) hGlobal);

        return 0;


    case XERRORNOTIFY:
	ee.resourceid = (XID) lParam;
	ee.error_code = (unsigned char) wParam;
	(*errorHandler)(NULL, &ee);
	return 0;

    case WM_DESTROY:
    	PostQuitMessage(0);
    	return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

static int useSocket;
static HANDLE hSem;

/*
 *----------------------------------------------------------------------
 *
 * TclTerminalThread --
 *
 *	This routine reads stdin, and then it does one of two things:
 *	If the system is using sockets, it writes whatever it reads
 *	to one end of a socket.  The other end of the socket is checked
 *	by the event loop for activity.  If the system is not using
 *	sockets, a message is posted to let the system know something
 *	can be read.  The routine then suspends itself, and it can be
 *	resumed by calling TerminalThreadReenable().  StdinProc()
 *	in Tcl_Main() does this when it is ready.  Not elegant, but...
 *
 * Results:
 *	Shouldn't return, unless somthing went wrong.
 *
 * Side Effects:
 *      If sockets are not used, data is written into a global buffer.
 *	This buffer should not be changed until the data has been copied.
 *
 *----------------------------------------------------------------------
 */
DWORD WINAPI
TclTerminalThread(LPVOID arg)
{
    HWND postWin = arg;		/* Window to post messages to */
    SOCKET sock = (SOCKET) arg;	/* Socket to write input to */
    HANDLE hStdin;
    DWORD bytesRead;
    HGLOBAL hGlobal;
    char *pGlobal;
    char chbuffer[BUFFER_LENGTH];

    hSem = CreateSemaphore(NULL, 0L, 1L, NULL);
    if (! useSocket) {
	hGlobal = (HGLOBAL) GetWindowLong(postWin, 0);
    }

    hStdin = GetStdHandle(STD_INPUT_HANDLE);
    while (1) {
	if (! ReadFile(hStdin, chbuffer, BUFFER_LENGTH-1, &bytesRead, NULL)) {
	    /* This condition can occur after we do an exec or something
	     * that creates a file
	     */
	    continue;
	}

	if (! useSocket) {
	    chbuffer[bytesRead] = '\0';
	    if (bytesRead == 0) {
		continue;
	    }
	    pGlobal = GlobalLock(hGlobal);
	    strcpy(pGlobal, chbuffer);
	    GlobalUnlock(hGlobal);
	    PostMessage(postWin, INPUTNOTIFY, (WPARAM) hGlobal, 0L/* stdin */);
	    
	} else {
	    send(sock, chbuffer, bytesRead, 0);
	}
	WaitForSingleObject(hSem, INFINITE);
    }
	
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * TerminalThreadReenable --
 *
 *	Reenable the TclTerminalThread
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	TclTerminalThread becomes unfrozen
 *
 *----------------------------------------------------------------------
 */
void
TerminalThreadReenable()
{
    ReleaseSemaphore(hSem, 1L, NULL);
}
    
static HANDLE stdinThread;
/*
 *----------------------------------------------------------------------
 *
 * CreateTclTerminal --
 *
 *	This routine creates a thread that reads the standard input.
 *	The thread will post messages to a window or write to a socket.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The thread reading stdin cannot be disabled right now.  This
 *	causes problems for other programs that are started from this
 *	one that need to read stdin.  We should have a way to disable
 *	the thread when a process is being executed.
 *
 *----------------------------------------------------------------------
 */
void
CreateTclTerminal(HANDLE outHandle, int useSock)
{
    DWORD threadId;

    useSocket = useSock;
    stdinThread = CreateThread(NULL, 0, TclTerminalThread, outHandle, 0, 
			  &threadId);
    return;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyTclTerminal --
 *
 *	Destroys TclTerminalThread
 *
 * Results:
 *	None
 *
 * Side Effects:
 *	TclTerminalThread is destroyed
 *
 *----------------------------------------------------------------------
 */
void
DestroyTclTerminal()
{
    TerminateThread(stdinThread, 0);
}
