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

#ifndef COMPONENTS_EXO_SEAT_H_
#define COMPONENTS_EXO_SEAT_H_

#include <array>

#include "ash/ime/ime_controller_impl.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "components/exo/data_source_observer.h"
#include "components/exo/key_state.h"
#include "components/exo/ui_lock_controller.h"
#include "ui/aura/client/drag_drop_delegate.h"
#include "ui/aura/client/focus_change_observer.h"
#include "ui/base/clipboard/clipboard_observer.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom-forward.h"
#include "ui/events/event_handler.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/platform/platform_event_observer.h"

namespace ui {
class KeyEvent;
}  // namespace ui

namespace exo {
class DragDropOperation;
class DataExchangeDelegate;
class Pointer;
class ScopedDataSource;
class SeatObserver;
class Surface;
class XkbTracker;

// Seat object represent a group of input devices such as keyboard, pointer and
// touch devices and keeps track of input focus.
class Seat : public aura::client::FocusChangeObserver,
             public ui::PlatformEventObserver,
             public ui::EventHandler,
             public ui::ClipboardObserver,
             public ash::ImeController::Observer,
             public DataSourceObserver {
 public:
  explicit Seat(std::unique_ptr<DataExchangeDelegate> delegate);
  Seat();
  Seat(const Seat&) = delete;
  Seat& operator=(const Seat&) = delete;
  ~Seat() override;

  using FocusChangedCallback =
      base::RepeatingCallback<void(Surface*, Surface*, bool)>;

  void Shutdown();

  // Registers the observer with the given priority.
  // Observers with smaller priority value will be called earlier.
  // The same observer should not be registered twice or more even with
  // different priorities. If the order of observer invocations with
  // the same priority is implementation dependent.
  // The priority must be in a range of
  // [0, kMaxObserverPriority] inclusive.
  void AddObserver(SeatObserver* observer, int priority);

  // Unregisters the observer.
  void RemoveObserver(SeatObserver* observer);

  // Returns true if the given priority can be used for AddObserver.
  static constexpr bool IsValidObserverPriority(int priority) {
    return 0 <= priority && priority <= kMaxObserverPriority;
  }

  // Nontify observers when a new surface is created.
  void NotifySurfaceCreated(Surface* surface);

  // Notify observers about pointer capture state changes.
  void NotifyPointerCaptureEnabled(Pointer* pointer,
                                   aura::Window* capture_window);
  void NotifyPointerCaptureDisabled(Pointer* pointer,
                                    aura::Window* capture_window);

  // Returns currently focused surface. This is virtual so that we can override
  // the behavior for testing.
  virtual Surface* GetFocusedSurface();

  // Returns currently pressed keys.
  const base::flat_map<PhysicalCode, base::flat_set<KeyState>>& pressed_keys()
      const {
    return pressed_keys_;
  }

  const XkbTracker* xkb_tracker() const { return xkb_tracker_.get(); }

  DataExchangeDelegate* data_exchange_delegate() {
    return data_exchange_delegate_.get();
  }

  // Returns physical code for the currently processing event.
  const PhysicalCode& physical_code_for_currently_processing_event() const {
    return physical_code_for_currently_processing_event_;
  }

  // Sets clipboard data from |source|.
  void SetSelection(DataSource* source);

  void StartDrag(DataSource* source,
                 Surface* origin,
                 Surface* icon,
                 ui::mojom::DragEventSource event_source);

  // Abort any drag operations that haven't been started yet.
  void AbortPendingDragOperation();

  // Returns true if the drag and drop has been started (which happens
  // asynchronously) and hasn't been fully finished yet. This can return true
  // even if ash's DND session is finished because wayland's dnd finished event
  // is sent asynchronosly.
  bool IsDragDropOperationInProgress() const;

  // Overridden from aura::client::FocusChangeObserver:
  void OnWindowFocused(aura::Window* gained_focus,
                       aura::Window* lost_focus) override;

  // Overridden from ui::PlatformEventObserver:
  void WillProcessEvent(const ui::PlatformEvent& event) override;
  void DidProcessEvent(const ui::PlatformEvent& event) override;

  // Overridden from ui::EventHandler:
  void OnKeyEvent(ui::KeyEvent* event) override;

  // Overridden from ui::ClipboardObserver:
  void OnClipboardDataChanged() override;

  // Overridden from DataSourceObserver:
  void OnDataSourceDestroying(DataSource* source) override;

  // Overridden from ash::ImeController::Observer:
  void OnCapsLockChanged(bool enabled) override;
  void OnKeyboardLayoutNameChanged(const std::string& layout_name) override;

  UILockController* GetUILockControllerForTesting();

  void set_physical_code_for_currently_processing_event_for_testing(
      PhysicalCode physical_code_for_currently_processing_event) {
    physical_code_for_currently_processing_event_ =
        physical_code_for_currently_processing_event;
  }

  base::WeakPtr<DragDropOperation> get_drag_drop_operation_for_testing() {
    return drag_drop_operation_;
  }

  bool was_shutdown() const { return was_shutdown_; }

  base::WeakPtr<Seat> GetWeakPtr();

 private:
  class RefCountedScopedClipboardWriter;

  // Called when data is read from FD passed from a client.
  // |data| is read data. |source| is source of the data, or nullptr if
  // DataSource has already been destroyed.
  void OnTextRead(scoped_refptr<RefCountedScopedClipboardWriter> writer,
                  base::OnceClosure callback,
                  const std::string& mime_type,
                  std::u16string data);
  void OnRTFRead(scoped_refptr<RefCountedScopedClipboardWriter> writer,
                 base::OnceClosure callback,
                 const std::string& mime_type,
                 const std::vector<uint8_t>& data);
  void OnHTMLRead(scoped_refptr<RefCountedScopedClipboardWriter> writer,
                  base::OnceClosure callback,
                  const std::string& mime_type,
                  std::u16string data);
  void OnImageRead(scoped_refptr<RefCountedScopedClipboardWriter> writer,
                   base::OnceClosure callback,
                   const std::string& mime_type,
                   const std::vector<uint8_t>& data);
  void OnImageDecoded(base::OnceClosure callback,
                      scoped_refptr<RefCountedScopedClipboardWriter> writer,
                      const SkBitmap& bitmap);
  void OnFilenamesRead(ui::EndpointType source,
                       scoped_refptr<RefCountedScopedClipboardWriter> writer,
                       base::OnceClosure callback,
                       const std::string& mime_type,
                       const std::vector<uint8_t>& data);
  void OnWebCustomDataRead(
      scoped_refptr<RefCountedScopedClipboardWriter> writer,
      base::OnceClosure callback,
      const std::string& mime_type,
      const std::vector<uint8_t>& data);

  void OnAllReadsFinished(
      scoped_refptr<RefCountedScopedClipboardWriter> writer);

  // Max value of SeatObserver's priority. Both side are inclusive.
  static constexpr int kMaxObserverPriority = 1;

  // Map from priority to a list of SeatObserver pointers.
  std::array<base::ObserverList<SeatObserver>, kMaxObserverPriority + 1>
      priority_observer_list_;

  // The platform code is the key in this map as it represents the physical
  // key that was pressed. The value is a set of potentially rewritten key codes
  // that the physical key press generated.
  base::flat_map<PhysicalCode, base::flat_set<KeyState>> pressed_keys_;
  PhysicalCode physical_code_for_currently_processing_event_ =
      ui::DomCode::NONE;

  // Data source being used as a clipboard content.
  std::unique_ptr<ScopedDataSource> selection_source_;

  // TODO(oshima): Move this to DataDevice.
  base::WeakPtr<DragDropOperation> drag_drop_operation_;

  // True while Seat is updating clipboard data to selection source.
  bool changing_clipboard_data_to_selection_source_;

  bool was_shutdown_ = false;

  std::unique_ptr<UILockController> ui_lock_controller_;
  std::unique_ptr<XkbTracker> xkb_tracker_;

  std::unique_ptr<DataExchangeDelegate> data_exchange_delegate_;
  base::WeakPtrFactory<Seat> weak_ptr_factory_{this};
};

}  // namespace exo

#endif  // COMPONENTS_EXO_SEAT_H_
