/* --------- message.c ---------- */

#include "dflat.h"
#include "system.h"

static int handshaking = 0;

BOOL AllocTesting = FALSE;
jmp_buf AllocError;
BOOL AltDown = FALSE;

/* ---------- event queue ---------- */
static struct events
{
	DFMESSAGE event;
	int mx;
	int my;
} EventQueue[DF_MAXMESSAGES];

/* ---------- message queue --------- */
static struct msgs
{
	DFWINDOW wnd;
	DFMESSAGE msg;
	DF_PARAM p1;
	DF_PARAM p2;
} MsgQueue[DF_MAXMESSAGES];

static int EventQueueOnCtr;
static int EventQueueOffCtr;
static int EventQueueCtr;

static int MsgQueueOnCtr;
static int MsgQueueOffCtr;
static int MsgQueueCtr;


DFWINDOW DfCaptureMouse;
DFWINDOW DfCaptureKeyboard;
static BOOL NoChildCaptureMouse;
static BOOL NoChildCaptureKeyboard;

//static int doubletimer = -1;
//static int delaytimer  = -1;
static int clocktimer  = -1;

static DFWINDOW Cwnd;

//static char ermsg[] = "Error accessing drive x";


static void StopMsg(void)
{
	DfClearClipboard();
	DfClearDialogBoxes();
	DfRestoreCursor();
	DfUnhideCursor();
}

SHORT DfGetScreenHeight (void)
{
	return DfScreenHeight;
}

SHORT DfGetScreenWidth (void)
{
	return DfScreenWidth;
}

/* ------------ initialize the message system --------- */
BOOL DfInitialize (VOID)
{
	CONSOLE_SCREEN_BUFFER_INFO csbi;

	AllocTesting = TRUE;
	if (setjmp(AllocError) != 0)
	{
		StopMsg();
		return FALSE;
	}

	/* get input and output handles */
	DfInput = GetStdHandle (STD_INPUT_HANDLE);
	DfOutput = GetStdHandle (STD_OUTPUT_HANDLE);

	/* get screen size */
	GetConsoleScreenBufferInfo (DfOutput, &csbi);
	DfScreenHeight = (csbi.srWindow.Bottom - csbi.srWindow.Top) + 1;
	DfScreenWidth = (csbi.srWindow.Right - csbi.srWindow.Left) + 1;

	/* enable mouse events */
	SetConsoleMode (DfInput, ENABLE_MOUSE_INPUT);

	DfSaveCursor();
	DfHideCursor();

	DfCaptureMouse = NULL;
	DfCaptureKeyboard = NULL;
	NoChildCaptureMouse = FALSE;
	NoChildCaptureKeyboard = FALSE;
	MsgQueueOnCtr = 0;
	MsgQueueOffCtr = 0;
	MsgQueueCtr = 0;
	EventQueueOnCtr = 0;
	EventQueueOffCtr = 0;
	EventQueueCtr = 0;
	DfPostMessage (NULL, DFM_START, 0, 0);

	return TRUE;
}


void DfTerminate (void)
{

}

/* ----- post an event and parameters to event queue ---- */
static void PostEvent(DFMESSAGE event, int p1, int p2)
{
    if (EventQueueCtr != DF_MAXMESSAGES)    {
        EventQueue[EventQueueOnCtr].event = event;
        EventQueue[EventQueueOnCtr].mx = p1;
        EventQueue[EventQueueOnCtr].my = p2;
        if (++EventQueueOnCtr == DF_MAXMESSAGES)
            EventQueueOnCtr = 0;
        EventQueueCtr++;
    }
}

