/*
 RDP Keyboard helper

 Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz

 This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
 If a copy of the MPL was not distributed with this file, You can obtain one at
 http://mozilla.org/MPL/2.0/.
 */

#import "RDPKeyboard.h"
#include <freerdp/locale/keyboard.h>

@interface RDPKeyboard (Private)
- (void)sendUnicodeKey:(int)character up:(BOOL)up;
- (void)handleSpecialKey:(int)character;
- (void)handleAlphaNumChar:(int)character;
- (void)notifyDelegateModifiersChanged;
@end

@implementation RDPKeyboard

@synthesize delegate = _delegate, ctrlPressed = _ctrl_pressed, altPressed = _alt_pressed,
            shiftPressed = _shift_pressed, winPressed = _win_pressed;

- (id)init
{
	if ((self = [super init]) != nil)
	{
		[self initWithSession:nil delegate:nil];

		memset(_virtual_key_map, 0, sizeof(_virtual_key_map));
		memset(_virtual_key_shift_map, 0, sizeof(_virtual_key_shift_map));

		// init vkey map - used for alpha-num characters
		_virtual_key_map['0'] = VK_KEY_0;
		_virtual_key_map['1'] = VK_KEY_1;
		_virtual_key_map['2'] = VK_KEY_2;
		_virtual_key_map['3'] = VK_KEY_3;
		_virtual_key_map['4'] = VK_KEY_4;
		_virtual_key_map['5'] = VK_KEY_5;
		_virtual_key_map['6'] = VK_KEY_6;
		_virtual_key_map['7'] = VK_KEY_7;
		_virtual_key_map['8'] = VK_KEY_8;
		_virtual_key_map['9'] = VK_KEY_9;

		_virtual_key_map['a'] = VK_KEY_A;
		_virtual_key_map['b'] = VK_KEY_B;
		_virtual_key_map['c'] = VK_KEY_C;
		_virtual_key_map['d'] = VK_KEY_D;
		_virtual_key_map['e'] = VK_KEY_E;
		_virtual_key_map['f'] = VK_KEY_F;
		_virtual_key_map['g'] = VK_KEY_G;
		_virtual_key_map['h'] = VK_KEY_H;
		_virtual_key_map['i'] = VK_KEY_I;
		_virtual_key_map['j'] = VK_KEY_J;
		_virtual_key_map['k'] = VK_KEY_K;
		_virtual_key_map['l'] = VK_KEY_L;
		_virtual_key_map['m'] = VK_KEY_M;
		_virtual_key_map['n'] = VK_KEY_N;
		_virtual_key_map['o'] = VK_KEY_O;
		_virtual_key_map['p'] = VK_KEY_P;
		_virtual_key_map['q'] = VK_KEY_Q;
		_virtual_key_map['r'] = VK_KEY_R;
		_virtual_key_map['s'] = VK_KEY_S;
		_virtual_key_map['t'] = VK_KEY_T;
		_virtual_key_map['u'] = VK_KEY_U;
		_virtual_key_map['v'] = VK_KEY_V;
		_virtual_key_map['w'] = VK_KEY_W;
		_virtual_key_map['x'] = VK_KEY_X;
		_virtual_key_map['y'] = VK_KEY_Y;
		_virtual_key_map['z'] = VK_KEY_Z;

		// some server does not accept unicode key event
		// so, send physical key combinations
		_virtual_key_map[' '] = VK_SPACE;
		_virtual_key_map['`'] = _virtual_key_map['~'] = VK_OEM_3;
		_virtual_key_map['-'] = _virtual_key_map['_'] = VK_OEM_MINUS;
		_virtual_key_map['='] = _virtual_key_map['+'] = VK_OEM_PLUS;
		_virtual_key_map['['] = _virtual_key_map['{'] = VK_OEM_4;
		_virtual_key_map[']'] = _virtual_key_map['}'] = VK_OEM_6;
		_virtual_key_map['\\'] = _virtual_key_map['|'] = VK_OEM_5;
		_virtual_key_map[';'] = _virtual_key_map[':'] = VK_OEM_1;
		_virtual_key_map['\''] = _virtual_key_map['\"'] = VK_OEM_7;
		_virtual_key_map[','] = _virtual_key_map['<'] = VK_OEM_COMMA;
		_virtual_key_map['.'] = _virtual_key_map['>'] = VK_OEM_PERIOD;
		_virtual_key_map['/'] = _virtual_key_map['?'] = VK_OEM_2;

		const char *shifted = "~_+{}|:\"<>?!@#$%^&*()";
		for (const char *p = shifted; *p != '\0'; p++)
			_virtual_key_shift_map[(unsigned char)*p] = YES;

		_virtual_key_map['!'] = VK_KEY_1;
		_virtual_key_map['@'] = VK_KEY_2;
		_virtual_key_map['#'] = VK_KEY_3;
		_virtual_key_map['$'] = VK_KEY_4;
		_virtual_key_map['%'] = VK_KEY_5;
		_virtual_key_map['^'] = VK_KEY_6;
		_virtual_key_map['&'] = VK_KEY_7;
		_virtual_key_map['*'] = VK_KEY_8;
		_virtual_key_map['('] = VK_KEY_9;
		_virtual_key_map[')'] = VK_KEY_0;
	}
	return self;
}

