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

#ifndef CHROME_BROWSER_CHROMEOS_EVENTS_EVENT_REWRITER_H_
#define CHROME_BROWSER_CHROMEOS_EVENTS_EVENT_REWRITER_H_

#include <map>
#include <set>
#include <string>

#include "base/compiler_specific.h"
#include "base/containers/hash_tables.h"
#include "base/memory/scoped_ptr.h"
#include "ui/events/event.h"
#include "ui/events/event_rewriter.h"

#if defined(USE_X11)
#include "chrome/browser/chromeos/device_hierarchy_observer.h"
#include "ui/events/platform/platform_event_observer.h"
typedef union _XEvent XEvent;
#endif

class PrefService;

namespace chromeos {
namespace input_method {
class ImeKeyboard;
}

// EventRewriter makes various changes to keyboard-related events,
// including KeyEvents and some other events with keyboard modifier flags:
// - maps modifiers keys (Control, Alt, Search, Caps, Diamond) according
//   to user preferences;
// - maps Command to Control on Apple keyboards;
// - converts numeric pad editing keys to their numeric forms;
// - converts top-row function keys to special keys where necessary;
// - handles various key combinations like Search+Backspace -> Delete
//   and Search+number to Fnumber;
// - handles key/pointer combinations like Alt+Button1 -> Button3.
class EventRewriter
    :
#if defined(USE_X11)
      public DeviceHierarchyObserver,
      public ui::PlatformEventObserver,
#endif
      public ui::EventRewriter {
 public:
  enum DeviceType {
    kDeviceUnknown = 0,
    kDeviceAppleKeyboard,
  };

  EventRewriter();
  virtual ~EventRewriter();

  // Calls DeviceAddedInternal.
  DeviceType DeviceAddedForTesting(int device_id,
                                   const std::string& device_name);

  // Calls RewriteLocatedEvent().
  void RewriteLocatedEventForTesting(const ui::Event& event, int* flags);

#if defined(USE_X11)
  const std::map<int, DeviceType>& device_id_to_type_for_testing() const {
    return device_id_to_type_;
  }
#endif

  void set_last_device_id_for_testing(int device_id) {
    last_device_id_ = device_id;
  }
  void set_pref_service_for_testing(const PrefService* pref_service) {
    pref_service_for_testing_ = pref_service;
  }
  void set_ime_keyboard_for_testing(
      chromeos::input_method::ImeKeyboard* ime_keyboard) {
    ime_keyboard_for_testing_ = ime_keyboard;
  }

  // EventRewriter overrides:
  virtual ui::EventRewriteStatus RewriteEvent(
      const ui::Event& event,
      scoped_ptr<ui::Event>* rewritten_event) OVERRIDE;
  virtual ui::EventRewriteStatus NextDispatchEvent(
      const ui::Event& last_event,
      scoped_ptr<ui::Event>* new_event) OVERRIDE;

#if defined(USE_X11)
  // ui::PlatformEventObserver:
  virtual void WillProcessEvent(const ui::PlatformEvent& event) OVERRIDE;
  virtual void DidProcessEvent(const ui::PlatformEvent& event) OVERRIDE;

  // DeviceHierarchyObserver:
  virtual void DeviceHierarchyChanged() OVERRIDE;
  virtual void DeviceAdded(int device_id) OVERRIDE;
  virtual void DeviceRemoved(int device_id) OVERRIDE;
#endif

 private:
  // Things that internal rewriter phases can change about an Event.
  struct MutableKeyState {
    int flags;
    ui::KeyboardCode key_code;
  };

  // Tables of direct remappings for |RewriteWithKeyboardRemappingsByKeyCode()|.
  struct KeyboardRemapping {
    ui::KeyboardCode input_key_code;
    int input_flags;
    ui::KeyboardCode output_key_code;
    int output_flags;
  };

#if defined(USE_X11)
  void DeviceKeyPressedOrReleased(int device_id);
#endif

  // Returns the PrefService that should be used.
  const PrefService* GetPrefService() const;

  // Checks the type of the |device_name|, and inserts a new entry to
  // |device_id_to_type_|.
  DeviceType DeviceAddedInternal(int device_id, const std::string& device_name);

  // Returns true if |last_device_id_| is Apple's.
  bool IsAppleKeyboard() const;

  // Returns true if the target for |event| would prefer to receive raw function
  // keys instead of having them rewritten into back, forward, brightness,
  // volume, etc. or if the user has specified that they desire top-row keys to
  // be treated as function keys globally.
  bool TopRowKeysAreFunctionKeys(const ui::KeyEvent& event) const;

  // Given modifier flags |original_flags|, returns the remapped modifiers
  // according to user preferences and/or event properties.
  int GetRemappedModifierMasks(const PrefService& pref_service,
                               const ui::Event& event,
                               int original_flags) const;

  // Given a set of KeyboardRemapping structs, it finds a matching struct
  // if possible, and updates the remapped event values. Returns true if a
  // remapping was found and remapped values were updated.
  bool RewriteWithKeyboardRemappingsByKeyCode(
      const KeyboardRemapping* remappings,
      size_t num_remappings,
      const MutableKeyState& input,
      MutableKeyState* remapped_state);

  // Rewrite a particular kind of event.
  ui::EventRewriteStatus RewriteKeyEvent(
      const ui::KeyEvent& key_event,
      scoped_ptr<ui::Event>* rewritten_event);
  ui::EventRewriteStatus RewriteMouseEvent(
      const ui::MouseEvent& mouse_event,
      scoped_ptr<ui::Event>* rewritten_event);
  ui::EventRewriteStatus RewriteTouchEvent(
      const ui::TouchEvent& touch_event,
      scoped_ptr<ui::Event>* rewritten_event);

  // Rewriter phases. These can inspect the original |event|, but operate using
  // the current |state|, which may have been modified by previous phases.
  void RewriteModifierKeys(const ui::KeyEvent& event, MutableKeyState* state);
  void RewriteNumPadKeys(const ui::KeyEvent& event, MutableKeyState* state);
  void RewriteExtendedKeys(const ui::KeyEvent& event, MutableKeyState* state);
  void RewriteFunctionKeys(const ui::KeyEvent& event, MutableKeyState* state);
  void RewriteLocatedEvent(const ui::Event& event, int* flags);

  // A set of device IDs whose press event has been rewritten.
  std::set<int> pressed_device_ids_;

  std::map<int, DeviceType> device_id_to_type_;
  int last_device_id_;

  chromeos::input_method::ImeKeyboard* ime_keyboard_for_testing_;
  const PrefService* pref_service_for_testing_;

  DISALLOW_COPY_AND_ASSIGN(EventRewriter);
};

}  // namespace chromeos

#endif  // CHROME_BROWSER_CHROMEOS_EVENTS_EVENT_REWRITER_H_