#pragma once

#include <dwmipcpp/connection.hpp>

#include "modules/meta/event_module.hpp"
#include "modules/meta/input_handler.hpp"

POLYBAR_NS

namespace modules {
  class dwm_module : public event_module<dwm_module>, public input_handler {
   public:
    explicit dwm_module(const bar_settings&, string);

    using tag_mask_t = unsigned int;
    using window_t = unsigned int;

    /**
     * Represents the relevant states a tag can have
     */
    enum class state_t : uint8_t {
      FOCUSED,    ///< Monitor is selected and tag is selected, overrides all below
      URGENT,     ///< Tag is urgent, overrides all below
      UNFOCUSED,  ///< Monitor is not selected, but tag is selected
      VISIBLE,    ///< Tag is not selected, but occupied
      EMPTY       ///< Tag is unoccupied and unselected
    };

    /**
     * Associates important properties of a tag
     */
    struct tag_t {
      /**
       * Construct a tag_t object
       *
       * @param name Name of tag
       * @param tag_mask_t Bit mask that represents this tag
       * @param state Current state of the tag
       * @param label Label to use for building tag on bar
       */
      tag_t(string& name, tag_mask_t bit_mask, state_t state, label_t&& label)
          : name(name), bit_mask(bit_mask), state(state), label(forward<label_t>(label)) {}

      string name;
      tag_mask_t bit_mask;
      state_t state;
      label_t label;
    };

    auto stop() -> void override;
    auto has_event() -> bool;
    auto update() -> bool;
    auto build(builder* builder, const string& tag) const -> bool;

   protected:
    auto input(string&& cmd) -> bool override;

   private:
    static constexpr const char* DEFAULT_FORMAT_TAGS{"<label-tags> <label-layout> <label-title>"};
    static constexpr const char* DEFAULT_STATE_LABEL{"%name%"};

    /**
     * The tags label is replaced with the tags. Each tag is displayed using one
     * of the following labels based on the tag state:
     *   * label-focused
     *   * label-unfocused
     *   * label-visible
     *   * label-urgent
     *   * label-empty
     */
    static constexpr const char* TAG_LABEL_TAGS{"<label-tags>"};

    /**
     * The layout label is replaced by the current layout symbol
     */
    static constexpr const char* TAG_LABEL_LAYOUT{"<label-layout>"};

    /**
     * The title layout is replaced by the currently focused window title
     */
    static constexpr const char* TAG_LABEL_TITLE{"<label-title>"};

    /**
     * All input handler commands start with this
     */
    static constexpr const char* EVENT_PREFIX{"dwm-"};

    /**
     * DWM command for changing the view to a tag with the specified bit mask
     */
    static constexpr const char* CMD_TAG_VIEW{"view"};

    /**
     * DWM command for toggling the selected state of a tag with the specified
     * bit mask
     */
    static constexpr const char* CMD_TAG_TOGGLE_VIEW{"toggleview"};

    /**
     * DWM command for setting the layout to a layout specified by the address
     */
    static constexpr const char* CMD_LAYOUT_SET{"setlayoutsafe"};

    /**
     * Called by has_event on layout changes. This updates the layout label
     *
     * @param ev Event data
     */
    void on_layout_change(const dwmipc::LayoutChangeEvent& ev);

    /**
     * Called by has_event when a new monitor is in focus. This updates
     * m_active_mon to keep track of the currently active monitor.
     *
     * @param ev Event data
     */
    void on_monitor_focus_change(const dwmipc::MonitorFocusChangeEvent& ev);

    /**
     * Called by has_event when any of the tag states change. This updates the
     * m_tags array and their states/labels.
     *
     * @param ev Event data
     */
    void on_tag_change(const dwmipc::TagChangeEvent& ev);

    /**
     * Called by has_event when a new client is in focus. This updates
     * m_focused_client_id and updates the title label
     *
     * @param ev Event data
     */
    void on_client_focus_change(const dwmipc::ClientFocusChangeEvent& ev);

    /**
     * Called by has_event when the title of the currently focused window
     * changes. This updates the title label.
     *
     * @param ev Event data
     */
    void on_focused_title_change(const dwmipc::FocusedTitleChangeEvent& ev);

    /**
     * Get a list of monitors from dwm, store them in m_monitors, and update the
     * pointers to the active monitor and bar monitor.
     *
     * @param ev Event data
     */
    void update_monitor_ref();

    /**
     * Update the labels for each tag based on their state
     */
    void update_tag_labels();

