// Copyright 2016 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.

#include "chrome/browser/ui/webui/settings/chromeos/device_keyboard_handler.h"

#include "ash/public/cpp/keyboard_shortcut_viewer.h"
#include "ash/public/cpp/tablet_mode.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/values.h"
#include "chromeos/constants/chromeos_switches.h"
#include "content/public/browser/web_ui.h"
#include "content/public/common/service_manager_connection.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/chromeos/events/event_rewriter_chromeos.h"
#include "ui/chromeos/events/keyboard_layout_util.h"

namespace {

struct KeyboardsStateResult {
  bool has_internal_keyboard = false;
  bool has_external_apple_keyboard = false;
  bool has_external_chromeos_keyboard = false;
  bool has_external_generic_keyboard = false;
};

KeyboardsStateResult GetKeyboardsState() {
  KeyboardsStateResult result;
  for (const ui::InputDevice& keyboard :
       ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
    switch (ui::EventRewriterChromeOS::GetDeviceType(keyboard)) {
      case ui::EventRewriterChromeOS::kDeviceInternalKeyboard:
        result.has_internal_keyboard = true;
        break;
      case ui::EventRewriterChromeOS::kDeviceExternalAppleKeyboard:
        result.has_external_apple_keyboard = true;
        break;
      case ui::EventRewriterChromeOS::kDeviceExternalChromeOsKeyboard:
        result.has_external_chromeos_keyboard = true;
        break;
      case ui::EventRewriterChromeOS::kDeviceExternalGenericKeyboard:
      case ui::EventRewriterChromeOS::kDeviceExternalUnknown:
        result.has_external_generic_keyboard = true;
        break;
      case ui::EventRewriterChromeOS::kDeviceHotrodRemote:
      case ui::EventRewriterChromeOS::kDeviceVirtualCoreKeyboard:
      case ui::EventRewriterChromeOS::kDeviceUnknown:
        break;
    }
  }

  return result;
}

}  // namespace

namespace chromeos {
namespace settings {

const char KeyboardHandler::kShowKeysChangedName[] = "show-keys-changed";

void KeyboardHandler::TestAPI::Initialize() {
  base::ListValue args;
  handler_->HandleInitialize(&args);
}

KeyboardHandler::KeyboardHandler() = default;
KeyboardHandler::~KeyboardHandler() = default;

void KeyboardHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "initializeKeyboardSettings",
      base::BindRepeating(&KeyboardHandler::HandleInitialize,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "showKeyboardShortcutViewer",
      base::BindRepeating(&KeyboardHandler::HandleShowKeyboardShortcutViewer,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "initializeKeyboardWatcher",
      base::BindRepeating(&KeyboardHandler::HandleKeyboardChange,
                          base::Unretained(this)));
}

void KeyboardHandler::OnJavascriptAllowed() {
  observer_.Add(ui::DeviceDataManager::GetInstance());
}

void KeyboardHandler::OnJavascriptDisallowed() {
  observer_.RemoveAll();
}

void KeyboardHandler::OnInputDeviceConfigurationChanged(
    uint8_t input_device_types) {
  if (input_device_types & ui::InputDeviceEventObserver::kKeyboard) {
    AllowJavascript();
    UpdateShowKeys();
    UpdateKeyboards();
  }
}

void KeyboardHandler::HandleInitialize(const base::ListValue* args) {
  AllowJavascript();
  UpdateShowKeys();
  UpdateKeyboards();
}

void KeyboardHandler::HandleShowKeyboardShortcutViewer(
    const base::ListValue* args) const {
  ash::ToggleKeyboardShortcutViewer();
}

void KeyboardHandler::HandleKeyboardChange(const base::ListValue* args) {
  AllowJavascript();
  UpdateKeyboards();
}

void KeyboardHandler::UpdateKeyboards() {
  bool physical_keyboard = false;
  // In tablet mode, physical keybards are disabled / ignored.
  if (!ash::TabletMode::Get() || !ash::TabletMode::Get()->InTabletMode()) {
    physical_keyboard = true;
  }
  if (!physical_keyboard) {
    for (const ui::InputDevice& keyboard :
         ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
      if (keyboard.type != ui::InputDeviceType::INPUT_DEVICE_INTERNAL) {
        physical_keyboard = true;
        break;
      }
    }
  }
  FireWebUIListener("has-hardware-keyboard", base::Value(physical_keyboard));
}

void KeyboardHandler::UpdateShowKeys() {
  // kHasChromeOSKeyboard will be unset on Chromebooks that have standalone Caps
  // Lock keys.
  const KeyboardsStateResult keyboards_state = GetKeyboardsState();
  const bool has_caps_lock = keyboards_state.has_external_apple_keyboard ||
                             keyboards_state.has_external_generic_keyboard ||
                             !base::CommandLine::ForCurrentProcess()->HasSwitch(
                                 chromeos::switches::kHasChromeOSKeyboard);

  base::Value keyboard_params(base::Value::Type::DICTIONARY);
  keyboard_params.SetKey("showCapsLock", base::Value(has_caps_lock));
  keyboard_params.SetKey(
      "showExternalMetaKey",
      base::Value(keyboards_state.has_external_generic_keyboard));
  keyboard_params.SetKey(
      "showAppleCommandKey",
      base::Value(keyboards_state.has_external_apple_keyboard));
  keyboard_params.SetKey("hasInternalKeyboard",
                         base::Value(keyboards_state.has_internal_keyboard));

  const bool show_assistant_key_settings = ui::DeviceKeyboardHasAssistantKey();
  keyboard_params.SetKey("hasAssistantKey",
                         base::Value(show_assistant_key_settings));

  FireWebUIListener(kShowKeysChangedName, keyboard_params);
}

}  // namespace settings
}  // namespace chromeos
