/*
 ** i_input.mm
 **
 **---------------------------------------------------------------------------
 ** Copyright 2012-2015 Alexey Lysiuk
 ** All rights reserved.
 **
 ** Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions
 ** are met:
 **
 ** 1. Redistributions of source code must retain the above copyright
 **    notice, this list of conditions and the following disclaimer.
 ** 2. Redistributions in binary form must reproduce the above copyright
 **    notice, this list of conditions and the following disclaimer in the
 **    documentation and/or other materials provided with the distribution.
 ** 3. The name of the author may not be used to endorse or promote products
 **    derived from this software without specific prior written permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **---------------------------------------------------------------------------
 **
 */

#include "i_common.h"

#import <Carbon/Carbon.h>

#include "c_console.h"
#include "c_cvars.h"
#include "c_dispatch.h"
#include "d_event.h"
#include "d_gui.h"
#include "dikeys.h"
#include "doomdef.h"
#include "doomstat.h"
#include "v_video.h"
#include "events.h"
#include "g_game.h"
#include "g_levellocals.h"


EXTERN_CVAR(Int, m_use_mouse)

CVAR(Bool, use_mouse,    true,  CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Bool, m_noprescale, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
CVAR(Bool, m_filter,     false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)

CVAR(Bool, k_allowfullscreentoggle, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)

CUSTOM_CVAR(Int, mouse_capturemode, 1, CVAR_GLOBALCONFIG | CVAR_ARCHIVE)
{
	if (self < 0)
	{
		self = 0;
	}
	else if (self > 2)
	{
		self = 2;
	}
}


extern int paused, chatmodeon;
extern constate_e ConsoleState;

bool GUICapture;


namespace
{

// TODO: remove this magic!
size_t s_skipMouseMoves;


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


void CheckGUICapture()
{
	bool wantCapture = (MENU_Off == menuactive)
		? (c_down == ConsoleState || c_falling == ConsoleState || chatmodeon)
		: (MENU_On == menuactive || MENU_OnNoPause == menuactive);

	// [ZZ] check active event handlers that want the UI processing
	if (!wantCapture && primaryLevel->localEventManager->CheckUiProcessors())
	{
		wantCapture = true;
	}

	if (wantCapture != GUICapture)
	{
		GUICapture = wantCapture;

		ResetButtonStates();
	}
}

void SetCursorPosition(const NSPoint position)
{
	NSWindow* window = [NSApp keyWindow];
	if (nil == window)
	{
		return;
	}

	const NSRect  displayRect = [[window screen] frame];
	const CGPoint eventPoint = CGPointMake(position.x, displayRect.size.height - position.y);

	CGEventSourceRef eventSource = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);

	if (NULL != eventSource)
	{
		CGEventRef mouseMoveEvent = CGEventCreateMouseEvent(eventSource,
			kCGEventMouseMoved, eventPoint, kCGMouseButtonLeft);

		if (NULL != mouseMoveEvent)
		{
			CGEventPost(kCGHIDEventTap, mouseMoveEvent);
			CFRelease(mouseMoveEvent);
		}

		CFRelease(eventSource);
	}

	// TODO: remove this magic!
	s_skipMouseMoves = 2;
}

void CenterCursor()
{
	NSWindow* window = [NSApp keyWindow];
	if (nil == window)
	{
		return;
	}

	const NSRect  displayRect = [[window screen] frame];
	const NSRect   windowRect = [window frame];
	const NSPoint centerPoint = { NSMidX(windowRect), NSMidY(windowRect) };

	SetCursorPosition(centerPoint);
}

bool IsInGame()
{
	switch (mouse_capturemode)
	{
		default:
		case 0:
			return gamestate == GS_LEVEL;

		case 1:
			return gamestate == GS_LEVEL
				|| gamestate == GS_INTERMISSION
				|| gamestate == GS_FINALE;

		case 2:
			return true;
	}
}

void CheckNativeMouse()
{
	const bool windowed = (NULL == screen) || !screen->IsFullscreen();
	bool wantNative;

	if (windowed)
	{
		if (![NSApp isActive] || !use_mouse)
		{
			wantNative = true;
		}
		else if (MENU_WaitKey == menuactive)
		{
			wantNative = false;
		}
		else
		{
			wantNative = (!m_use_mouse || MENU_WaitKey != menuactive)
				&& (!IsInGame() || GUICapture || paused || demoplayback);
		}
	}
	else
	{
		// ungrab mouse when in the menu with mouse control on.
		wantNative = m_use_mouse
			&& (MENU_On == menuactive || MENU_OnNoPause == menuactive);
	}

	if (!wantNative && primaryLevel->localEventManager->CheckRequireMouse())
		wantNative = true;

	I_SetNativeMouse(wantNative);
}

} // unnamed namespace


