//
//  SRKeyCodeTransformer.h
//  ShortcutRecorder
//
//  Copyright 2006-2012 Contributors. All rights reserved.
//
//  License: BSD
//
//  Contributors:
//      David Dauer
//      Jesper
//      Jamie Kirkpatrick
//      Ilya Kulakov
//      Silvio Rizzi

#import "SRKeyCodeTransformer.h"
#import "SRCommon.h"


FOUNDATION_STATIC_INLINE NSString* _SRUnicharToString(unichar aChar)
{
    return [NSString stringWithFormat: @"%C", aChar];
}


@implementation SRKeyCodeTransformer

- (instancetype)initWithASCIICapableKeyboardInputSource:(BOOL)aUsesASCII plainStrings:(BOOL)aUsesPlainStrings
{
    self = [super init];

    if (self)
    {
        _usesASCIICapableKeyboardInputSource = aUsesASCII;
        _usesPlainStrings = aUsesPlainStrings;
    }

    return self;
}

- (instancetype)init
{
    return [self initWithASCIICapableKeyboardInputSource:NO plainStrings:NO];
}


#pragma mark Methods

+ (instancetype)sharedTransformer
{
    static dispatch_once_t OnceToken;
    static SRKeyCodeTransformer *Transformer = nil;
    dispatch_once(&OnceToken, ^{
        Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:NO
                                                               plainStrings:NO];
    });
    return Transformer;
}

+ (instancetype)sharedASCIITransformer
{
    static dispatch_once_t OnceToken;
    static SRKeyCodeTransformer *Transformer = nil;
    dispatch_once(&OnceToken, ^{
        Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:YES
                                                               plainStrings:NO];
    });
    return Transformer;
}

+ (instancetype)sharedPlainTransformer
{
    static dispatch_once_t OnceToken;
    static SRKeyCodeTransformer *Transformer = nil;
    dispatch_once(&OnceToken, ^{
        Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:NO
                                                               plainStrings:YES];
    });
    return Transformer;
}

+ (SRKeyCodeTransformer *)sharedPlainASCIITransformer
{
    static dispatch_once_t OnceToken;
    static SRKeyCodeTransformer *Transformer = nil;
    dispatch_once(&OnceToken, ^{
        Transformer = [[self alloc] initWithASCIICapableKeyboardInputSource:YES
                                                               plainStrings:YES];
    });
    return Transformer;
}

+ (NSDictionary *)specialKeyCodesToUnicodeCharactersMapping
{
    // Most of these keys are system constans.
    // Values for rest of the keys were given by setting key equivalents in IB.
    static dispatch_once_t OnceToken;
    static NSDictionary *Mapping = nil;
    dispatch_once(&OnceToken, ^{
        Mapping = @{
            @(kVK_F1): _SRUnicharToString(NSF1FunctionKey),
            @(kVK_F2): _SRUnicharToString(NSF2FunctionKey),
            @(kVK_F3): _SRUnicharToString(NSF3FunctionKey),
            @(kVK_F4): _SRUnicharToString(NSF4FunctionKey),
            @(kVK_F5): _SRUnicharToString(NSF5FunctionKey),
            @(kVK_F6): _SRUnicharToString(NSF6FunctionKey),
            @(kVK_F7): _SRUnicharToString(NSF7FunctionKey),
            @(kVK_F8): _SRUnicharToString(NSF8FunctionKey),
            @(kVK_F9): _SRUnicharToString(NSF9FunctionKey),
            @(kVK_F10): _SRUnicharToString(NSF10FunctionKey),
            @(kVK_F11): _SRUnicharToString(NSF11FunctionKey),
            @(kVK_F12): _SRUnicharToString(NSF12FunctionKey),
            @(kVK_F13): _SRUnicharToString(NSF13FunctionKey),
            @(kVK_F14): _SRUnicharToString(NSF14FunctionKey),
            @(kVK_F15): _SRUnicharToString(NSF15FunctionKey),
            @(kVK_F16): _SRUnicharToString(NSF16FunctionKey),
            @(kVK_F17): _SRUnicharToString(NSF17FunctionKey),
            @(kVK_F18): _SRUnicharToString(NSF18FunctionKey),
            @(kVK_F19): _SRUnicharToString(NSF19FunctionKey),
            @(kVK_F20): _SRUnicharToString(NSF20FunctionKey),
            @(kVK_Space): _SRUnicharToString(' '),
            @(kVK_Delete): _SRUnicharToString(NSBackspaceCharacter),
            @(kVK_ForwardDelete): _SRUnicharToString(NSDeleteCharacter),
            @(kVK_ANSI_KeypadClear): _SRUnicharToString(NSClearLineFunctionKey),
            @(kVK_LeftArrow): _SRUnicharToString(NSLeftArrowFunctionKey),
            @(kVK_RightArrow): _SRUnicharToString(NSRightArrowFunctionKey),
            @(kVK_UpArrow): _SRUnicharToString(NSUpArrowFunctionKey),
            @(kVK_DownArrow): _SRUnicharToString(NSDownArrowFunctionKey),
            @(kVK_End): _SRUnicharToString(NSEndFunctionKey),
            @(kVK_Home): _SRUnicharToString(NSHomeFunctionKey),
            @(kVK_Escape): _SRUnicharToString('\e'),
            @(kVK_PageDown): _SRUnicharToString(NSPageDownFunctionKey),
            @(kVK_PageUp): _SRUnicharToString(NSPageUpFunctionKey),
            @(kVK_Return): _SRUnicharToString(NSCarriageReturnCharacter),
            @(kVK_ANSI_KeypadEnter): _SRUnicharToString(NSEnterCharacter),
            @(kVK_Tab): _SRUnicharToString(NSTabCharacter),
            @(kVK_Help): _SRUnicharToString(NSHelpFunctionKey)
        };
    });
    return Mapping;
}

