// Copyright 2013 The Flutter 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 FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_

#include <windows.h>

#include <atomic>
#include <deque>
#include <functional>
#include <map>

#include "flutter/fml/macros.h"

namespace flutter {

// Handles keyboard and text messages on Win32.
//
// |KeyboardManager| consumes raw Win32 messages related to key and chars, and
// converts them to key calls (|WindowDelegate::OnKey|) and possibly text calls
// (|WindowDelegate::OnText|).
//
// |KeyboardManager| requires a |WindowDelegate| to define how to access Win32
// system calls (to allow mocking) and where to send the results of key calls
// and text calls to.
//
// Typically, |KeyboardManager| is owned by a |Window|, which also implements
// the window delegate. The key calls and text calls are forwarded to those of
// |Window|'s, and consequently, those of |FlutterWindowsView|'s.
//
// ## Terminology
//
// The keyboard system follows the following terminology instead of the
// inconsistent/incomplete one used by Win32:
//
//  * Message: An invocation of |WndProc|, which consists of an
//    action, an lparam, and a wparam.
//  * Action: The type of a message.
//  * Session: One to three messages that should be processed together, such
//    as a key down message followed by char messages.
//  * Event: A FlutterKeyEvent/ui.KeyData sent to the framework.
//  * Call: A call to |WindowDelegate::OnKey| or |WindowDelegate::OnText|,
//    which contains semi-processed messages.
class KeyboardManager {
 public:
  // Define how the keyboard manager accesses Win32 system calls (to allow
  // mocking) and sends key calls and text calls.
  //
  // Typically implemented by |Window|.
  class WindowDelegate {
   public:
    using KeyEventCallback = std::function<void(bool)>;

    virtual ~WindowDelegate() = default;

    // Called when text input occurs.
    virtual void OnText(const std::u16string& text) = 0;

    // Called when raw keyboard input occurs.
    //
    // The `callback` must be called exactly once.
    virtual void OnKey(int key,
                       int scancode,
                       int action,
                       char32_t character,
                       bool extended,
                       bool was_down,
                       KeyEventCallback callback) = 0;

    // Win32's PeekMessage.
    //
    // Used to process key messages.
    virtual BOOL Win32PeekMessage(LPMSG lpMsg,
                                  UINT wMsgFilterMin,
                                  UINT wMsgFilterMax,
                                  UINT wRemoveMsg) = 0;

    // Win32's MapVirtualKey(*, MAPVK_VK_TO_CHAR).
    //
    // Used to process key messages.
    virtual uint32_t Win32MapVkToChar(uint32_t virtual_key) = 0;

    // Win32's |SendMessage|.
    //
    // Used to synthesize key messages.
    virtual UINT Win32DispatchMessage(UINT Msg,
                                      WPARAM wParam,
                                      LPARAM lParam) = 0;
  };

  using KeyEventCallback = WindowDelegate::KeyEventCallback;

  explicit KeyboardManager(WindowDelegate* delegate);

  // Processes Win32 messages related to keyboard and text.
  //
  // All messages related to keyboard and text should be sent here without
  // pre-processing, including WM_{SYS,}KEY{DOWN,UP} and WM_{SYS,}{DEAD,}CHAR.
  // Other message types will trigger assertion error.
  //
  // |HandleMessage| returns true if Flutter keyboard system decides to handle
  // this message synchronously. It doesn't mean that the Flutter framework
  // handles it, which is reported asynchronously later. Not handling this
  // message here usually means that this message is a redispatched message,
  // but there are other rare cases too. |Window| should forward unhandled
  // messages to |DefWindowProc|.
  bool HandleMessage(UINT const message,
                     WPARAM const wparam,
                     LPARAM const lparam);

 protected:
  struct Win32Message {
    UINT action;
    WPARAM wparam;
    LPARAM lparam;

    bool IsHighSurrogate() const { return IS_HIGH_SURROGATE(wparam); }

    bool IsLowSurrogate() const { return IS_LOW_SURROGATE(wparam); }

