/*
 * KeyCursorFilter.cpp
 *
 *	Copyright 2000, Be Incorporated.   All Rights Reserved.
 *	This file may be used under the terms of the Be Sample Code License.
 */

#include "KeyCursor.h"
#include <Beep.h>
#include <InterfaceDefs.h>
#include <List.h>
#include <Message.h>
#include <OS.h>
#include <stdio.h>

//------------------------------------------------------------------------

extern "C" {

BInputServerFilter* instantiate_input_filter()
{
	return new KeyCursorFilter();
}

} /* extern "C" */

//------------------------------------------------------------------------

KeyCursorFilter::KeyCursorFilter()
{
	fPortID = -1;
	fNecessaryMods = B_OPTION_KEY;
	fButtonPressed = 0;
	fLeftPressed = false;
	fRightPressed = false;
	fUpPressed = false;
	fDownPressed = false;
	fToggleMode = true;
	fToggleOn = false;
}

KeyCursorFilter::~KeyCursorFilter()
{
}

status_t KeyCursorFilter::InitCheck()
{
	return B_OK;
}

void KeyCursorFilter::SendMessageToDevice(int32 what, int32 data)
{
	// locate the device looper if it hasn't been set yet
	if (fPortID < 0) {
		fPortID = find_port(KEY_CURSOR_DEVICE_PORT_NAME);
		if (fPortID < 0) {
			// no point in continuing if we can't send messages
			// to the device looper
			return;
		}
	}
	write_port(fPortID, what, &data, sizeof(data));
}

filter_result KeyCursorFilter::Filter(BMessage *message, BList */*outList*/)
{
	static bool toggleModPressedLast = false;
	filter_result result = B_DISPATCH_MESSAGE;
	
	switch (message->what) {
	case B_UNMAPPED_KEY_DOWN:
	case B_UNMAPPED_KEY_UP:
		// raw modifier state changes only concern us in toggle mode
		if (!fToggleMode) {
			break;
		}

		uint32 mods;
		message->FindInt32("modifiers", (int32 *)&mods);
		mods = mods & (B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY |
					   B_OPTION_KEY | B_MENU_KEY);

		if (message->what == B_UNMAPPED_KEY_DOWN) {
			toggleModPressedLast = (mods == fNecessaryMods);
		} else {
			// detect if the toggle modifier was pressed down and then
			// let up without any key events in between, and if so,
			// toggle the mode.  This is a B_UNMAPPED_KEY_UP event,
			// so the modifiers will not include the key this event
			// was generated by, but we know that if toggleModPressedLast==true,
			// releasing the toggle modifier must have been what caused this
			// event.  This code makes the assumption (probably incorrect) that
			// the only unmapped key events are generated by modifiers.
			if (toggleModPressedLast) {
				fToggleOn = !fToggleOn;
				beep();
			}
			toggleModPressedLast = false;
		}
		break;
	case B_KEY_DOWN:	// fall through
	case B_KEY_UP:
		{
			toggleModPressedLast = false;

			uchar ch;
			uint32 raw;
			uint32 mods;

			if ((message->FindInt8("byte", 0, (int8 *)&ch) != B_OK) ||
				(message->FindInt32("raw_char", 0, (int32 *)&raw) != B_OK) ||
				(message->FindInt32("modifiers", (int32 *)&mods) != B_OK))
			{
				break;
			}

			uint32 origMods = mods;
			mods = mods & (B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY |
						   B_OPTION_KEY | B_MENU_KEY);

			if (fToggleMode && fToggleOn) {
				mods = fNecessaryMods;
			}

			switch (message->what) {
			case B_KEY_DOWN:
				result = B_SKIP_MESSAGE;
	
				switch (raw) {
				case B_LEFT_ARROW:
					if (!fLeftPressed) {
						if (mods == fNecessaryMods) {
							fLeftPressed = true;
							SendMessageToDevice(LEFT_KEY_DOWN);
						} else {
							result = B_DISPATCH_MESSAGE;					
						}
					}
					break;
				case B_RIGHT_ARROW:
					if (!fRightPressed) {
						if (mods == fNecessaryMods) {
							fRightPressed = true;
							SendMessageToDevice(RIGHT_KEY_DOWN);
						} else {
							result = B_DISPATCH_MESSAGE;					
						}
					}
					break;
				case B_UP_ARROW:
					if (!fUpPressed) {
						if (mods == fNecessaryMods) {
							fUpPressed = true;
							SendMessageToDevice(UP_KEY_DOWN);
						} else {
							result = B_DISPATCH_MESSAGE;					
						}
					}
					break;
				case B_DOWN_ARROW:
					if (!fDownPressed) {
						if (mods == fNecessaryMods) {
							fDownPressed = true;
							SendMessageToDevice(DOWN_KEY_DOWN);
						} else {
							result = B_DISPATCH_MESSAGE;					
						}
					}
					break;
				case B_SPACE:	// space bar is used to click
					if (fButtonPressed == 0) {
						if (mods == fNecessaryMods) {
							// space is button 1, shift-space is button 2
							fButtonPressed = (origMods && B_SHIFT_KEY) ? 2 : 1;
							SendMessageToDevice(BUTTON_DOWN, (int32)fButtonPressed);
						} else {
							result = B_DISPATCH_MESSAGE;					
						}					
					}
					break;
				default:
					result = B_DISPATCH_MESSAGE;
					break;
				}			
				break;
			case B_KEY_UP:
				//note: we don't care about modifiers on key up
				switch (raw) {
				case B_LEFT_ARROW:
					if (fLeftPressed) {
						fLeftPressed = false;
						SendMessageToDevice(LEFT_KEY_UP);
						result = B_SKIP_MESSAGE;
					}
					break;
				case B_RIGHT_ARROW:
					if (fRightPressed) {
						fRightPressed = false;
						SendMessageToDevice(RIGHT_KEY_UP);
						result = B_SKIP_MESSAGE;
					}
					break;
				case B_UP_ARROW:
					if (fUpPressed) {
						fUpPressed = false;
						SendMessageToDevice(UP_KEY_UP);
						result = B_SKIP_MESSAGE;
					}
					break;
				case B_DOWN_ARROW:
					if (fDownPressed) {
						fDownPressed = false;
						SendMessageToDevice(DOWN_KEY_UP);
						result = B_SKIP_MESSAGE;
					}
					break;
				case B_SPACE:
					if (fButtonPressed != 0) {
						SendMessageToDevice(BUTTON_UP, fButtonPressed);
						fButtonPressed = 0;
						result = B_SKIP_MESSAGE;
					}
					break;
				}				
				break;
			}
		}
		break;
	}
	return result;
}