+ (NSDictionary *)specialKeyCodesToPlainStringsMapping
{
    static dispatch_once_t OnceToken;
    static NSDictionary *Mapping = nil;
    dispatch_once(&OnceToken, ^{
        Mapping = @{
            @(kVK_F1): @"F1",
            @(kVK_F2): @"F2",
            @(kVK_F3): @"F3",
            @(kVK_F4): @"F4",
            @(kVK_F5): @"F5",
            @(kVK_F6): @"F6",
            @(kVK_F7): @"F7",
            @(kVK_F8): @"F8",
            @(kVK_F9): @"F9",
            @(kVK_F10): @"F10",
            @(kVK_F11): @"F11",
            @(kVK_F12): @"F12",
            @(kVK_F13): @"F13",
            @(kVK_F14): @"F14",
            @(kVK_F15): @"F15",
            @(kVK_F16): @"F16",
            @(kVK_F17): @"F17",
            @(kVK_F18): @"F18",
            @(kVK_F19): @"F19",
            @(kVK_F20): @"F20",
            @(kVK_Space): SRLoc(@"Space"),
            @(kVK_Delete): _SRUnicharToString(SRKeyCodeGlyphDeleteLeft),
            @(kVK_ForwardDelete): _SRUnicharToString(SRKeyCodeGlyphDeleteRight),
            @(kVK_ANSI_KeypadClear): _SRUnicharToString(SRKeyCodeGlyphPadClear),
            @(kVK_LeftArrow): _SRUnicharToString(SRKeyCodeGlyphLeftArrow),
            @(kVK_RightArrow): _SRUnicharToString(SRKeyCodeGlyphRightArrow),
            @(kVK_UpArrow): _SRUnicharToString(SRKeyCodeGlyphUpArrow),
            @(kVK_DownArrow): _SRUnicharToString(SRKeyCodeGlyphDownArrow),
            @(kVK_End): _SRUnicharToString(SRKeyCodeGlyphSoutheastArrow),
            @(kVK_Home): _SRUnicharToString(SRKeyCodeGlyphNorthwestArrow),
            @(kVK_Escape): _SRUnicharToString(SRKeyCodeGlyphEscape),
            @(kVK_PageDown): _SRUnicharToString(SRKeyCodeGlyphPageDown),
            @(kVK_PageUp): _SRUnicharToString(SRKeyCodeGlyphPageUp),
            @(kVK_Return): _SRUnicharToString(SRKeyCodeGlyphReturnR2L),
            @(kVK_ANSI_KeypadEnter): _SRUnicharToString(SRKeyCodeGlyphReturn),
            @(kVK_Tab): _SRUnicharToString(SRKeyCodeGlyphTabRight),
            @(kVK_Help): @"?⃝"
        };
    });
    return Mapping;
}

- (BOOL)isKeyCodeSpecial:(unsigned short)aKeyCode
{
    switch (aKeyCode)
    {
        case kVK_F1:
        case kVK_F2:
        case kVK_F3:
        case kVK_F4:
        case kVK_F5:
        case kVK_F6:
        case kVK_F7:
        case kVK_F8:
        case kVK_F9:
        case kVK_F10:
        case kVK_F11:
        case kVK_F12:
        case kVK_F13:
        case kVK_F14:
        case kVK_F15:
        case kVK_F16:
        case kVK_F17:
        case kVK_F18:
        case kVK_F19:
        case kVK_F20:
        case kVK_Space:
        case kVK_Delete:
        case kVK_ForwardDelete:
        case kVK_ANSI_KeypadClear:
        case kVK_LeftArrow:
        case kVK_RightArrow:
        case kVK_UpArrow:
        case kVK_DownArrow:
        case kVK_End:
        case kVK_Home:
        case kVK_Escape:
        case kVK_PageDown:
        case kVK_PageUp:
        case kVK_Return:
        case kVK_ANSI_KeypadEnter:
        case kVK_Tab:
        case kVK_Help:
            return YES;
        default:
            return NO;
    }
}


