//
//  SRValidator.h
//  ShortcutRecorder
//
//  Copyright 2006-2007 Contributors. All rights reserved.
//
//  License: BSD
//
//  Contributors:
//      David Dauer
//      Jesper
//      Jamie Kirkpatrick

#import "SRValidator.h"
#import "SRCommon.h"

@implementation SRValidator

//---------------------------------------------------------- 
// iinitWithDelegate:
//---------------------------------------------------------- 
- (id) initWithDelegate:(id)theDelegate;
{
    self = [super init];
    if ( !self )
        return nil;
    
    [self setDelegate:theDelegate];
    
    return self;
}

//---------------------------------------------------------- 
// isKeyCode:andFlagsTaken:error:
//---------------------------------------------------------- 
- (BOOL) isKeyCode:(NSInteger)keyCode andFlagsTaken:(NSUInteger)flags error:(NSError **)error;
{
    // if we have a delegate, it goes first...
	if ( delegate )
	{
		NSString *delegateReason = nil;
		if ( [delegate shortcutValidator:self 
                               isKeyCode:keyCode 
                           andFlagsTaken:SRCarbonToCocoaFlags( flags )
                                  reason:&delegateReason])
		{
            if ( error )
            {
                NSString *description = [NSString stringWithFormat: 
                    SRLoc(@"The key combination %@ can't be used!"), 
                    SRStringForCarbonModifierFlagsAndKeyCode( flags, keyCode )];
                NSString *recoverySuggestion = [NSString stringWithFormat: 
                    SRLoc(@"The key combination \"%@\" can't be used because %@."), 
                    SRReadableStringForCarbonModifierFlagsAndKeyCode( flags, keyCode ),
                    ( delegateReason && [delegateReason length] ) ? delegateReason : @"it's already used"];
                NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
										  description, NSLocalizedDescriptionKey,
										  recoverySuggestion, NSLocalizedRecoverySuggestionErrorKey,
										  [NSArray arrayWithObject:@"OK"], NSLocalizedRecoveryOptionsErrorKey, // Is this needed? Shouldn't it show 'OK' by default? -AK
										  nil];
                *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo];
            }
			return YES;
		}
	}
	
	// then our implementation...
	NSArray *globalHotKeys;
	
	// get global hot keys...
	if ( CopySymbolicHotKeys((CFArrayRef *)&globalHotKeys ) != noErr )
		return YES;
	
	NSEnumerator *globalHotKeysEnumerator = [globalHotKeys objectEnumerator];
	NSDictionary *globalHotKeyInfoDictionary;
	int32_t globalHotKeyFlags;
	NSInteger globalHotKeyCharCode;
	BOOL globalCommandMod = NO, globalOptionMod = NO, globalShiftMod = NO, globalCtrlMod = NO;
	BOOL localCommandMod = NO, localOptionMod = NO, localShiftMod = NO, localCtrlMod = NO;
	
	// Prepare local carbon comparison flags
	if ( flags & cmdKey )       localCommandMod = YES;
	if ( flags & optionKey )    localOptionMod = YES;
	if ( flags & shiftKey )     localShiftMod = YES;
	if ( flags & controlKey )   localCtrlMod = YES;
    
	while (( globalHotKeyInfoDictionary = [globalHotKeysEnumerator nextObject] ))
	{
		// Only check if global hotkey is enabled
		if ( (CFBooleanRef)[globalHotKeyInfoDictionary objectForKey:(NSString *)kHISymbolicHotKeyEnabled] != kCFBooleanTrue )
            continue;
		
        globalCommandMod    = NO;
        globalOptionMod     = NO;
        globalShiftMod      = NO;
        globalCtrlMod       = NO;
        
        globalHotKeyCharCode = [(NSNumber *)[globalHotKeyInfoDictionary objectForKey:(NSString *)kHISymbolicHotKeyCode] shortValue];
        
        CFNumberGetValue((CFNumberRef)[globalHotKeyInfoDictionary objectForKey: (NSString *)kHISymbolicHotKeyModifiers],kCFNumberSInt32Type,&globalHotKeyFlags);
        
        if ( globalHotKeyFlags & cmdKey )        globalCommandMod = YES;
        if ( globalHotKeyFlags & optionKey )     globalOptionMod = YES;
        if ( globalHotKeyFlags & shiftKey)       globalShiftMod = YES;
        if ( globalHotKeyFlags & controlKey )    globalCtrlMod = YES;
        
        NSString *localKeyString = SRStringForKeyCode( keyCode );
        if (![localKeyString length]) return YES;
        
        
        // compare unichar value and modifier flags
		if ( ( globalHotKeyCharCode == keyCode ) 
             && ( globalCommandMod == localCommandMod ) 
             && ( globalOptionMod == localOptionMod ) 
             && ( globalShiftMod == localShiftMod ) 
             && ( globalCtrlMod == localCtrlMod ) )
        {
            if ( error )
            {
                NSString *description = [NSString stringWithFormat: 
                    SRLoc(@"The key combination %@ can't be used!"), 
                    SRStringForCarbonModifierFlagsAndKeyCode( flags, keyCode )];
                NSString *recoverySuggestion = [NSString stringWithFormat: 
                    SRLoc(@"The key combination \"%@\" can't be used because it's already used by a system-wide keyboard shortcut. (If you really want to use this key combination, most shortcuts can be changed in the Keyboard & Mouse panel in System Preferences.)"), 
                    SRReadableStringForCarbonModifierFlagsAndKeyCode( flags, keyCode )];
				NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
										  description, NSLocalizedDescriptionKey,
										  recoverySuggestion, NSLocalizedRecoverySuggestionErrorKey,
										  [NSArray arrayWithObject:@"OK"], NSLocalizedRecoveryOptionsErrorKey,
										  nil];
                *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo];
            }
            return YES;
        }
	}
	
	// Check menus too
	return [self isKeyCode:keyCode andFlags:flags takenInMenu:[NSApp mainMenu] error:error];
}