- (void)dealloc
{
	[super dealloc];
}

#pragma mark -
#pragma mark class methods

// return a keyboard instance
+ (RDPKeyboard *)getSharedRDPKeyboard
{
	static RDPKeyboard *_shared_keyboard = nil;

	if (_shared_keyboard == nil)
	{
		@synchronized(self)
		{
			if (_shared_keyboard == nil)
				_shared_keyboard = [[RDPKeyboard alloc] init];
		}
	}

	return _shared_keyboard;
}

// reset the keyboard instance and assign the given rdp instance
- (void)initWithSession:(RDPSession *)session delegate:(NSObject<RDPKeyboardDelegate> *)delegate
{
	_alt_pressed = NO;
	_ctrl_pressed = NO;
	_shift_pressed = NO;
	_win_pressed = NO;

	_session = session;
	_delegate = delegate;
}

- (void)reset
{
	// reset pressed ctrl, alt, shift or win key
	if (_shift_pressed)
		[self toggleShiftKey];
	if (_alt_pressed)
		[self toggleAltKey];
	if (_ctrl_pressed)
		[self toggleCtrlKey];
	if (_win_pressed)
		[self toggleWinKey];
}

// handles button pressed input event from the iOS keyboard
// performs all conversions etc.
- (void)sendUnicode:(int)character
{
	if ((character >= 0) && (character < 256) && isalnum((unsigned char)character))
		[self handleAlphaNumChar:character];
	else
		[self handleSpecialKey:character];

	[self reset];
}

// send a backspace key press
- (void)sendVirtualKeyCode:(int)keyCode
{
	[self sendVirtualKey:keyCode up:NO];
	[self sendVirtualKey:keyCode up:YES];
}

// sends the vk code to the session
- (void)sendVirtualKey:(int)vKey up:(BOOL)up
{
	DWORD scancode = GetVirtualScanCodeFromVirtualKeyCode(vKey, 4);
	int flags = (up ? KBD_FLAGS_RELEASE : KBD_FLAGS_DOWN);
	flags |= ((scancode & KBDEXT) ? KBD_FLAGS_EXTENDED : 0);
	[_session
	    sendInputEvent:[NSDictionary
	                       dictionaryWithObjectsAndKeys:@"keyboard", @"type", @"scancode",
	                                                    @"subtype",
	                                                    [NSNumber numberWithUnsignedShort:flags],
	                                                    @"flags",
	                                                    [NSNumber
	                                                        numberWithUnsignedShort:(scancode &
	                                                                                 0xFF)],
	                                                    @"scancode", nil]];
}

