// Aseprite UI Library
// Copyright (C) 2018-2023  Igara Studio S.A.
// Copyright (C) 2001-2018  David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.

#ifndef UI_MESSAGE_H_INCLUDED
#define UI_MESSAGE_H_INCLUDED
#pragma once

#include "base/paths.h"
#include "gfx/point.h"
#include "gfx/rect.h"
#include "ui/base.h"
#include "ui/keys.h"
#include "ui/message_type.h"
#include "ui/mouse_button.h"
#include "ui/pointer_type.h"

#include <functional>

namespace ui {

  class Display;
  class Timer;
  class Widget;

  class Message {
    enum Flags {
      FromFilter          = 1,  // Sent from pre-filter
      PropagateToChildren = 2,
      PropagateToParent   = 4,
    };
  public:
    Message(MessageType type,
            KeyModifiers modifiers = kKeyUninitializedModifier);
    virtual ~Message();

    MessageType type() const { return m_type; }
    Display* display() const { return m_display; }
    Widget* recipient() const { return m_recipient; }
    bool fromFilter() const { return hasFlag(FromFilter); }
    void setFromFilter(const bool state) { setFlag(FromFilter, state); }
    KeyModifiers modifiers() const { return m_modifiers; }
    bool shiftPressed() const { return (m_modifiers & kKeyShiftModifier) == kKeyShiftModifier; }
    bool ctrlPressed() const { return (m_modifiers & kKeyCtrlModifier) == kKeyCtrlModifier; }
    bool altPressed() const { return (m_modifiers & kKeyAltModifier) == kKeyAltModifier; }
    bool cmdPressed() const { return (m_modifiers & kKeyCmdModifier) == kKeyCmdModifier; }
    bool winPressed() const { return (m_modifiers & kKeyWinModifier) == kKeyWinModifier; }
    bool onlyShiftPressed() const { return m_modifiers == kKeyShiftModifier; }
    bool onlyCtrlPressed() const { return m_modifiers == kKeyCtrlModifier; }
    bool onlyAltPressed() const { return m_modifiers == kKeyAltModifier; }
    bool onlyCmdPressed() const { return m_modifiers == kKeyCmdModifier; }
    bool onlyWinPressed() const { return m_modifiers == kKeyWinModifier; }

    void setDisplay(Display* display);
    void setRecipient(Widget* widget);
    void removeRecipient(Widget* widget);

    bool propagateToChildren() const { return hasFlag(PropagateToChildren); }
    bool propagateToParent() const { return hasFlag(PropagateToParent); }
    void setPropagateToChildren(const bool state) { setFlag(PropagateToChildren, state); }
    void setPropagateToParent(const bool state) { setFlag(PropagateToParent, state); }

    Widget* commonAncestor() { return m_commonAncestor; }
    void setCommonAncestor(Widget* widget) { m_commonAncestor = widget; }

  private:
    bool hasFlag(const Flags flag) const {
      return (m_flags & flag) == flag;
    }
    void setFlag(const Flags flag, const bool state) {
      m_flags = (state ? (m_flags | flag):
                         (m_flags & ~flag));
    }

    MessageType m_type;       // Type of message
    int m_flags;              // Special flags for this message
    Display* m_display;
    Widget* m_recipient;      // Recipient of this message
    Widget* m_commonAncestor; // Common ancestor between the Leave <-> Enter messages
    KeyModifiers m_modifiers; // Key modifiers pressed when message was created
  };

  class CallbackMessage : public Message {
  public:
    CallbackMessage(std::function<void()>&& callback)
      : Message(kCallbackMessage)
      , m_callback(std::move(callback)) { }
    void call() {
      m_callback();
    }
  private:
    std::function<void()> m_callback;
  };

  class KeyMessage : public Message {
  public:
    KeyMessage(MessageType type,
               KeyScancode scancode,
               KeyModifiers modifiers,
               int unicodeChar,
               int repeat);

    KeyScancode scancode() const { return m_scancode; }
    int unicodeChar() const { return m_unicodeChar; }
    int repeat() const { return m_repeat; }
    bool isDeadKey() const { return m_isDead; }
    void setDeadKey(bool state) { m_isDead = state; }

  private:
    KeyScancode m_scancode;
    int m_unicodeChar;
    int m_repeat; // repeat=0 means the first time the key is pressed
    bool m_isDead;
  };