#pragma mark NSValueTransformer

+ (BOOL)allowsReverseTransformation
{
    return NO;
}

+ (Class)transformedValueClass;
{
    return [NSString class];
}

- (NSString *)transformedValue:(NSNumber *)aValue
{
    return [self transformedValue:aValue withModifierFlags:nil];
}

- (NSString *)transformedValue:(NSNumber *)aValue withModifierFlags:(NSNumber *)aModifierFlags
{
    return [self transformedValue:aValue withImplicitModifierFlags:aModifierFlags explicitModifierFlags:nil];
}

- (NSString *)transformedValue:(NSNumber *)aValue withImplicitModifierFlags:(NSNumber *)anImplicitModifierFlags explicitModifierFlags:(NSNumber *)anExplicitModifierFlags
{
    if ([anImplicitModifierFlags unsignedIntegerValue] & [anExplicitModifierFlags unsignedIntegerValue] & SRCocoaModifierFlagsMask)
    {
        [NSException raise:NSInvalidArgumentException format:@"anImplicitModifierFlags and anExplicitModifierFlags MUST NOT have common elements"];
    }

    if (![aValue isKindOfClass:[NSNumber class]])
        return @"";

    // Some key codes cannot be translated directly.
    NSString *unmappedString = [self transformedSpecialKeyCode:aValue withExplicitModifierFlags:anExplicitModifierFlags];

    if (unmappedString)
        return unmappedString;

    CFDataRef layoutData = NULL;

    if (self.usesASCIICapableKeyboardInputSource)
    {
        TISInputSourceRef tisSource = TISCopyCurrentASCIICapableKeyboardLayoutInputSource();

        if (!tisSource)
            return @"";

        layoutData = (CFDataRef)TISGetInputSourceProperty(tisSource, kTISPropertyUnicodeKeyLayoutData);
        CFRelease(tisSource);
    }
    else
    {
        TISInputSourceRef tisSource = TISCopyCurrentKeyboardLayoutInputSource();

        if (!tisSource)
            return @"";

        layoutData = (CFDataRef)TISGetInputSourceProperty(tisSource, kTISPropertyUnicodeKeyLayoutData);
        CFRelease(tisSource);

        // For non-unicode layouts such as Chinese, Japanese, and Korean, get the ASCII capable layout
        if (!layoutData)
        {
            tisSource = TISCopyCurrentASCIICapableKeyboardLayoutInputSource();

            if (!tisSource)
                return @"";

            layoutData = (CFDataRef)TISGetInputSourceProperty(tisSource, kTISPropertyUnicodeKeyLayoutData);
            CFRelease(tisSource);
        }
    }

    if (!layoutData)
        return @"";

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

    static const UniCharCount MaxLength = 255;
    UniCharCount actualLength = 0;
    UniChar chars[MaxLength] = {0};

    UInt32 deadKeyState = 0;
    OSStatus err = UCKeyTranslate(keyLayout,
                                  [aValue unsignedShortValue],
                                  kUCKeyActionDisplay,
                                  SRCocoaToCarbonFlags([anImplicitModifierFlags unsignedIntegerValue]) >> 8,
                                  LMGetKbdType(),
                                  kUCKeyTranslateNoDeadKeysBit,
                                  &deadKeyState,
                                  sizeof(chars) / sizeof(UniChar),
                                  &actualLength,
                                  chars);
    if (err != noErr)
        return @"";

    if (self.usesPlainStrings)
        return [[NSString stringWithCharacters:chars length:actualLength] uppercaseString];
    else
        return [NSString stringWithCharacters:chars length:actualLength];
}

- (NSString *)transformedSpecialKeyCode:(NSNumber *)aKeyCode withExplicitModifierFlags:(NSNumber *)anExplicitModifierFlags
{
    if ([anExplicitModifierFlags unsignedIntegerValue] & NSShiftKeyMask && [aKeyCode unsignedShortValue] == kVK_Tab)
    {
        if (self.usesPlainStrings)
            return _SRUnicharToString(SRKeyCodeGlyphTabLeft);
        else
            return _SRUnicharToString(NSBackTabCharacter);
    }

    if (self.usesPlainStrings)
        return [[self class] specialKeyCodesToPlainStringsMapping][aKeyCode];
    else
        return [[self class] specialKeyCodesToUnicodeCharactersMapping][aKeyCode];
}

@end