    /**
     * Get the window title of the currently focused client from dwm and update
     * the title label
     */
    void update_title_label();

    /**
     * Translate the tag's tag states to a state_t enum value
     *
     * @param bit_mask Bit mask of the tag
     *
     * @return state_t enum value representing the state of the tag
     */
    auto get_state(tag_mask_t bit_mask) const -> state_t;

    /**
     * Get the address to the layout represented by the symbol.
     */
    auto find_layout(const string& sym) const -> const dwmipc::Layout*;

    /**
     * Get the address to the layout represented by the address.
     */
    auto find_layout(uintptr_t addr) const -> const dwmipc::Layout*;

    /**
     * Get the address of the next layout in m_layouts.
     *
     * @param layout Address of the current layout
     * @param wrap True to wrap around the array, false to return the same
     *   layout if the next layout does not exist.
     */
    auto next_layout(const dwmipc::Layout& layout, bool wrap) const -> const dwmipc::Layout*;

    /**
     * Get the address of the previous layout in m_layouts.
     *
     * @param layout Address of the current layout
     * @param wrap True to wrap around the array, false to return the same
     *   layout if the next layout does not exist.
     */
    auto prev_layout(const dwmipc::Layout& layout, bool wrap) const -> const dwmipc::Layout*;

    /**
     * Check if the command matches the specified IPC command name and if so,
     * parse and send the command to dwm
     *
     * @param cmd The command string given by dwm_modue::input
     * @param ipc_cmd The name of dwm IPC command to check for
     *
     * @return true if the command matched, was succesfully parsed, and sent to
     *   dwm, false otherwise
     */
    auto check_send_cmd(string cmd, const string& ipc_cmd) -> bool;

    /**
     * Helper function to build cmd string
     *
     * @param ipc_cmd The dwm IPC command name
     * @param arg The argument to the dwm command
     */
    auto static build_cmd(const char* ipc_cmd, const string& arg) -> string;

    /**
     * Attempt to connect to any disconnected dwm sockets. Catch errors.
     */
    auto reconnect_dwm() -> bool;

    /**
     * If true, enables the click handlers for the tags
     */
    bool m_tags_click{true};

    /**
     * If true, enables the click handlers for the layout label
     */
    bool m_layout_click{true};

    /**
     * If true, scrolling the layout cycle through available layouts
     */
    bool m_layout_scroll{true};

    /**
     * If true, scrolling the layout will wrap around to the beginning
     */
    bool m_layout_wrap{true};

    /**
     * If true, scrolling the layout will cycle layouts in the reverse direction
     */
    bool m_layout_reverse{false};

    /**
     * If the layout symbol is clicked on, it will set the layout represented by
     * this symbol. The default is monocle mode [M].
     */
    string m_secondary_layout_symbol{"[M]"};

    /**
     * Holds the address to the secondary layout specified by the secondary
     * layout symbol
     */
    const dwmipc::Layout* m_secondary_layout = nullptr;

    /**
     * Holds the address to the current layout
     */
    const dwmipc::Layout* m_current_layout = nullptr;

    /**
     * Holds the address to the default layout
     */
    const dwmipc::Layout* m_default_layout = nullptr;

    /**
     * Holds the address to the currently active monitor in the m_monitors array
     */
    const dwmipc::Monitor* m_active_mon = nullptr;

    /**
     * Holds the address to the bar monitor in the m_monitors array
     */
    const dwmipc::Monitor* m_bar_mon = nullptr;

    /**
     * XID of the currently focused client
     */
    window_t m_focused_client_id = 0;

    /**
     * Current layout symbol
     */
    label_t m_layout_label;

    /**
     * Inserted between tags
     */
    label_t m_seperator_label;

    /**
     * Title of the currently focused window on the bar's monitor
     */
    label_t m_title_label;

    /**
     * Connection to DWM
     */
    unique_ptr<dwmipc::Connection> m_ipc;

    /**
     * Vector of monitors returned by m_ipc->get_monitors
     */
    shared_ptr<vector<dwmipc::Monitor>> m_monitors;

    /**
     * Vector of layouts returned by m_ipc->get_layouts
     */
    shared_ptr<vector<dwmipc::Layout>> m_layouts;

    /**
     * Maps state_t enum values to their corresponding labels
     */
    std::unordered_map<state_t, label_t> m_state_labels;

    /**
     * Vector of all tags
     */
    vector<tag_t> m_tags;
  };
}  // namespace modules

POLYBAR_NS_END