#pragma mark modifier key handling
// toggle ctrl key, returns true if pressed, otherwise false
- (void)toggleCtrlKey
{
	[self sendVirtualKey:VK_LCONTROL up:_ctrl_pressed];
	_ctrl_pressed = !_ctrl_pressed;
	[self notifyDelegateModifiersChanged];
}

// toggle alt key, returns true if pressed, otherwise false
- (void)toggleAltKey
{
	[self sendVirtualKey:VK_LMENU up:_alt_pressed];
	_alt_pressed = !_alt_pressed;
	[self notifyDelegateModifiersChanged];
}

// toggle shift key, returns true if pressed, otherwise false
- (void)toggleShiftKey
{
	[self sendVirtualKey:VK_LSHIFT up:_shift_pressed];
	_shift_pressed = !_shift_pressed;
	[self notifyDelegateModifiersChanged];
}

// toggle windows key, returns true if pressed, otherwise false
- (void)toggleWinKey
{
	[self sendVirtualKey:(VK_LWIN | KBDEXT) up:_win_pressed];
	_win_pressed = !_win_pressed;
	[self notifyDelegateModifiersChanged];
}

#pragma mark Sending special key strokes

- (void)sendEnterKeyStroke
{
	[self sendVirtualKeyCode:(VK_RETURN | KBDEXT)];
}

- (void)sendEscapeKeyStroke
{
	[self sendVirtualKeyCode:VK_ESCAPE];
}

- (void)sendBackspaceKeyStroke
{
	[self sendVirtualKeyCode:VK_BACK];
}

@end

#pragma mark -
@implementation RDPKeyboard (Private)

- (void)handleAlphaNumChar:(int)character
{
	// if we receive an uppercase letter - make it lower and send an shift down event to server
	BOOL shift_was_sent = NO;
	if (isupper(character) && _shift_pressed == NO)
	{
		character = tolower(character);
		[self sendVirtualKey:VK_LSHIFT up:NO];
		shift_was_sent = YES;
	}

	// convert the character to a VK
	int vk = _virtual_key_map[character];
	if (vk != 0)
	{
		// send key pressed
		[self sendVirtualKey:vk up:NO];
		[self sendVirtualKey:vk up:YES];
	}

	// send the missing shift up if we had a shift down
	if (shift_was_sent)
		[self sendVirtualKey:VK_LSHIFT up:YES];
}

- (void)handleSpecialKey:(int)character
{
	if ((character >= 0) && (character < 256) && (_virtual_key_map[character] != 0))
	{
		BOOL shift_was_sent = NO;
		if (_virtual_key_shift_map[character] && !_shift_pressed)
		{
			[self sendVirtualKey:VK_LSHIFT up:NO];
			shift_was_sent = YES;
		}

		[self sendVirtualKey:_virtual_key_map[character] up:NO];
		[self sendVirtualKey:_virtual_key_map[character] up:YES];

		if (shift_was_sent)
			[self sendVirtualKey:VK_LSHIFT up:YES];
		return;
	}

	// Fall back for characters that have no US keyboard scancode representation.
	[self sendUnicodeKey:character up:NO];
	[self sendUnicodeKey:character up:YES];
}

- (void)sendUnicodeKey:(int)character up:(BOOL)up
{
	[_session
	    sendInputEvent:[NSDictionary
	                       dictionaryWithObjectsAndKeys:
	                           @"keyboard", @"type", @"unicode", @"subtype",
	                           [NSNumber numberWithUnsignedShort:(up ? KBD_FLAGS_RELEASE : 0)],
	                           @"flags", [NSNumber numberWithUnsignedShort:character],
	                           @"unicode_char", nil]];
}

- (void)notifyDelegateModifiersChanged
{
	if ([[self delegate] respondsToSelector:@selector(modifiersChangedForKeyboard:)])
		[[self delegate] modifiersChangedForKeyboard:self];
}

@end
