// Aseprite
// Copyright (C) 2018-2025  Igara Studio S.A.
// Copyright (C) 2001-2018  David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

#ifndef APP_UI_KEY_H_INCLUDED
#define APP_UI_KEY_H_INCLUDED
#pragma once

#include "app/commands/params.h"
#include "app/ui/key_context.h"
#include "base/convert_to.h"
#include "base/vector2d.h"
#include "ui/shortcut.h"

#include <memory>
#include <utility>
#include <vector>

namespace ui {
class Message;
}

namespace app {
class Command;
class KeyboardShortcuts;

namespace tools {
class Tool;
}

enum class KeySource { Original, ExtensionDefined, UserDefined };

enum class KeyType {
  Command,
  Tool,
  Quicktool,
  Action,
  WheelAction,
  DragAction,
};

// TODO This should be called "KeyActionModifier" or something similar
enum class KeyAction {
  None = 0x00000000,
  CopySelection = 0x00000001,
  SnapToGrid = 0x00000002,
  AngleSnap = 0x00000004,
  MaintainAspectRatio = 0x00000008,
  LockAxis = 0x00000010,
  AddSelection = 0x00000020,
  SubtractSelection = 0x00000040,
  IntersectSelection = 0x00000080,
  AutoSelectLayer = 0x00000100,
  LeftMouseButton = 0x00000200,
  RightMouseButton = 0x00000400,
  StraightLineFromLastPoint = 0x00000800,
  MoveOrigin = 0x00001000,
  SquareAspect = 0x00002000,
  DrawFromCenter = 0x00004000,
  ScaleFromCenter = 0x00008000,
  AngleSnapFromLastPoint = 0x00010000,
  RotateShape = 0x00020000,
  FineControl = 0x00040000,
  CornerRadius = 0x00080000,
};

enum class WheelAction {
  None,
  Zoom,
  VScroll,
  HScroll,
  FgColor,
  BgColor,
  FgTile,
  BgTile,
  Frame,
  BrushSize,
  BrushAngle,
  ToolSameGroup,
  ToolOtherGroup,
  Layer,
  InkType,
  InkOpacity,
  LayerOpacity,
  CelOpacity,
  Alpha,
  HslHue,
  HslSaturation,
  HslLightness,
  HsvHue,
  HsvSaturation,
  HsvValue,

  // Range
  First = Zoom,
  Last = HsvValue,
};

inline KeyAction operator&(KeyAction a, KeyAction b)
{
  return KeyAction(int(a) & int(b));
}

// This is a ui::Shortcut wrapper (just one key shortcut) defined by
// the app, an extension, or the user (KeySource).
class AppShortcut : public ui::Shortcut {
public:
  AppShortcut(const KeySource source, const ui::Shortcut& shortcut)
    : Shortcut(shortcut)
    , m_source(source)
  {
  }

  KeySource source() const { return m_source; }
  const ui::Shortcut& shortcut() const { return *this; }

  // bool operator==(const AppShortcut& other) const { return shortcut.operator==(other.shortcut); }
  // bool operator!=(const AppShortcut& other) const { return shortcut.operator!=(other.shortcut); }

  // Returns true if this AppShortcut is better for the current
  // context, compared to another shortcut.
  bool fitsBetterThan(KeyContext currentContext,
                      KeyContext thisShortcutContext,
                      KeyContext otherShortcutContext,
                      const AppShortcut& otherShortcut) const;

private:
  KeySource m_source;
};

using AppShortcuts = ui::ShortcutsT<AppShortcut>;

class Key;
using KeyPtr = std::shared_ptr<Key>;
using Keys = std::vector<KeyPtr>;
using DragVector = base::Vector2d<double>;

// A set of key shortcuts (AppShortcuts) associated to one command,
// tool, or specific action.
class Key {
public:
  Key(const Key& key);
  Key(Command* command, const Params& params, const KeyContext keyContext);
  Key(const KeyType type, tools::Tool* tool);
  explicit Key(const KeyAction action, const KeyContext keyContext);
  explicit Key(const WheelAction action);
  static KeyPtr MakeDragAction(WheelAction dragAction);

  KeyType type() const { return m_type; }
  const AppShortcuts& shortcuts() const;
  const AppShortcuts& addsKeys() const { return m_adds; }
  const AppShortcuts& delsKeys() const { return m_dels; }

  void add(const ui::Shortcut& shortcut, KeySource source, KeyboardShortcuts& globalKeys);

  bool fitsContext(KeyContext keyContext) const;
  const AppShortcut* isPressed(const ui::Message* msg, KeyContext keyContext) const;
  const AppShortcut* isPressed(const ui::Message* msg) const;
  const AppShortcut* isPressed() const;
  bool isLooselyPressed() const;
  bool isCommandListed() const;

  bool hasShortcut(const ui::Shortcut& shortcut) const;
  bool hasUserDefinedShortcuts() const;

  // The KeySource indicates from where the key was disabled
  // (e.g. if it was removed from an extension-defined file, or from
  // user-defined).
  void disableShortcut(const ui::Shortcut& shortcut, KeySource source);

  // Resets user shortcuts to the original & extension-defined ones.
  void reset();

  void copyOriginalToUser();

  // for KeyType::Command
  Command* command() const { return m_command; }
  const Params& params() const { return m_params; }
  KeyContext keycontext() const { return m_keycontext; }
  // for KeyType::Tool or Quicktool
  tools::Tool* tool() const { return m_tool; }
  // for KeyType::Action
  KeyAction action() const { return m_action; }
  // for KeyType::WheelAction / KeyType::DragAction
  WheelAction wheelAction() const { return m_wheelAction; }
  // for KeyType::DragAction
  DragVector dragVector() const { return m_dragVector; }
  void setDragVector(const DragVector& v) { m_dragVector = v; }

  std::string triggerString() const;

private:
  KeyType m_type;
  AppShortcuts m_adds;
  AppShortcuts m_dels;
  // Final list of shortcuts after processing the
  // addition/deletion of extension-defined & user-defined keys.
  mutable std::unique_ptr<AppShortcuts> m_shortcuts;
  KeyContext m_keycontext;

  // for KeyType::Command
  Command* m_command = nullptr;
  Params m_params;

  tools::Tool* m_tool = nullptr; // for KeyType::Tool or Quicktool
  KeyAction m_action;            // for KeyType::Action
  WheelAction m_wheelAction;     // for KeyType::WheelAction / DragAction
  DragVector m_dragVector;       // for KeyType::DragAction
};

// Clears collection with strings that depends on the current
// language, so they can be reconstructed when they are needed with a
// new selected language.
void reset_key_tables_that_depends_on_language();

std::string key_tooltip(const char* str, const Key* key);
std::string convert_keycontext_to_user_friendly_string(KeyContext keyctx);

} // namespace app

namespace base {

template<>
app::KeyAction convert_to(const std::string& from);
template<>
std::string convert_to(const app::KeyAction& from);

template<>
app::WheelAction convert_to(const std::string& from);
template<>
std::string convert_to(const app::WheelAction& from);

template<>
app::KeyContext convert_to(const std::string& from);
template<>
std::string convert_to(const app::KeyContext& from);

} // namespace base

#endif
