/*
 * $Id: win32.c,v 1.38 2004/02/07 06:32:01 phil Exp $
 * Win32 support for XY display simulator
 * Phil Budne <phil@ultimate.com>
 * September 2003
 * Revised by Douglas A. Gwyn, 05 Feb. 2004
 */

/*
 * Copyright (c) 2003-2004, Philip L. Budne
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Except as contained in this notice, the names of the authors shall
 * not be used in advertising or otherwise to promote the sale, use or
 * other dealings in this Software without prior written authorization
 * from the authors.
 */

/* use a thread to handle windows messages; */
#define THREADS

/*
 * BUGS:
 * Does not allow you to close display window;
 * would need to tear down both system, and system independent data.
 *
 * now tries to handle PAINT message, as yet untested!!
 */

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "ws.h"
#include "display.h"

#ifndef PIX_SIZE
#define PIX_SIZE 1
#endif

#define APP_CLASS "XYAppClass"
#define APP_MENU "XYAppMenu"		/* ?? */

/*
 * light pen location
 * see ws.h for full description
 */
int ws_lp_x = -1;
int ws_lp_y = -1;

static HWND static_wh;
static HINSTANCE static_inst;
static int xpixels, ypixels;
static char *window_name;
static HBRUSH white_brush;
static HBRUSH black_brush;
#ifdef SWITCH_CURSORS
static HCURSOR cross, arrow;
#endif

static __inline int
map_key(int k)
{
    switch (k) {
    case 186: return ';';		/* VK_OEM_1? */
    case 222: return '\'';		/* VK_OEM_7? */
    }
    return k;
}

static void
keydown(int k)
{
    display_keydown(map_key(k));
}

static void
keyup(int k)
{
    display_keyup(map_key(k));
}

/*
 * here on any button click, or if mouse dragged while a button down
 */
static void
mousepos(DWORD lp)
{
    int x, y;

    x = LOWORD(lp);
    y = HIWORD(lp);

    /* convert to display coordinates */
#if PIX_SIZE > 1
    x /= PIX_SIZE;
    y /= PIX_SIZE;
#endif
    y = ypixels - 1 - y;

    /* if window has been stretched, can get out of range bits!! */
    if (x >= 0 && x < xpixels && y >= 0 && y < ypixels) {
	/* checked by display_add_point() */
	ws_lp_x = x;
	ws_lp_y = y;
    }
}

/* thoingggg!! "message for you sir!!!" */
static LRESULT CALLBACK
patsy(HWND wh, UINT msg, WPARAM wp, LPARAM lp) /* "WndProc" */
{
    /* printf("msg %d\n", msg); */
    switch (msg) {
    case WM_DESTROY:
	PostQuitMessage(0);
	return 0;

    case WM_MOUSEMOVE:
	if (wp & (MK_LBUTTON|MK_MBUTTON|MK_RBUTTON)) {
#ifdef SWITCH_CURSORS
	    if (ws_lp_x == -1 && !display_tablet)
		SetCursor(cross);
#endif
	    mousepos(lp);
	}
#ifdef SWITCH_CURSORS
	else if (ws_lp_x != -1 && !display_tablet)
	    SetCursor(arrow);
#endif
	break;				/* return?? */

    case WM_LBUTTONDOWN:
	display_lp_sw = 1;
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN:
#ifdef SWITCH_CURSORS
	if (!display_tablet)
	    SetCursor(cross);
#endif
	mousepos(lp);
	break;				/* return?? */

    case WM_LBUTTONUP:
	display_lp_sw = 0;
    case WM_MBUTTONUP:
    case WM_RBUTTONUP:
#ifdef SWITCH_CURSORS
	if (!display_tablet)
	    SetCursor(arrow);
#endif
	ws_lp_x = ws_lp_y = -1;
	break;				/* return?? */

    case WM_KEYDOWN:
	keydown(wp);
	break;

    case WM_KEYUP:
	keyup(wp);
	break;

    case WM_PAINT:
	display_repaint();
	break;				/* return?? */
    }
    return DefWindowProc(wh, msg, wp, lp);
}

int
ws_poll(int *valp, int maxus)
{
#ifdef THREADS
    /* msgthread handles window events; just delay simulator */
    if (maxus > 0)
	Sleep((maxus+999)/1000);
#else
    MSG msg;
    DWORD start;
    int maxms = (maxus + 999) / 1000;

    for (start = GetTickCount(); GetTickCount() - start < maxms; Sleep(1)) {
	/* empty message queue without blocking */
	while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
	    TranslateMessage(&msg);
	    DispatchMessage(&msg);
	}
    }
#endif
    return 1;
}

/* called from non-threaded main program */
int
ws_loop(void (*func)(void *), void *arg)
{
    int val;
    while (ws_poll(&val, 0))
	(*func)(arg);
    return val;
}

