/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * FreeRDP Mac OS X Server (Input)
 *
 * Copyright 2013 Corey Clayton <can.of.tuna@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <ApplicationServices/ApplicationServices.h>
#include <Carbon/Carbon.h>

#include <winpr/windows.h>

#include "mf_input.h"
#include "mf_info.h"

#include <freerdp/log.h>
#define TAG SERVER_TAG("mac")

static const CGKeyCode keymap[256] = {
	0xFF,                    // 0x0
	kVK_Escape,              // 0x1
	kVK_ANSI_1,              // 0x2
	kVK_ANSI_2,              // 0x3
	kVK_ANSI_3,              // 0x4
	kVK_ANSI_4,              // 0x5
	kVK_ANSI_5,              // 0x6
	kVK_ANSI_6,              // 0x7
	kVK_ANSI_7,              // 0x8
	kVK_ANSI_8,              // 0x9
	kVK_ANSI_9,              // 0xa
	kVK_ANSI_0,              // 0xb
	kVK_ANSI_Minus,          // 0xc
	kVK_ANSI_Equal,          // 0xd
	kVK_Delete,              // 0xe
	kVK_Tab,                 // 0xf
	kVK_ANSI_Q,              // 0x10
	kVK_ANSI_W,              // 0x11
	kVK_ANSI_E,              // 0x12
	kVK_ANSI_R,              // 0x13
	kVK_ANSI_T,              // 0x14
	kVK_ANSI_Y,              // 0x15
	kVK_ANSI_U,              // 0x16
	kVK_ANSI_I,              // 0x17
	kVK_ANSI_O,              // 0x18
	kVK_ANSI_P,              // 0x19
	kVK_ANSI_LeftBracket,    // 0x1a
	kVK_ANSI_RightBracket,   // 0x1b
	kVK_Return,              // 0x1c
	kVK_Control,             // 0x1d
	kVK_ANSI_A,              // 0x1e
	kVK_ANSI_S,              // 0x1f
	kVK_ANSI_D,              // 0x20
	kVK_ANSI_F,              // 0x21
	kVK_ANSI_G,              // 0x22
	kVK_ANSI_H,              // 0x23
	kVK_ANSI_J,              // 0x24
	kVK_ANSI_K,              // 0x25
	kVK_ANSI_L,              // 0x26
	kVK_ANSI_Semicolon,      // 0x27
	kVK_ANSI_Quote,          // 0x28
	kVK_ANSI_Grave,          // 0x29
	kVK_Shift,               // 0x2a
	kVK_ANSI_Backslash,      // 0x2b
	kVK_ANSI_Z,              // 0x2c
	kVK_ANSI_X,              // 0x2d
	kVK_ANSI_C,              // 0x2e
	kVK_ANSI_V,              // 0x2f
	kVK_ANSI_B,              // 0x30
	kVK_ANSI_N,              // 0x31
	kVK_ANSI_M,              // 0x32
	kVK_ANSI_Comma,          // 0x33
	kVK_ANSI_Period,         // 0x34
	kVK_ANSI_Slash,          // 0x35
	kVK_Shift,               // 0x36
	kVK_ANSI_KeypadMultiply, // 0x37
	kVK_Option,              // 0x38
	kVK_Space,               // 0x39
	kVK_CapsLock,            // 0x3a
	kVK_F1,                  // 0x3b
	kVK_F2,                  // 0x3c
	kVK_F3,                  // 0x3d
	kVK_F4,                  // 0x3e
	kVK_F5,                  // 0x3f
	kVK_F6,                  // 0x40
	kVK_F7,                  // 0x41
	kVK_F8,                  // 0x42
	kVK_F9,                  // 0x43
	kVK_F10,                 // 0x44
	0xFF,                    // 0x45 -- numlock
	0xFF,                    // 0x46 -- scroll lock
	kVK_ANSI_Keypad7,        // 0x47
	kVK_ANSI_Keypad8,        // 0x48
	kVK_ANSI_Keypad9,        // 0x49
	kVK_ANSI_KeypadMinus,    // 0x4a
	kVK_ANSI_Keypad4,        // 0x4b
	kVK_ANSI_Keypad5,        // 0x4c
	kVK_ANSI_Keypad6,        // 0x4d
	kVK_ANSI_KeypadPlus,     // 0x4e
	kVK_ANSI_Keypad1,        // 0x4f
	kVK_ANSI_Keypad2,        // 0x50
	kVK_ANSI_Keypad3,        // 0x51
	kVK_ANSI_Keypad0,        // 0x52
	kVK_ANSI_KeypadDecimal,  // 0x53
	0xFF,                    // 0x54
	0xFF,                    // 0x55
	0xFF,                    // 0x56
	kVK_F11,                 // 0x57
	kVK_F12,                 // 0x58
	0xFF,                    // 0x59 -- pause
	0xFF,                    // 0x5a
	kVK_Control,             // 0x5b
	kVK_Control,             // 0x5c
	0xFF,                    // 0x5d -- application
	0xFF,                    // 0x5e -- power
	0xFF,                    // 0x5f -- sleep
	0xFF,                    // 0x60
	0xFF,                    // 0x61
	0xFF,                    // 0x62
	0xFF,                    // 0x63 -- wake
	0xFF,                    // 0x64
	0xFF,                    // 0x65
	0xFF,                    // 0x66
	0xFF,                    // 0x67
	0xFF,                    // 0x68
	0xFF,                    // 0x69
	0xFF,                    // 0x6a
	0xFF,                    // 0x6b
	0xFF,                    // 0x6c
	0xFF,                    // 0x6d
	0xFF,                    // 0x6e
	0xFF,                    // 0x6f
	0xFF,                    // 0x70
	0xFF,                    // 0x71
	0xFF,                    // 0x72
	0xFF,                    // 0x73
	0xFF,                    // 0x74
	0xFF,                    // 0x75
	0xFF,                    // 0x76
	0xFF,                    // 0x77
	0xFF,                    // 0x78
	0xFF,                    // 0x79
	0xFF,                    // 0x7a
	0xFF,                    // 0x7b
	0xFF,                    // 0x7c
	0xFF,                    // 0x7d
	0xFF,                    // 0x7e
	0xFF,                    // 0x7f
	0xFF,                    // 0x80
	0xFF,                    // 0x81
	0xFF,                    // 0x82
	0xFF,                    // 0x83
	0xFF,                    // 0x84
	0xFF,                    // 0x85
	0xFF,                    // 0x86
	0xFF,                    // 0x87
	0xFF,                    // 0x88
	0xFF,                    // 0x89
	0xFF,                    // 0x8a
	0xFF,                    // 0x8b
	0xFF,                    // 0x8c
	0xFF,                    // 0x8d
	0xFF,                    // 0x8e
	0xFF,                    // 0x8f
	0xFF,                    // 0x90
	0xFF,                    // 0x91
	0xFF,                    // 0x92
	0xFF,                    // 0x93
	0xFF,                    // 0x94
	0xFF,                    // 0x95
	0xFF,                    // 0x96
	0xFF,                    // 0x97
	0xFF,                    // 0x98
	0xFF,                    // 0x99
	0xFF,                    // 0x9a
	0xFF,                    // 0x9b
	0xFF,                    // 0x9c
	0xFF,                    // 0x9d
	0xFF,                    // 0x9e
	0xFF,                    // 0x9f
	0xFF,                    // 0xa0
	0xFF,                    // 0xa1
	0xFF,                    // 0xa2
	0xFF,                    // 0xa3
	0xFF,                    // 0xa4
	0xFF,                    // 0xa5
	0xFF,                    // 0xa6
	0xFF,                    // 0xa7
	0xFF,                    // 0xa8
	0xFF,                    // 0xa9
	0xFF,                    // 0xaa
	0xFF,                    // 0xab
	0xFF,                    // 0xac
	0xFF,                    // 0xad
	0xFF,                    // 0xae
	0xFF,                    // 0xaf
	0xFF,                    // 0xb0
	0xFF,                    // 0xb1
	0xFF,                    // 0xb2
	0xFF,                    // 0xb3
	0xFF,                    // 0xb4
	0xFF,                    // 0xb5
	0xFF,                    // 0xb6
	0xFF,                    // 0xb7
	0xFF,                    // 0xb8
	0xFF,                    // 0xb9
	0xFF,                    // 0xba
	0xFF,                    // 0xbb
	0xFF,                    // 0xbc
	0xFF,                    // 0xbd
	0xFF,                    // 0xbe
	0xFF,                    // 0xbf
	0xFF,                    // 0xc0
	0xFF,                    // 0xc1
	0xFF,                    // 0xc2
	0xFF,                    // 0xc3
	0xFF,                    // 0xc4
	0xFF,                    // 0xc5
	0xFF,                    // 0xc6
	0xFF,                    // 0xc7
	0xFF,                    // 0xc8
	0xFF,                    // 0xc9
	0xFF,                    // 0xca
	0xFF,                    // 0xcb
	0xFF,                    // 0xcc
	0xFF,                    // 0xcd
	0xFF,                    // 0xce
	0xFF,                    // 0xcf
	0xFF,                    // 0xd0
	0xFF,                    // 0xd1
	0xFF,                    // 0xd2
	0xFF,                    // 0xd3
	0xFF,                    // 0xd4
	0xFF,                    // 0xd5
	0xFF,                    // 0xd6
	0xFF,                    // 0xd7
	0xFF,                    // 0xd8
	0xFF,                    // 0xd9
	0xFF,                    // 0xda
	0xFF,                    // 0xdb
	0xFF,                    // 0xdc
	0xFF,                    // 0xdd
	0xFF,                    // 0xde
	0xFF,                    // 0xdf
	0xFF,                    // 0xe0
	0xFF,                    // 0xe1
	0xFF,                    // 0xe2
	0xFF,                    // 0xe3
	0xFF,                    // 0xe4
	0xFF,                    // 0xe5
	0xFF,                    // 0xe6
	0xFF,                    // 0xe7
	0xFF,                    // 0xe8
	0xFF,                    // 0xe9
	0xFF,                    // 0xea
	0xFF,                    // 0xeb
	0xFF,                    // 0xec
	0xFF,                    // 0xed
	0xFF,                    // 0xee
	0xFF,                    // 0xef
	0xFF,                    // 0xf0
	0xFF,                    // 0xf1
	0xFF,                    // 0xf2
	0xFF,                    // 0xf3
	0xFF,                    // 0xf4
	0xFF,                    // 0xf5
	0xFF,                    // 0xf6
	0xFF,                    // 0xf7
	0xFF,                    // 0xf8
	0xFF,                    // 0xf9
	0xFF,                    // 0xfa
	0xFF,                    // 0xfb
	0xFF,                    // 0xfc
	0xFF,                    // 0xfd
	0xFF,                    // 0xfe
};