void I_GetEvent()
{
	[[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode];
}

void I_StartTic()
{
	CheckGUICapture();
	CheckNativeMouse();

	I_ProcessJoysticks();
	I_GetEvent();
}

void I_StartFrame()
{
}


void I_SetMouseCapture()
{
}

void I_ReleaseMouseCapture()
{
}

void I_SetNativeMouse(bool wantNative)
{
	static bool nativeMouse = true;
	static NSPoint mouseLocation;

	if (wantNative != nativeMouse)
	{
		nativeMouse = wantNative;

		if (!wantNative)
		{
			mouseLocation = [NSEvent mouseLocation];
			CenterCursor();
		}

		CGAssociateMouseAndMouseCursorPosition(wantNative);

		if (wantNative)
		{
			SetCursorPosition(mouseLocation);

			[NSCursor unhide];
		}
		else
		{
			[NSCursor hide];
		}
	}
}


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


namespace
{

const size_t KEY_COUNT = 128;


// See Carbon -> HIToolbox -> Events.h for kVK_ constants

const uint8_t KEYCODE_TO_DIK[KEY_COUNT] =
{
	DIK_A,        DIK_S,             DIK_D,         DIK_F,        DIK_H,           DIK_G,       DIK_Z,        DIK_X,          // 0x00 - 0x07
	DIK_C,        DIK_V,             0,             DIK_B,        DIK_Q,           DIK_W,       DIK_E,        DIK_R,          // 0x08 - 0x0F
	DIK_Y,        DIK_T,             DIK_1,         DIK_2,        DIK_3,           DIK_4,       DIK_6,        DIK_5,          // 0x10 - 0x17
	DIK_EQUALS,   DIK_9,             DIK_7,         DIK_MINUS,    DIK_8,           DIK_0,       DIK_RBRACKET, DIK_O,          // 0x18 - 0x1F
	DIK_U,        DIK_LBRACKET,      DIK_I,         DIK_P,        DIK_RETURN,      DIK_L,       DIK_J,        DIK_APOSTROPHE, // 0x20 - 0x27
	DIK_K,        DIK_SEMICOLON,     DIK_BACKSLASH, DIK_COMMA,    DIK_SLASH,       DIK_N,       DIK_M,        DIK_PERIOD,     // 0x28 - 0x2F
	DIK_TAB,      DIK_SPACE,         DIK_GRAVE,     DIK_BACK,     0,               DIK_ESCAPE,  0,            DIK_LWIN,       // 0x30 - 0x37
	DIK_LSHIFT,   DIK_CAPITAL,       DIK_LMENU,     DIK_LCONTROL, DIK_RSHIFT,      DIK_RMENU,   DIK_RCONTROL, 0,              // 0x38 - 0x3F
	0,            DIK_DECIMAL,       0,             DIK_MULTIPLY, 0,               DIK_ADD,     0,            0,              // 0x40 - 0x47
	DIK_VOLUMEUP, DIK_VOLUMEDOWN,    DIK_MUTE,      DIK_SLASH,    DIK_NUMPADENTER, 0,           DIK_SUBTRACT, 0,              // 0x48 - 0x4F
	0,            DIK_NUMPAD_EQUALS, DIK_NUMPAD0,   DIK_NUMPAD1,  DIK_NUMPAD2,     DIK_NUMPAD3, DIK_NUMPAD4,  DIK_NUMPAD5,    // 0x50 - 0x57
	DIK_NUMPAD6,  DIK_NUMPAD7,       0,             DIK_NUMPAD8,  DIK_NUMPAD9,     0,           0,            0,              // 0x58 - 0x5F
	DIK_F5,       DIK_F6,            DIK_F7,        DIK_F3,       DIK_F8,          DIK_F9,      0,            DIK_F11,        // 0x60 - 0x67
	0,            DIK_F13,           0,             DIK_F14,      0,               DIK_F10,     0,            DIK_F12,        // 0x68 - 0x6F
	0,            DIK_F15,           0,             DIK_HOME,     0,               DIK_DELETE,  DIK_F4,       DIK_END,        // 0x70 - 0x77
	DIK_F2,       0,                 DIK_F1,        DIK_LEFT,     DIK_RIGHT,       DIK_DOWN,    DIK_UP,       0,              // 0x78 - 0x7F
};

const uint8_t KEYCODE_TO_ASCII[KEY_COUNT] =
{
	'a', 's',  'd', 'f', 'h', 'g', 'z',  'x', // 0x00 - 0x07
	'c', 'v',    0, 'b', 'q', 'w', 'e',  'r', // 0x08 - 0x0F
	'y', 't',  '1', '2', '3', '4', '6',  '5', // 0x10 - 0x17
	'=', '9',  '7', '-', '8', '0', ']',  'o', // 0x18 - 0x1F
	'u', '[',  'i', 'p',  13, 'l', 'j', '\'', // 0x20 - 0x27
	'k', ';', '\\', ',', '/', 'n', 'm',  '.', // 0x28 - 0x2F
	  9, ' ',  '`',  12,   0,  27,   0,    0, // 0x30 - 0x37
	  0,   0,    0,   0,   0,   0,   0,    0, // 0x38 - 0x3F
	  0,   0,    0,   0,   0,   0,   0,    0, // 0x40 - 0x47
	  0,   0,    0,   0,   0,   0,   0,    0, // 0x48 - 0x4F
	  0,   0,    0,   0,   0,   0,   0,    0, // 0x50 - 0x57
	  0,   0,    0,   0,   0,   0,   0,    0, // 0x58 - 0x5F
	  0,   0,    0,   0,   0,   0,   0,    0, // 0x60 - 0x67
	  0,   0,    0,   0,   0,   0,   0,    0, // 0x68 - 0x6F
	  0,   0,    0,   0,   0,   0,   0,    0, // 0x70 - 0x77
	  0,   0,    0,   0,   0,   0,   0,    0, // 0x78 - 0x7F
};


uint8_t ModifierToDIK(const uint32_t modifier)
{
	switch (modifier)
	{
		case NSAlphaShiftKeyMask: return DIK_CAPITAL;
		case NSShiftKeyMask:      return DIK_LSHIFT;
		case NSControlKeyMask:    return DIK_LCONTROL;
		case NSAlternateKeyMask:  return DIK_LMENU;
		case NSCommandKeyMask:    return DIK_LWIN;
	}

	return 0;
}

int16_t ModifierFlagsToGUIKeyModifiers(NSEvent* theEvent)
{
	const NSUInteger modifiers([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask);
	return ((modifiers & NSShiftKeyMask    ) ? GKM_SHIFT : 0)
		 | ((modifiers & NSControlKeyMask  ) ? GKM_CTRL  : 0)
		 | ((modifiers & NSAlternateKeyMask) ? GKM_ALT   : 0)
		 | ((modifiers & NSCommandKeyMask  ) ? GKM_META  : 0);
}

bool ShouldGenerateGUICharEvent(NSEvent* theEvent)
{
	const NSUInteger modifiers([theEvent modifierFlags] & NSDeviceIndependentModifierFlagsMask);
	return !(modifiers & NSControlKeyMask)
		&& !(modifiers & NSAlternateKeyMask)
		&& !(modifiers & NSCommandKeyMask)
		&& !(modifiers & NSFunctionKeyMask);
}


NSStringEncoding GetEncodingForUnicodeCharacter(const unichar character)
{
	if (character >= L'\u0100' && character <= L'\u024F')
	{
		return NSWindowsCP1250StringEncoding; // Central and Eastern Europe
	}
	else if (character >= L'\u0370' && character <= L'\u03FF')
	{
		return NSWindowsCP1253StringEncoding; // Greek
	}
	else if (character >= L'\u0400' && character <= L'\u04FF')
	{
		return NSWindowsCP1251StringEncoding; // Cyrillic
	}

	// TODO: add handling for other characters
	// TODO: Turkish should use NSWindowsCP1254StringEncoding

	return NSWindowsCP1252StringEncoding;
}

unsigned char GetCharacterFromNSEvent(NSEvent* theEvent, unichar *realchar)
{
	const NSString* unicodeCharacters = [theEvent characters];

	if (0 == [unicodeCharacters length])
	{
		return '\0';
	}

	const unichar unicodeCharacter = [unicodeCharacters characterAtIndex:0];
	const NSStringEncoding encoding = GetEncodingForUnicodeCharacter(unicodeCharacter);

	unsigned char character = '\0';

	if (NSWindowsCP1252StringEncoding == encoding)
	{
		// TODO: make sure that the following is always correct
		character = unicodeCharacter & 0xFF;
	}
	else
	{
		const NSData* const characters =
			[[theEvent characters] dataUsingEncoding:encoding];

		character = [characters length] > 0
			? *static_cast<const unsigned char*>([characters bytes])
			: '\0';
	}

	*realchar = unicodeCharacter;
	return character;
}

void ProcessKeyboardEventInMenu(NSEvent* theEvent)
{
	event_t event = {};

	unichar realchar;
	event.type    = EV_GUI_Event;
	event.subtype = NSKeyDown == [theEvent type] ? EV_GUI_KeyDown : EV_GUI_KeyUp;
	event.data2   = GetCharacterFromNSEvent(theEvent, &realchar);
	event.data3   = ModifierFlagsToGUIKeyModifiers(theEvent);

	if (EV_GUI_KeyDown == event.subtype && [theEvent isARepeat])
	{
		event.subtype = EV_GUI_KeyRepeat;
	}

	const unsigned short keyCode = [theEvent keyCode];

	switch (keyCode)
	{
		case kVK_Return:        event.data1 = GK_RETURN;    break;
		case kVK_PageUp:        event.data1 = GK_PGUP;      break;
		case kVK_PageDown:      event.data1 = GK_PGDN;      break;
		case kVK_End:           event.data1 = GK_END;       break;
		case kVK_Home:          event.data1 = GK_HOME;      break;
		case kVK_LeftArrow:     event.data1 = GK_LEFT;      break;
		case kVK_RightArrow:    event.data1 = GK_RIGHT;     break;
		case kVK_UpArrow:       event.data1 = GK_UP;        break;
		case kVK_DownArrow:     event.data1 = GK_DOWN;      break;
		case kVK_Delete:        event.data1 = GK_BACKSPACE; break;
		case kVK_ForwardDelete: event.data1 = GK_DEL;       break;
		case kVK_Escape:        event.data1 = GK_ESCAPE;    break;
		case kVK_F1:            event.data1 = GK_F1;        break;
		case kVK_F2:            event.data1 = GK_F2;        break;
		case kVK_F3:            event.data1 = GK_F3;        break;
		case kVK_F4:            event.data1 = GK_F4;        break;
		case kVK_F5:            event.data1 = GK_F5;        break;
		case kVK_F6:            event.data1 = GK_F6;        break;
		case kVK_F7:            event.data1 = GK_F7;        break;
		case kVK_F8:            event.data1 = GK_F8;        break;
		case kVK_F9:            event.data1 = GK_F9;        break;
		case kVK_F10:           event.data1 = GK_F10;       break;
		case kVK_F11:           event.data1 = GK_F11;       break;
		case kVK_F12:           event.data1 = GK_F12;       break;
		default:
			event.data1 = KEYCODE_TO_ASCII[keyCode];
			break;
	}

	if (event.data1 < 128)
	{
		event.data1 = toupper(event.data1);

		D_PostEvent(&event);
	}

	if (!iscntrl(event.data2)
		&& EV_GUI_KeyUp != event.subtype
		&& ShouldGenerateGUICharEvent(theEvent))
	{
		event.subtype = EV_GUI_Char;
		event.data1   = realchar;
		event.data2   = event.data3 & GKM_ALT;
		
		D_PostEvent(&event);
	}
}


void NSEventToGameMousePosition(NSEvent* inEvent, event_t* outEvent)
{
	const NSWindow* window = [inEvent window];
	const NSView*     view = [window contentView];

	const NSPoint screenPos = [NSEvent mouseLocation];
	const NSRect screenRect = NSMakeRect(screenPos.x, screenPos.y, 0, 0);
	const NSRect windowRect = [window convertRectFromScreen:screenRect];
	const NSPoint   viewPos = [view convertPointToBacking:windowRect.origin];
	const CGFloat frameHeight = I_GetContentViewSize(window).height;

	outEvent->data1 = static_cast<int16_t>(              viewPos.x);
	outEvent->data2 = static_cast<int16_t>(frameHeight - viewPos.y);

	screen->ScaleCoordsFromWindow(outEvent->data1, outEvent->data2);
}

void ProcessMouseMoveInMenu(NSEvent* theEvent)
{
	event_t event = {};

	event.type    = EV_GUI_Event;
	event.subtype = EV_GUI_MouseMove;
	event.data3   = ModifierFlagsToGUIKeyModifiers(theEvent);

	NSEventToGameMousePosition(theEvent, &event);

	D_PostEvent(&event);
}

void ProcessMouseMoveInGame(NSEvent* theEvent)
{
	int x([theEvent deltaX]);
	int y(-[theEvent deltaY]);

	if (0 == x && 0 == y)
	{
		return;
	}

	if (!m_noprescale)
	{
		x *= 3;
		y *= 2;
	}

	event_t event = {};

	static int lastX = 0, lastY = 0;

	if (m_filter)
	{
		event.x = (x + lastX) / 2;
		event.y = (y + lastY) / 2;
	}
	else
	{
		event.x = x;
		event.y = y;
	}

	lastX = x;
	lastY = y;

	if (0 != event.x || 0 != event.y)
	{
		event.type = EV_Mouse;
		
		D_PostEvent(&event);
	}
}


void ProcessKeyboardEvent(NSEvent* theEvent)
{
	const unsigned short keyCode = [theEvent keyCode];
	if (keyCode >= KEY_COUNT)
	{
		assert(!"Unknown keycode");
		return;
	}

	const bool isARepeat = [theEvent isARepeat];

	if (k_allowfullscreentoggle
		&& (kVK_ANSI_F == keyCode)
		&& (NSCommandKeyMask & [theEvent modifierFlags])
		&& (NSKeyDown == [theEvent type])
		&& !isARepeat)
	{
		ToggleFullscreen = !ToggleFullscreen;
		return;
	}

	if (GUICapture)
	{
		ProcessKeyboardEventInMenu(theEvent);
	}
	else if (!isARepeat)
	{
		event_t event = {};

		event.type  = NSKeyDown == [theEvent type] ? EV_KeyDown : EV_KeyUp;
		event.data1 = KEYCODE_TO_DIK[ keyCode ];

		if (0 != event.data1)
		{
			event.data2 = KEYCODE_TO_ASCII[ keyCode ];

			D_PostEvent(&event);
		}
	}
}

void ProcessKeyboardFlagsEvent(NSEvent* theEvent)
{
	if (GUICapture)
	{
		// Ignore events from modifier keys in menu/console/chat
		return;
	}

	static const uint32_t FLAGS_MASK =
		NSDeviceIndependentModifierFlagsMask & ~NSNumericPadKeyMask;

	const  uint32_t      modifiers = [theEvent modifierFlags] & FLAGS_MASK;
	static uint32_t   oldModifiers = 0;
	const  uint32_t deltaModifiers = modifiers ^ oldModifiers;

	if (0 == deltaModifiers)
	{
		return;
	}

	event_t event = {};
	event.type  = modifiers > oldModifiers ? EV_KeyDown : EV_KeyUp;
	event.data1 = ModifierToDIK(deltaModifiers);

	oldModifiers = modifiers;

	if (DIK_CAPITAL == event.data1)
	{
		// Caps Lock is a modifier key which generates one event per state change
		// but not per actual key press or release. So treat any event as key down
		event.type = EV_KeyDown;
	}

	D_PostEvent(&event);
}


void ProcessMouseMoveEvent(NSEvent* theEvent)
{
	if (!use_mouse)
	{
		return;
	}

	if (s_skipMouseMoves > 0)
	{
		--s_skipMouseMoves;
		return;
	}

	if (GUICapture)
	{
		ProcessMouseMoveInMenu(theEvent);
	}
	else
	{
		ProcessMouseMoveInGame(theEvent);
	}
}

void ProcessMouseButtonEvent(NSEvent* theEvent)
{
	if (!use_mouse)
	{
		return;
	}

	event_t event = {};

	const NSEventType cocoaEventType = [theEvent type];

	if (GUICapture)
	{
		event.type  = EV_GUI_Event;
		event.data3 = ModifierFlagsToGUIKeyModifiers(theEvent);

		switch (cocoaEventType)
		{
			case NSLeftMouseDown:  event.subtype = EV_GUI_LButtonDown; break;
			case NSRightMouseDown: event.subtype = EV_GUI_RButtonDown; break;
			case NSOtherMouseDown: event.subtype = EV_GUI_MButtonDown; break;
			case NSLeftMouseUp:    event.subtype = EV_GUI_LButtonUp;   break;
			case NSRightMouseUp:   event.subtype = EV_GUI_RButtonUp;   break;
			case NSOtherMouseUp:   event.subtype = EV_GUI_MButtonUp;   break;
			default: break;
		}

		NSEventToGameMousePosition(theEvent, &event);

		D_PostEvent(&event);
	}
	else
	{
		switch (cocoaEventType)
		{
			case NSLeftMouseDown:
			case NSRightMouseDown:
			case NSOtherMouseDown:
				event.type = EV_KeyDown;
				break;

			case NSLeftMouseUp:
			case NSRightMouseUp:
			case NSOtherMouseUp:
				event.type = EV_KeyUp;
				break;

			default:
				break;
		}

		event.data1 = MIN(KEY_MOUSE1 + [theEvent buttonNumber], NSInteger(KEY_MOUSE8));

		D_PostEvent(&event);
	}
}

void ProcessMouseWheelEvent(NSEvent* theEvent)
{
	if (!use_mouse)
	{
		return;
	}

	const int16_t modifiers = ModifierFlagsToGUIKeyModifiers(theEvent);
	const CGFloat delta   = (modifiers & GKM_SHIFT)
		? [theEvent deltaX]
		: [theEvent deltaY];
	const bool isZeroDelta = fabs(delta) < 1.0E-5;

	if (isZeroDelta && GUICapture)
	{
		return;
	}
	
	event_t event = {};
	
	if (GUICapture)
	{
		event.type    = EV_GUI_Event;
		event.subtype = delta > 0.0f ? EV_GUI_WheelUp : EV_GUI_WheelDown;
		event.data3   = modifiers;
	}
	else
	{
		event.type  = isZeroDelta  ? EV_KeyUp     : EV_KeyDown;
		event.data1 = delta > 0.0f ? KEY_MWHEELUP : KEY_MWHEELDOWN;
	}
	
	D_PostEvent(&event);
}

} // unnamed namespace


void I_ProcessEvent(NSEvent* event)
{
	const NSEventType eventType = [event type];

	switch (eventType)
	{
		case NSMouseMoved:
			ProcessMouseMoveEvent(event);
			break;

		case NSLeftMouseDown:
		case NSLeftMouseUp:
		case NSRightMouseDown:
		case NSRightMouseUp:
		case NSOtherMouseDown:
		case NSOtherMouseUp:
			ProcessMouseButtonEvent(event);
			break;

		case NSLeftMouseDragged:
		case NSRightMouseDragged:
		case NSOtherMouseDragged:
			ProcessMouseButtonEvent(event);
			ProcessMouseMoveEvent(event);
			break;

		case NSScrollWheel:
			ProcessMouseWheelEvent(event);
			break;

		case NSKeyDown:
		case NSKeyUp:
			ProcessKeyboardEvent(event);
			break;

		case NSFlagsChanged:
			ProcessKeyboardFlagsEvent(event);
			break;

		default:
			break;
	}
}