//---------------------------------------------------------- 
// isKeyCode:andFlags:takenInMenu:error:
//---------------------------------------------------------- 
- (BOOL) isKeyCode:(NSInteger)keyCode andFlags:(NSUInteger)flags takenInMenu:(NSMenu *)menu error:(NSError **)error;
{
    NSArray *menuItemsArray = [menu itemArray];
	NSEnumerator *menuItemsEnumerator = [menuItemsArray objectEnumerator];
	NSMenuItem *menuItem;
	NSUInteger menuItemModifierFlags;
	NSString *menuItemKeyEquivalent;
	
	BOOL menuItemCommandMod = NO, menuItemOptionMod = NO, menuItemShiftMod = NO, menuItemCtrlMod = NO;
	BOOL localCommandMod = NO, localOptionMod = NO, localShiftMod = NO, localCtrlMod = NO;
	
	// Prepare local carbon comparison flags
	if ( flags & cmdKey )       localCommandMod = YES;
	if ( flags & optionKey )    localOptionMod = YES;
	if ( flags & shiftKey )     localShiftMod = YES;
	if ( flags & controlKey )   localCtrlMod = YES;
	
	while (( menuItem = [menuItemsEnumerator nextObject] ))
	{
        // rescurse into all submenus...
		if ( [menuItem hasSubmenu] )
		{
			if ( [self isKeyCode:keyCode andFlags:flags takenInMenu:[menuItem submenu] error:error] ) 
			{
				return YES;
			}
		}
		
		if ( ( menuItemKeyEquivalent = [menuItem keyEquivalent] )
             && ( ![menuItemKeyEquivalent isEqualToString: @""] ) )
		{
			menuItemCommandMod = NO;
			menuItemOptionMod = NO;
			menuItemShiftMod = NO;
			menuItemCtrlMod = NO;
			
			menuItemModifierFlags = [menuItem keyEquivalentModifierMask];
            
			if ( menuItemModifierFlags & NSCommandKeyMask )     menuItemCommandMod = YES;
			if ( menuItemModifierFlags & NSAlternateKeyMask )   menuItemOptionMod = YES;
			if ( menuItemModifierFlags & NSShiftKeyMask )       menuItemShiftMod = YES;
			if ( menuItemModifierFlags & NSControlKeyMask )     menuItemCtrlMod = YES;
			
			NSString *localKeyString = SRStringForKeyCode( keyCode );
			
			// Compare translated keyCode and modifier flags
			if ( ( [[menuItemKeyEquivalent uppercaseString] isEqualToString: localKeyString] ) 
                 && ( menuItemCommandMod == localCommandMod ) 
                 && ( menuItemOptionMod == localOptionMod ) 
                 && ( menuItemShiftMod == localShiftMod ) 
                 && ( menuItemCtrlMod == localCtrlMod ) )
			{
                if ( error )
                {
                    NSString *description = [NSString stringWithFormat: 
                        SRLoc(@"The key combination %@ can't be used!"),
                        SRStringForCarbonModifierFlagsAndKeyCode( flags, keyCode )];
                    NSString *recoverySuggestion = [NSString stringWithFormat: 
                        SRLoc(@"The key combination \"%@\" can't be used because it's already used by the menu item \"%@\"."), 
                        SRReadableStringForCocoaModifierFlagsAndKeyCode( menuItemModifierFlags, keyCode ),
                        [menuItem title]];
                    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
											  description, NSLocalizedDescriptionKey,
											  recoverySuggestion, NSLocalizedRecoverySuggestionErrorKey,
											  [NSArray arrayWithObject:@"OK"], NSLocalizedRecoveryOptionsErrorKey,
											  nil];
                    *error = [NSError errorWithDomain:NSCocoaErrorDomain code:0 userInfo:userInfo];
                }
				return YES;
			}
		}
	}
	return NO;    
}

#pragma mark -
#pragma mark accessors

//---------------------------------------------------------- 
//  delegate 
//---------------------------------------------------------- 
- (id) delegate
{
    return delegate; 
}

- (void) setDelegate: (id) theDelegate
{
    delegate = theDelegate; // Standard delegate pattern does not retain the delegate
}

@end

#pragma mark -
#pragma mark default delegate implementation

@implementation NSObject( SRValidation )

//---------------------------------------------------------- 
// shortcutValidator:isKeyCode:andFlagsTaken:reason:
//---------------------------------------------------------- 
- (BOOL) shortcutValidator:(SRValidator *)validator isKeyCode:(NSInteger)keyCode andFlagsTaken:(NSUInteger)flags reason:(NSString **)aReason;
{
    return NO;
}

@end