/* worker for display init */
static void
ws_init2(void) {
    WNDCLASS wc;
    int h, w;

#ifdef SWITCH_CURSORS
    if (!display_tablet) {
	arrow = LoadCursor(NULL, IDC_ARROW);
	cross = LoadCursor(NULL, IDC_CROSS);
    }
#endif

    black_brush = GetStockObject(BLACK_BRUSH);
    white_brush = GetStockObject(WHITE_BRUSH);

    wc.lpszClassName	= APP_CLASS;
    wc.lpfnWndProc	= patsy;
    wc.style		= CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
			/* also CS_NOCLOSE? CS_SAVEBITS? */

    wc.hInstance	= static_inst = GetModuleHandleA(0);
    wc.hIcon		= LoadIcon(NULL, IDI_APPLICATION);
#ifdef SWITCH_CURSORS
    wc.hCursor		= NULL;
#else
    wc.hCursor		= display_tablet ? NULL : LoadCursor(NULL, IDC_CROSS);
#endif
    wc.hbrBackground	= black_brush;
    wc.lpszMenuName	= APP_MENU;
    wc.cbClsExtra	= 0;
    wc.cbWndExtra	= 0; 
    /* WNDCLASSEX/RegisterClassEx include hIconSm (small icon) */
    RegisterClass(&wc);

    /* 
     * WS_OVERLAPPEDWINDOW=>
     *	WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME,
     *	WS_MINIMIZEBOX, WS_MAXIMIZEBOX
     *
     * WS_CHILD (no menu bar), WS_POPUP (mutually exclusive)
     */

    /* empirical crocks to get entire screen; */
    w = (xpixels*PIX_SIZE)+6;
    h = (ypixels*PIX_SIZE)+32;
    /* XXX -- above values work with XP; Phil had +10,+30 */

    static_wh = CreateWindow(APP_CLASS,		/* registered class name */
			     window_name,	/* window name */
			     WS_OVERLAPPED,	/* style */
			     CW_USEDEFAULT, CW_USEDEFAULT, /* X,Y */
			     w, h,
			     NULL,		/* HWND hWndParent */
			     NULL,		/* HMENU hMenu */
			     static_inst,	/* application instance */
			     NULL);		/* lpParam */

    ShowWindow(static_wh, SW_SHOW);
    UpdateWindow(static_wh);
}

#ifdef THREADS
static volatile int init_done;
static DWORD msgthread_id;

static DWORD WINAPI
msgthread(LPVOID arg)
{
    MSG msg;

    ws_init2();

    /* XXX use a mutex? */
    init_done = 1;

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
	TranslateMessage(&msg);
	DispatchMessage(&msg);
    }
    return msg.wParam;
}

static void
ws_thread_init(void)
{
    HANDLE th = CreateThread(NULL,		/* sec. attr. */
			     0,			/* stack size */
			     msgthread,
			     NULL,		/* param */
			     0,			/* flags */
			     &msgthread_id);
    CloseHandle(th);

    /* XXX use a mutex; don't wait forever!! */
    while (!init_done)
	;
}
#endif /* THREADS */

/* called from display layer on first display op */
int
ws_init(char *name, int xp, int yp, int colors)
{
    xpixels = xp;
    ypixels = yp;
    window_name = name;

#ifdef THREADS
    ws_thread_init();
#else
    ws_init2();
#endif
    return 1;				/* XXX return errors!! */
}

void *
ws_color_rgb(int r, int g, int b)
{
    /* XXX check for failure??? try GetNearestColor??? */
    return CreateSolidBrush(RGB(r/256, g/256, b/256));
}

void *
ws_color_black(void)
{
    return black_brush;
}

void *
ws_color_white(void)
{
    return white_brush;
}

void
ws_display_point(int x, int y, void *color)
{
    HDC dc;
    RECT r;
    HBRUSH brush = color;

    if (x > xpixels || y > ypixels)
	return;

    y = ypixels - 1 - y;		/* invert y, top left origin */

    /* top left corner */
    r.left = x*PIX_SIZE;
    r.top = y*PIX_SIZE;

    /* bottom right corner, non-inclusive */
    r.right = (x+1)*PIX_SIZE;
    r.bottom = (y+1)*PIX_SIZE;

    if (brush == NULL)
	brush = black_brush;

    dc = GetDC(static_wh);
    FillRect(dc, &r, brush);
    ReleaseDC(static_wh, dc);
}
  
void
ws_sync(void) {
    /* noop */
}

void
ws_beep(void) {
#if 0
    /* play SystemDefault sound; does not work over terminal service */
    MessageBeep(MB_OK);
#else
    /* works over terminal service? Plays default sound/beep on Win9x/ME */
    Beep(440, 500);			/* Hz, ms. */
#endif
}

unsigned long
os_elapsed(void)
{
    static int new;
    unsigned long ret;
    static DWORD t[2];

    /*
     * only returns milliseconds, but Sleep()
     * only takes milliseconds.
     *
     * wraps after 49.7 days of uptime.
     * DWORD is an unsigned long, so this should be OK
     */
    t[new] = GetTickCount();
    if (t[!new] == 0)
	ret = ~0L;			/* +INF */
    else
	ret = (t[new] - t[!new]) * 1000;
    new = !new;				/* Ecclesiastes III */
    return ret;
}