/* ------ collect mouse, clock, and keyboard events ----- */
static void collect_events(void)
{
	static int OldShiftKeys = 0;
	int sk = 0;

#ifdef TIMER_AVAILABLE
	static BOOL flipflop = FALSE;
	static char timestr[9];
	struct tm *now;
	int hr;
#endif

	HANDLE DfInput = GetStdHandle (STD_INPUT_HANDLE);
	INPUT_RECORD ir;
	DWORD dwRead;
	int c;

#ifdef TIMER_AVAILABLE
    /* -------- test for a clock event (one/second) ------- */
    if (DfTimedOut(clocktimer))
    {
        /* ----- get the current time ----- */
        time_t t = time(NULL);
        now = localtime(&t);
        hr = now->tm_hour > 12 ?
             now->tm_hour - 12 :
             now->tm_hour;
        if (hr == 0)
            hr = 12;
        sprintf(timestr, "%2d:%02d", hr, now->tm_min);
        strcpy(timestr+5, now->tm_hour > 11 ? "pm " : "am ");
        /* ------- blink the : at one-second intervals ----- */
        if (flipflop)
            *(timestr+2) = ' ';
        flipflop ^= TRUE;
        /* -------- reset the timer -------- */
        DfSetTimer(clocktimer, 1);
        /* -------- post the clock event -------- */
        PostEvent(DFM_CLOCKTICK, (DF_PARAM)timestr, 0);
    }
#endif

//	WaitForSingleObject (DfInput, INFINITE);
	ReadConsoleInput (DfInput, &ir, 1, &dwRead);

	if ((ir.EventType == KEY_EVENT) &&
	    (ir.Event.KeyEvent.bKeyDown == TRUE))
	{
		/* handle key down events */

		/* handle shift state changes */
		if (ir.Event.KeyEvent.dwControlKeyState &
		    (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
		{
			sk |= DF_ALTKEY;
		}
		if (ir.Event.KeyEvent.dwControlKeyState &
		    (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
		{
			sk |= DF_CTRLKEY;
		}
		if (ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED)
		{
			sk |= DF_LEFTSHIFT + DF_RIGHTSHIFT;
		}

		if (sk != OldShiftKeys)
		{
			OldShiftKeys = sk;
			/* the shift status changed */
			PostEvent(DFM_SHIFT_CHANGED, sk, 0);
#if 0
			if (sk & DF_ALTKEY)
				AltDown = TRUE;
			else
				AltDown = FALSE;
#endif
		}

		if (ir.Event.KeyEvent.uChar.AsciiChar == 0)
		{
			switch (ir.Event.KeyEvent.wVirtualKeyCode)
			{
				case VK_F1:
					c = DF_F1;
					break;

				case VK_F4:
					if (sk & DF_ALTKEY)
						c = DF_ALT_F4;
					else if (sk & DF_CTRLKEY)
						c = DF_CTRL_F4;
					else
						c = DF_F4;

				case VK_F10:
					c = DF_F10;
					break;

				case VK_UP:
					c = DF_UP;
					break;

				case VK_DOWN:
					c = DF_DN;
					break;

				case VK_LEFT:
					c = DF_BS;
					break;

				case VK_RIGHT:
					c = DF_FWD;
					break;

				case VK_INSERT:
					c = DF_INS;
					break;

				case VK_DELETE:
					c = DF_DEL;
					break;

				case VK_HOME:
					c = DF_HOME;
					break;

				case VK_END:
					c = DF_END;
					break;

				case VK_PRIOR:
					c = DF_PGUP;
					break;

				case VK_NEXT:
					c = DF_PGDN;
					break;

				default:
					return;
			}
		}
		else
		{
			/* special handling of SHIFT+TAB */
			if (ir.Event.KeyEvent.uChar.AsciiChar == VK_TAB &&
			    (ir.Event.KeyEvent.dwControlKeyState & SHIFT_PRESSED))
				c = DF_SHIFT_HT;
			else
				c = ir.Event.KeyEvent.uChar.AsciiChar;
		}

		PostEvent (DFM_KEYBOARD, c, sk);
	}
	else if (ir.EventType == MOUSE_EVENT)
	{
		/* handle mouse events */
		if (ir.Event.MouseEvent.dwEventFlags == MOUSE_MOVED)
		{
			PostEvent (MOUSE_MOVED,
			           ir.Event.MouseEvent.dwMousePosition.X,
			           ir.Event.MouseEvent.dwMousePosition.Y);
		}
		else if (ir.Event.MouseEvent.dwEventFlags == DOUBLE_CLICK)
		{
			if (ir.Event.MouseEvent.dwButtonState ==
			    FROM_LEFT_1ST_BUTTON_PRESSED)
			{
				PostEvent (DOUBLE_CLICK,
				           ir.Event.MouseEvent.dwMousePosition.X,
				           ir.Event.MouseEvent.dwMousePosition.Y);
			}
		}
		else if (ir.Event.MouseEvent.dwEventFlags == 0)
		{
			/* single click */
			if (ir.Event.MouseEvent.dwButtonState ==
			    FROM_LEFT_1ST_BUTTON_PRESSED)
			{
				PostEvent (DFM_LEFT_BUTTON,
				           ir.Event.MouseEvent.dwMousePosition.X,
				           ir.Event.MouseEvent.dwMousePosition.Y);
			}
			else if (ir.Event.MouseEvent.dwButtonState ==
			         RIGHTMOST_BUTTON_PRESSED)
			{
				PostEvent (DFM_RIGHT_BUTTON,
				           ir.Event.MouseEvent.dwMousePosition.X,
				           ir.Event.MouseEvent.dwMousePosition.Y);
			}
			else if (ir.Event.MouseEvent.dwButtonState == 0)
			{
				PostEvent (DFM_BUTTON_RELEASED,
						   ir.Event.MouseEvent.dwMousePosition.X,
						   ir.Event.MouseEvent.dwMousePosition.Y);
			}
		}
	}
}


/* ----- post a message and parameters to msg queue ---- */
void DfPostMessage(DFWINDOW wnd, DFMESSAGE msg, DF_PARAM p1, DF_PARAM p2)
{
	if (msg == DFM_ENDDIALOG)
	{
		msg++;
		--msg;
	}

	if (MsgQueueCtr != DF_MAXMESSAGES)
	{
		MsgQueue[MsgQueueOnCtr].wnd = wnd;
		MsgQueue[MsgQueueOnCtr].msg = msg;
		MsgQueue[MsgQueueOnCtr].p1 = p1;
		MsgQueue[MsgQueueOnCtr].p2 = p2;
		if (++MsgQueueOnCtr == DF_MAXMESSAGES)
			MsgQueueOnCtr = 0;
		MsgQueueCtr++;
	}
}

/* --------- send a message to a window ----------- */
int DfSendMessage(DFWINDOW wnd, DFMESSAGE msg, DF_PARAM p1, DF_PARAM p2)
{
    int rtn = TRUE, x, y;

#ifdef INCLUDE_LOGGING
	DfLogMessages(wnd, msg, p1, p2);
#endif
    if (wnd != NULL)
        switch (msg)    {
            case DFM_PAINT:
            case DFM_BORDER:
                /* ------- don't send these messages unless the
                    window is visible -------- */
                if (DfIsVisible(wnd))
	                rtn = (*wnd->wndproc)(wnd, msg, p1, p2);
                break;
            case DFM_RIGHT_BUTTON:
            case DFM_LEFT_BUTTON:
            case DOUBLE_CLICK:
            case DFM_BUTTON_RELEASED:
                /* --- don't send these messages unless the
                    window is visible or has captured the mouse -- */
                if (DfIsVisible(wnd) || wnd == DfCaptureMouse)
	                rtn = (*wnd->wndproc)(wnd, msg, p1, p2);
                break;
            case DFM_KEYBOARD:
            case DFM_SHIFT_CHANGED:
                /* ------- don't send these messages unless the
                    window is visible or has captured the keyboard -- */
                if (!(DfIsVisible(wnd) || wnd == DfCaptureKeyboard))
	                break;
            default:
                rtn = (*wnd->wndproc)(wnd, msg, p1, p2);
                break;
        }
    /* ----- window processor returned true or the message was sent
        to no window at all (NULL) ----- */
    if (rtn != FALSE)    {
        /* --------- process messages that a window sends to the
            system itself ---------- */
        switch (msg)    {
            case DFM_STOP:
				StopMsg();
                break;
            /* ------- clock messages --------- */
            case DFM_CAPTURE_CLOCK:
                Cwnd = wnd;
                DfSetTimer(clocktimer, 0);
                break;
            case DFM_RELEASE_CLOCK:
                Cwnd = NULL;
                DfDisableTimer(clocktimer);
                break;
            /* -------- keyboard messages ------- */
            case DFM_KEYBOARD_CURSOR:
                if (wnd == NULL)
                    DfCursor((int)p1, (int)p2);
                else if (wnd == DfInFocus)
                    DfCursor(DfGetClientLeft(wnd)+(int)p1,
                                DfGetClientTop(wnd)+(int)p2);
                break;
            case DFM_CAPTURE_KEYBOARD:
                if (p2)
                    ((DFWINDOW)p2)->PrevKeyboard=DfCaptureKeyboard;
                else
                    wnd->PrevKeyboard = DfCaptureKeyboard;
                DfCaptureKeyboard = wnd;
                NoChildCaptureKeyboard = (int)p1;
                break;
			case DFM_RELEASE_KEYBOARD:
				if (wnd != NULL)
				{
					if (DfCaptureKeyboard == wnd || (int)p1)
						DfCaptureKeyboard = wnd->PrevKeyboard;
					else
					{
						DFWINDOW twnd = DfCaptureKeyboard;
						while (twnd != NULL)
						{
							if (twnd->PrevKeyboard == wnd)
							{
								twnd->PrevKeyboard = wnd->PrevKeyboard;
								break;
							}
							twnd = twnd->PrevKeyboard;
						}
						if (twnd == NULL)
							DfCaptureKeyboard = NULL;
					}
					wnd->PrevKeyboard = NULL;
				}
				else
					DfCaptureKeyboard = NULL;
				NoChildCaptureKeyboard = FALSE;
				break;
            case DFM_CURRENT_KEYBOARD_CURSOR:
                DfCurrCursor(&x, &y);
                *(int*)p1 = x;
                *(int*)p2 = y;
                break;
            case DFM_SAVE_CURSOR:
                DfSaveCursor();
                break;
            case DFM_RESTORE_CURSOR:
                DfRestoreCursor();
                break;
            case DFM_HIDE_CURSOR:
                DfNormalCursor();
                DfHideCursor();
                break;
            case DFM_SHOW_CURSOR:
                if (p1)
                    DfSetCursorSize(100);
                else
                    DfSetCursorSize(5);
                DfUnhideCursor();
                break;

			case DFM_CAPTURE_MOUSE:
				if (p2)
					((DFWINDOW)p2)->PrevMouse = DfCaptureMouse;
				else
					wnd->PrevMouse = DfCaptureMouse;
				DfCaptureMouse = wnd;
				NoChildCaptureMouse = (int)p1;
				break;

			case DFM_RELEASE_MOUSE:
				if (wnd != NULL)
				{
					if (DfCaptureMouse == wnd || (int)p1)
						DfCaptureMouse = wnd->PrevMouse;
					else
					{
						DFWINDOW twnd = DfCaptureMouse;
						while (twnd != NULL)
						{
							if (twnd->PrevMouse == wnd)
							{
								twnd->PrevMouse = wnd->PrevMouse;
								break;
							}
							twnd = twnd->PrevMouse;
						}
						if (twnd == NULL)
							DfCaptureMouse = NULL;
					}
					wnd->PrevMouse = NULL;
				}
				else
					DfCaptureMouse = NULL;
				NoChildCaptureMouse = FALSE;
				break;

			default:
				break;
		}
	}
	return rtn;
}

static DFRECT VisibleRect(DFWINDOW wnd)
{
	DFRECT rc = DfWindowRect(wnd);
	if (!DfTestAttribute(wnd, DF_NOCLIP))
	{
		DFWINDOW pwnd = DfGetParent(wnd);
		DFRECT prc;
		prc = DfClientRect(pwnd);
		while (pwnd != NULL)
		{
			if (DfTestAttribute(pwnd, DF_NOCLIP))
				break;
			rc = DfSubRectangle(rc, prc);
			if (!DfValidRect(rc))
				break;
			if ((pwnd = DfGetParent(pwnd)) != NULL)
				prc = DfClientRect(pwnd);
		}
	}
	return rc;
}

/* ----- find window that mouse coordinates are in --- */
static DFWINDOW inWindow(DFWINDOW wnd, int x, int y)
{
	DFWINDOW Hit = NULL;
	while (wnd != NULL)	{
		if (DfIsVisible(wnd))	{
			DFWINDOW wnd1;
			DFRECT rc = VisibleRect(wnd);
			if (DfInsideRect(x, y, rc))
				Hit = wnd;
			if ((wnd1 = inWindow(DfLastWindow(wnd), x, y)) != NULL)
				Hit = wnd1;
			if (Hit != NULL)
				break;
		}
		wnd = DfPrevWindow(wnd);
	}
	return Hit;
}

static DFWINDOW MouseWindow(int x, int y)
{
	/* get the window in which a mouse event occurred */
	DFWINDOW Mwnd = inWindow(DfApplicationWindow, x, y);

	/* ---- process mouse captures ----- */
	if (DfCaptureMouse != NULL)
	{
		if (NoChildCaptureMouse ||
		    Mwnd == NULL 	||
		    !DfIsAncestor(Mwnd, DfCaptureMouse))
			Mwnd = DfCaptureMouse;
	}
	return Mwnd;
}


void DfHandshake(void)
{
	handshaking++;
	DfDispatchMessage ();
	--handshaking;
}


/* ---- dispatch messages to the message proc function ---- */
BOOL DfDispatchMessage (void)
{
	DFWINDOW Mwnd, Kwnd;

	/* -------- collect mouse and keyboard events ------- */
	collect_events();

	/* --------- dequeue and process events -------- */
	while (EventQueueCtr > 0)
	{
		struct events ev;

		ev = EventQueue[EventQueueOffCtr];
		if (++EventQueueOffCtr == DF_MAXMESSAGES)
			EventQueueOffCtr = 0;
		--EventQueueCtr;

		/* get the window in which a keyboard event occurred */
		Kwnd = DfInFocus;

		/* process keyboard captures */
		if (DfCaptureKeyboard != NULL)
		{
			if (Kwnd == NULL ||
			    NoChildCaptureKeyboard ||
			    !DfIsAncestor(Kwnd, DfCaptureKeyboard))
				Kwnd = DfCaptureKeyboard;
		}

		/* send mouse and keyboard messages to the
		   window that should get them */
		switch (ev.event)
		{
			case DFM_SHIFT_CHANGED:
			case DFM_KEYBOARD:
				if (!handshaking)
					DfSendMessage(Kwnd, ev.event, ev.mx, ev.my);
				break;

			case DFM_LEFT_BUTTON:
				if (!handshaking)
				{
					Mwnd = MouseWindow(ev.mx, ev.my);
					if (!DfCaptureMouse ||
					    (!NoChildCaptureMouse &&
					     DfIsAncestor(Mwnd, DfCaptureMouse)))
					{
						if (Mwnd != DfInFocus)
							DfSendMessage(Mwnd, DFM_SETFOCUS, TRUE, 0);
						DfSendMessage(Mwnd, DFM_LEFT_BUTTON, ev.mx, ev.my);
					}
				}
				break;

			case DFM_BUTTON_RELEASED:
			case DOUBLE_CLICK:
			case DFM_RIGHT_BUTTON:
				if (handshaking)
					break;

			case MOUSE_MOVED:
				Mwnd = MouseWindow(ev.mx, ev.my);
				DfSendMessage(Mwnd, ev.event, ev.mx, ev.my);
				break;

			case DFM_CLOCKTICK:
				DfSendMessage(Cwnd, ev.event, ev.mx, ev.my);
				break;

			default:
				break;
		}
	}

	/* ------ dequeue and process messages ----- */
	while (MsgQueueCtr > 0)
	{
		struct msgs mq;

		mq = MsgQueue[MsgQueueOffCtr];

		if (++MsgQueueOffCtr == DF_MAXMESSAGES)
			MsgQueueOffCtr = 0;
		--MsgQueueCtr;

		DfSendMessage (mq.wnd, mq.msg, mq.p1, mq.p2);
		if (mq.msg == DFM_ENDDIALOG)
			return FALSE;

		if (mq.msg == DFM_STOP)
			return FALSE;
	}

	return TRUE;
}

/* EOF */