  class PaintMessage : public Message {
  public:
    PaintMessage(int count, const gfx::Rect& rect)
      : Message(kPaintMessage)
      , m_count(count)
      , m_rect(rect) {
    }

    int count() const { return m_count; }
    const gfx::Rect& rect() const { return m_rect; }

  private:
    int m_count;             // Cound=0 if it's last msg of draw-chain
    gfx::Rect m_rect;        // Area to draw
  };

  class MouseMessage : public Message {
  public:
    MouseMessage(MessageType type,
                 PointerType pointerType,
                 MouseButton button,
                 KeyModifiers modifiers,
                 const gfx::Point& pos,
                 const gfx::Point& wheelDelta = gfx::Point(0, 0),
                 bool preciseWheel = false,
                 float pressure = 0.0f)
      : Message(type, modifiers),
        m_pointerType(pointerType),
        m_button(button),
        m_pos(pos),
        m_wheelDelta(wheelDelta),
        m_preciseWheel(preciseWheel),
        m_pressure(pressure) {
    }

    // Copy other MouseMessage converting its type
    MouseMessage(MessageType type,
                 const MouseMessage& other,
                 const gfx::Point& newPosition)
      : Message(type, other.modifiers()),
        m_pointerType(other.pointerType()),
        m_button(other.button()),
        m_pos(newPosition),
        m_wheelDelta(other.wheelDelta()),
        m_preciseWheel(other.preciseWheel()),
        m_pressure(other.pressure()) {
    }

    PointerType pointerType() const { return m_pointerType; }
    MouseButton button() const { return m_button; }
    bool left() const { return (m_button == kButtonLeft); }
    bool right() const { return (m_button == kButtonRight); }
    bool middle() const { return (m_button == kButtonMiddle); }
    gfx::Point wheelDelta() const { return m_wheelDelta; }
    bool preciseWheel() const { return m_preciseWheel; }
    float pressure() const { return m_pressure; }

    const gfx::Point& position() const { return m_pos; }

    // Returns the mouse message position relative to the given
    // "anotherDisplay" (the m_pos field is relative to m_display).
    gfx::Point positionForDisplay(Display* anotherDisplay) const;

    // Absolute position of this message on the screen.
    gfx::Point screenPosition() const;

  private:
    PointerType m_pointerType;
    MouseButton m_button;       // Pressed button
    gfx::Point m_pos;           // Mouse position
    gfx::Point m_wheelDelta;    // Wheel axis variation
    bool m_preciseWheel;
    float m_pressure;
  };

  class TouchMessage : public Message {
  public:
    TouchMessage(MessageType type,
                 KeyModifiers modifiers,
                 const gfx::Point& pos,
                 double magnification)
      : Message(type, modifiers),
        m_pos(pos),
        m_magnification(magnification) {
    }

    // Copy other TouchMessage converting its type
    TouchMessage(MessageType type,
                 const TouchMessage& other,
                 const gfx::Point& newPosition)
      : Message(type, other.modifiers()),
        m_pos(newPosition),
        m_magnification(other.magnification()) {
    }

    const gfx::Point& position() const { return m_pos; }
    double magnification() const { return m_magnification; }

  private:
    gfx::Point m_pos;           // Mouse position
    double m_magnification;
  };

  class TimerMessage : public Message {
  public:
    TimerMessage(int count, Timer* timer)
      : Message(kTimerMessage), m_count(count), m_timer(timer) {
    }

    int count() const { return m_count; }
    Timer* timer() { return m_timer; }

    // Used by Manager::removeMessagesForTimer() to invalidate the
    // message. It's like removing the message from the queue, but
    // without touching the queue in case that we're iterating it
    // (which can happen if we remove the timer from a kTimerMessage
    // handler).
    void _resetTimer() { m_timer = nullptr; }

  private:
    int m_count;                    // Accumulated calls
    Timer* m_timer;                 // Timer handle
  };

  class DropFilesMessage : public Message {
  public:
    DropFilesMessage(const base::paths& files)
      : Message(kDropFilesMessage)
      , m_files(files) {
    }

    const base::paths& files() const { return m_files; }

  private:
    base::paths m_files;
  };

} // namespace ui

#endif
