#include "keycode.h"

#if defined(IS_MACOSX)

#include <CoreFoundation/CoreFoundation.h>
#include <Carbon/Carbon.h> /* For kVK_ constants, and TIS functions. */

/* Returns string representation of key, if it is printable.
 * Ownership follows the Create Rule; that is, it is the caller's
 * responsibility to release the returned object. */
CFStringRef createStringForKey(CGKeyCode keyCode);

#elif defined(USE_X11)

/*
 * Structs to store key mappings not handled by XStringToKeysym() on some
 * Linux systems.
 */

struct XSpecialCharacterMapping {
	char name;
	MMKeyCode code;
};

struct XSpecialCharacterMapping XSpecialCharacterTable[] = {
	{'~', XK_asciitilde},
  	{'_', XK_underscore},
  	{'[', XK_bracketleft},
  	{']', XK_bracketright},
  	{'!', XK_exclam},
  	{'\'', XK_quotedbl},
  	{'#', XK_numbersign},
  	{'$', XK_dollar},
  	{'%', XK_percent},
  	{'&', XK_ampersand},
  	{'\'', XK_quoteright},
  	{'*', XK_asterisk},
  	{'+', XK_plus},
  	{',', XK_comma},
  	{'-', XK_minus},
  	{'.', XK_period},
  	{'?', XK_question},
  	{'<', XK_less},
  	{'>', XK_greater},
  	{'=', XK_equal},
  	{'@', XK_at},
  	{':', XK_colon},
  	{';', XK_semicolon},
  	{'\\', XK_backslash},
  	{'`', XK_grave},
  	{'{', XK_braceleft},
  	{'}', XK_braceright},
  	{'|', XK_bar},
  	{'^', XK_asciicircum},
  	{'(', XK_parenleft},
  	{')', XK_parenright},
  	{' ', XK_space},
  	{'/', XK_slash},
  	{'\t', XK_Tab},
  	{'\n', XK_Return}
};

#endif

MMKeyCode keyCodeForChar(const char c){
	#if defined(IS_MACOSX)
		/* OS X does not appear to have a built-in function for this, so instead we
		* have to write our own. */
		static CFMutableDictionaryRef charToCodeDict = NULL;
		CGKeyCode code;
		UniChar character = c;
		CFStringRef charStr = NULL;

		/* Generate table of keycodes and characters. */
		if (charToCodeDict == NULL) {
			size_t i;
			charToCodeDict = CFDictionaryCreateMutable(kCFAllocatorDefault,
													128,
													&kCFCopyStringDictionaryKeyCallBacks,
													NULL);
			if (charToCodeDict == NULL) return UINT16_MAX;

			/* Loop through every keycode (0 - 127) to find its current mapping. */
			for (i = 0; i < 128; ++i) {
				CFStringRef string = createStringForKey((CGKeyCode)i);
				if (string != NULL) {
					CFDictionaryAddValue(charToCodeDict, string, (const void *)i);
					CFRelease(string);
				}
			}
		}

		charStr = CFStringCreateWithCharacters(kCFAllocatorDefault, &character, 1);

		/* Our values may be NULL (0), so we need to use this function. */
		if (!CFDictionaryGetValueIfPresent(charToCodeDict, charStr,
										(const void **)&code)) {
			code = UINT16_MAX; /* Error */
		}

		CFRelease(charStr);
		return (MMKeyCode)code;
	#elif defined(IS_WINDOWS)
		return VkKeyScan(c);
	#elif defined(USE_X11)
		MMKeyCode code;

		char buf[2];
		buf[0] = c;
		buf[1] = '\0';

		code = XStringToKeysym(buf);
		if (code == NoSymbol) {
			/* Some special keys are apparently not handled properly by
			* XStringToKeysym() on some systems, so search for them instead in our
			* mapping table. */
			size_t i;
			const size_t specialCharacterCount =
				sizeof(XSpecialCharacterTable) / sizeof(XSpecialCharacterTable[0]);
			for (i = 0; i < specialCharacterCount; ++i) {
				if (c == XSpecialCharacterTable[i].name) {
					code = XSpecialCharacterTable[i].code;
					break;
				}
			}
		}

		return code;
	#endif
}


#if defined(IS_MACOSX)

CFStringRef createStringForKey(CGKeyCode keyCode){
	TISInputSourceRef currentKeyboard = TISCopyCurrentASCIICapableKeyboardInputSource();
	CFDataRef layoutData =
		(CFDataRef)TISGetInputSourceProperty(currentKeyboard,
		                          kTISPropertyUnicodeKeyLayoutData);

	if (layoutData == nil) { return 0; }

	const UCKeyboardLayout *keyboardLayout =
		(const UCKeyboardLayout *)CFDataGetBytePtr(layoutData);

	UInt32 keysDown = 0;
	UniChar chars[4];
	UniCharCount realLength;

	UCKeyTranslate(keyboardLayout,
	               keyCode,
	               kUCKeyActionDisplay,
	               0,
	               LMGetKbdType(),
	               kUCKeyTranslateNoDeadKeysBit,
	               &keysDown,
	               sizeof(chars) / sizeof(chars[0]),
	               &realLength,
	               chars);
	CFRelease(currentKeyboard);

	return CFStringCreateWithCharacters(kCFAllocatorDefault, chars, 1);
}

#endif