BOOL mf_input_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
{
	CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
	BOOL keyDown = TRUE;
	CGEventRef kbEvent;
	CGKeyCode kCode = 0xFF;

	if (flags & KBD_FLAGS_RELEASE)
	{
		keyDown = FALSE;
	}

	if (flags & KBD_FLAGS_EXTENDED)
	{
		switch (code)
		{
			// case 0x52: //insert
			case 0x53:
				kCode = kVK_ForwardDelete;
				break;

			case 0x4B:
				kCode = kVK_LeftArrow;
				break;

			case 0x47:
				kCode = kVK_Home;
				break;

			case 0x4F:
				kCode = kVK_End;
				break;

			case 0x48:
				kCode = kVK_UpArrow;
				break;

			case 0x50:
				kCode = kVK_DownArrow;
				break;

			case 0x49:
				kCode = kVK_PageUp;
				break;

			case 0x51:
				kCode = kVK_PageDown;
				break;

			case 0x4D:
				kCode = kVK_RightArrow;
				break;

			default:
				break;
		}
	}
	else
	{
		kCode = keymap[code];
	}

	kbEvent = CGEventCreateKeyboardEvent(source, kCode, keyDown);
	CGEventPost(kCGHIDEventTap, kbEvent);
	CFRelease(kbEvent);
	CFRelease(source);
	return TRUE;
}

