// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "content/browser/cocoa/system_hotkey_map.h"

#import <Carbon/Carbon.h>

#include "base/mac/scoped_nsobject.h"

#pragma mark - NSDictionary Helper Functions

namespace {

// All 4 following functions return nil if the object doesn't exist, or isn't of
// the right class.
id ObjectForKey(NSDictionary* dict, NSString* key, Class aClass) {
  id object = [dict objectForKey:key];
  if (![object isKindOfClass:aClass])
    return nil;
  return object;
}

NSDictionary* DictionaryForKey(NSDictionary* dict, NSString* key) {
  return ObjectForKey(dict, key, [NSDictionary class]);
}

NSArray* ArrayForKey(NSDictionary* dict, NSString* key) {
  return ObjectForKey(dict, key, [NSArray class]);
}

NSNumber* NumberForKey(NSDictionary* dict, NSString* key) {
  return ObjectForKey(dict, key, [NSNumber class]);
}

NSString* StringForKey(NSDictionary* dict, NSString* key) {
  return ObjectForKey(dict, key, [NSString class]);
}

}  // namespace

#pragma mark - SystemHotkey

namespace content {

struct SystemHotkey {
  unsigned short key_code;
  NSUInteger modifiers;
};

#pragma mark - SystemHotkeyMap

SystemHotkeyMap::SystemHotkeyMap() {
}
SystemHotkeyMap::~SystemHotkeyMap() {
}

NSDictionary* SystemHotkeyMap::DictionaryFromData(NSData* data) {
  if (!data)
    return nil;

  NSError* error = nil;
  NSPropertyListFormat format;
  NSDictionary* dictionary =
      [NSPropertyListSerialization propertyListWithData:data
                                                options:0
                                                 format:&format
                                                  error:&error];

  if (![dictionary isKindOfClass:[NSDictionary class]])
    return nil;

  return dictionary;
}

bool SystemHotkeyMap::ParseDictionary(NSDictionary* dictionary) {
  system_hotkeys_.clear();

  if (!dictionary)
    return false;

  NSDictionary* user_hotkey_dictionaries =
      DictionaryForKey(dictionary, @"AppleSymbolicHotKeys");
  if (!user_hotkey_dictionaries)
    return false;

  // Start with a dictionary of default OS X hotkeys that are not necessarily
  // listed in com.apple.symbolichotkeys.plist, but should still be handled as
  // reserved.
  // If the user has overridden or disabled any of these hotkeys,
  // -NSMutableDictionary addEntriesFromDictionary:] will ensure that the new
  // values are used.
  // See https://crbug.com/145062#c8
  base::scoped_nsobject<NSMutableDictionary> hotkey_dictionaries([@{
    // Default Window switch key binding: Command + `
    // Note: The first parameter @96 is not used by |SystemHotkeyMap|.
    @"27" : @{
      @"enabled" : @YES,
      @"value" : @{
        @"type" : @"standard",
        @"parameters" :
            @[ @96 /* unused */, @(kVK_ANSI_Grave), @(NSCommandKeyMask) ],
      }
    }
  } mutableCopy]);
  [hotkey_dictionaries addEntriesFromDictionary:user_hotkey_dictionaries];

  for (NSString* hotkey_system_effect in [hotkey_dictionaries allKeys]) {
    if (![hotkey_system_effect isKindOfClass:[NSString class]])
      continue;

    NSDictionary* hotkey_dictionary =
        [hotkey_dictionaries objectForKey:hotkey_system_effect];
    if (![hotkey_dictionary isKindOfClass:[NSDictionary class]])
      continue;

    NSNumber* enabled = NumberForKey(hotkey_dictionary, @"enabled");
    if (!enabled || enabled.boolValue == NO)
      continue;

    NSDictionary* value = DictionaryForKey(hotkey_dictionary, @"value");
    if (!value)
      continue;

    NSString* type = StringForKey(value, @"type");
    if (!type || ![type isEqualToString:@"standard"])
      continue;

    NSArray* parameters = ArrayForKey(value, @"parameters");
    if (!parameters || [parameters count] != 3)
      continue;

    NSNumber* key_code = [parameters objectAtIndex:1];
    if (![key_code isKindOfClass:[NSNumber class]])
      continue;

    NSNumber* modifiers = [parameters objectAtIndex:2];
    if (![modifiers isKindOfClass:[NSNumber class]])
      continue;

    ReserveHotkey(key_code.unsignedShortValue,
                  modifiers.unsignedIntegerValue,
                  hotkey_system_effect);
  }

  return true;
}

bool SystemHotkeyMap::IsEventReserved(NSEvent* event) const {
  return IsHotkeyReserved(event.keyCode, event.modifierFlags);
}

bool SystemHotkeyMap::IsHotkeyReserved(unsigned short key_code,
                                       NSUInteger modifiers) const {
  modifiers &= NSDeviceIndependentModifierFlagsMask;
  std::vector<SystemHotkey>::const_iterator it;
  for (it = system_hotkeys_.begin(); it != system_hotkeys_.end(); ++it) {
    if (it->key_code == key_code && it->modifiers == modifiers)
      return true;
  }
  return false;
}

void SystemHotkeyMap::ReserveHotkey(unsigned short key_code,
                                    NSUInteger modifiers,
                                    NSString* system_effect) {
  ReserveHotkey(key_code, modifiers);

  // If a hotkey exists for toggling through the windows of an application, then
  // adding shift to that hotkey toggles through the windows backwards.
  if ([system_effect isEqualToString:@"27"])
    ReserveHotkey(key_code, modifiers | NSShiftKeyMask);
}

void SystemHotkeyMap::ReserveHotkey(unsigned short key_code,
                                    NSUInteger modifiers) {
  // Hotkeys require at least one of control, command, or alternate keys to be
  // down.
  NSUInteger required_modifiers =
      NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask;
  if ((modifiers & required_modifiers) == 0)
    return;

  SystemHotkey hotkey;
  hotkey.key_code = key_code;
  hotkey.modifiers = modifiers;
  system_hotkeys_.push_back(hotkey);
}

}  // namespace content