    bool IsGeneralKeyDown() const {
      return action == WM_KEYDOWN || action == WM_SYSKEYDOWN;
    }
  };

  struct PendingEvent {
    WPARAM key;
    uint8_t scancode;
    UINT action;
    char32_t character;
    bool extended;
    bool was_down;

    std::vector<Win32Message> session;
  };

  virtual void RedispatchEvent(std::unique_ptr<PendingEvent> event);

 private:
  using OnKeyCallback =
      std::function<void(std::unique_ptr<PendingEvent>, bool)>;

  struct PendingText {
    bool ready;
    std::u16string content;
    bool placeholder = false;
  };

  // Resume processing the pending events.
  //
  // If there is at least one pending event and no event is being processed,
  // the oldest pending event will be handed over to |PerformProcessEvent|.
  // After the event is processed, the next pending event will be automatically
  // started, until there are no pending events left.
  //
  // Otherwise, this call is a no-op.
  void ProcessNextEvent();

  // Process an event and call `callback` when it's completed.
  //
  // The `callback` is constructed by |ProcessNextEvent| to start the next
  // event, and must be called exactly once.
  void PerformProcessEvent(std::unique_ptr<PendingEvent> event,
                           std::function<void()> callback);

  // Handle the result of |WindowDelegate::OnKey|, possibly dispatching the text
  // result to |WindowDelegate::OnText| and then redispatching.
  //
  // The `pending_text` is either a valid iterator of `pending_texts`, or its
  // end(). In the latter case, this OnKey message does not contain a text.
  void HandleOnKeyResult(std::unique_ptr<PendingEvent> event,
                         bool framework_handled);

  // Dispatch the text content of a |PendingEvent| to |WindowDelegate::OnText|.
  //
  // If the content is empty of invalid, |WindowDelegate::OnText| will not be
  // called.
  void DispatchText(const PendingEvent& event);

  // Returns the type of the next WM message.
  //
  // The parameters limits the range of interested messages. See Win32's
  // |PeekMessage| for information.
  //
  // If there's no message, returns 0.
  UINT PeekNextMessageType(UINT wMsgFilterMin, UINT wMsgFilterMax);

  // Find an event in the redispatch list that matches the given one.
  //
  // If an matching event is found, removes the matching event from the
  // redispatch list, and returns true. Otherwise, returns false;
  bool RemoveRedispatchedMessage(UINT action, WPARAM wparam, LPARAM lparam);

  WindowDelegate* window_delegate_;

  // Keeps track of all messages during the current session.
  //
  // At the end of a session, it is moved to the `PendingEvent`, which is
  // passed to `WindowDelegate::OnKey`.
  std::vector<Win32Message> current_session_;

  // Whether the last event is a CtrlLeft key down.
  //
  // This is used to resolve a corner case described in |IsKeyDownAltRight|.
  bool last_key_is_ctrl_left_down;

  // The scancode of the last met CtrlLeft down.
  //
  // This is used to resolve a corner case described in |IsKeyDownAltRight|.
  uint8_t ctrl_left_scancode;

  // Whether a CtrlLeft up should be synthesized upon the next AltRight up.
  //
  // This is used to resolve a corner case described in |IsKeyDownAltRight|.
  bool should_synthesize_ctrl_left_up;

  // Store the messages coming from |HandleMessage|.
  //
  // Only one message is processed at a time. The next one will not start
  // until the framework has responded to the previous message.
  std::deque<std::unique_ptr<PendingEvent>> pending_events_;

  // Whether a message is being processed.
  std::atomic<bool> processing_event_;

  // The queue of messages that have been redispatched to the system but have
  // not yet been received for a second time.
  std::deque<Win32Message> pending_redispatches_;

  FML_DISALLOW_COPY_AND_ASSIGN(KeyboardManager);
};

}  // namespace flutter

#endif  // FLUTTER_SHELL_PLATFORM_WINDOWS_KEYBOARD_MANAGER_H_