BOOL mf_input_unicode_keyboard_event(rdpInput* input, UINT16 flags, UINT16 code)
{
	return FALSE;
}

BOOL mf_input_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
{
	float width, height;
	CGWheelCount wheelCount = 2;
	INT32 scroll_x = 0;
	INT32 scroll_y = 0;

	if (flags & (PTR_FLAGS_WHEEL | PTR_FLAGS_HWHEEL))
	{
		INT32 scroll = flags & WheelRotationMask;

		if (flags & PTR_FLAGS_WHEEL_NEGATIVE)
			scroll = -(flags & WheelRotationMask) / 392;
		else
			scroll = (flags & WheelRotationMask) / 120;

		if (flags & PTR_FLAGS_WHEEL)
			scroll_y = scroll;
		else
			scroll_x = scroll;

		CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
		CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(source, kCGScrollEventUnitLine,
		                                                       wheelCount, scroll_y, scroll_x);
		CGEventPost(kCGHIDEventTap, scrollEvent);
		CFRelease(scrollEvent);
		CFRelease(source);
	}
	else
	{
		mfInfo* mfi;
		CGEventSourceRef source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
		CGEventType mouseType = kCGEventNull;
		CGMouseButton mouseButton = kCGMouseButtonLeft;
		mfi = mf_info_get_instance();
		// width and height of primary screen (even in multimon setups
		width = (float)mfi->servscreen_width;
		height = (float)mfi->servscreen_height;
		x += mfi->servscreen_xoffset;
		y += mfi->servscreen_yoffset;

		if (flags & PTR_FLAGS_MOVE)
		{
			if (mfi->mouse_down_left == TRUE)
			{
				mouseType = kCGEventLeftMouseDragged;
			}
			else if (mfi->mouse_down_right == TRUE)
			{
				mouseType = kCGEventRightMouseDragged;
			}
			else if (mfi->mouse_down_other == TRUE)
			{
				mouseType = kCGEventOtherMouseDragged;
			}
			else
			{
				mouseType = kCGEventMouseMoved;
			}

			CGEventRef move = CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y),
			                                          mouseButton // ignored for just movement
			);
			CGEventPost(kCGHIDEventTap, move);
			CFRelease(move);
		}

		if (flags & PTR_FLAGS_BUTTON1)
		{
			mouseButton = kCGMouseButtonLeft;

			if (flags & PTR_FLAGS_DOWN)
			{
				mouseType = kCGEventLeftMouseDown;
				mfi->mouse_down_left = TRUE;
			}
			else
			{
				mouseType = kCGEventLeftMouseUp;
				mfi->mouse_down_right = FALSE;
			}
		}
		else if (flags & PTR_FLAGS_BUTTON2)
		{
			mouseButton = kCGMouseButtonRight;

			if (flags & PTR_FLAGS_DOWN)
			{
				mouseType = kCGEventRightMouseDown;
				mfi->mouse_down_right = TRUE;
			}
			else
			{
				mouseType = kCGEventRightMouseUp;
				mfi->mouse_down_right = FALSE;
			}
		}
		else if (flags & PTR_FLAGS_BUTTON3)
		{
			mouseButton = kCGMouseButtonCenter;

			if (flags & PTR_FLAGS_DOWN)
			{
				mouseType = kCGEventOtherMouseDown;
				mfi->mouse_down_other = TRUE;
			}
			else
			{
				mouseType = kCGEventOtherMouseUp;
				mfi->mouse_down_other = FALSE;
			}
		}

		CGEventRef mouseEvent =
		    CGEventCreateMouseEvent(source, mouseType, CGPointMake(x, y), mouseButton);
		CGEventPost(kCGHIDEventTap, mouseEvent);
		CFRelease(mouseEvent);
		CFRelease(source);
	}

	return TRUE;
}

BOOL mf_input_extended_mouse_event(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
{
	return FALSE;
}

BOOL mf_input_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code)
{
	return FALSE;
}

BOOL mf_input_unicode_keyboard_event_dummy(rdpInput* input, UINT16 flags, UINT16 code)
{
	return FALSE;
}

BOOL mf_input_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
{
	return FALSE;
}

BOOL mf_input_extended_mouse_event_dummy(rdpInput* input, UINT16 flags, UINT16 x, UINT16 y)
{
	return FALSE;
}
