/**
Copyright (C) 2024  Matthew Kosarek

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

#ifndef MIRACLE_WM_COMMAND_CONTROLLER_H
#define MIRACLE_WM_COMMAND_CONTROLLER_H

#include "compositor_state.h"
#include "container_scope.h"
#include "direction.h"
#include "output_interface.h"
#include <nlohmann/json.hpp>
#include <optional>
#include <string>

namespace miral
{
class MirRunner;
}

namespace miracle
{
class Scratchpad;
class ModeObserverRegistrar;
class OutputManager;

class CommandControllerInterface
{
public:
    virtual ~CommandControllerInterface() = default;
    virtual void quit() = 0;
};

/// Responsible for fielding requests from the system and forwarding
/// them to an appropriate handler. Requests can come from any thread
/// (e.g. the keyboard input thread, the ipc thread, etc.).
/// This class behaves similar to a controller pattern in a server
/// whereby requests are made on the controller that are then sent
/// to the proper subsystem for processing. In this case, the subsystem
/// may be anything from the tiles in the grid, the scratchpad, etc.
class CommandController
{
public:
    CommandController(
        std::shared_ptr<Config> const& config,
        std::recursive_mutex& mutex,
        std::shared_ptr<CompositorState> const& state,
        std::shared_ptr<WindowController> const& window_controller,
        std::shared_ptr<WorkspaceManager> const& workspace_manager,
        std::shared_ptr<ModeObserverRegistrar> const& mode_observer_registrar,
        std::unique_ptr<CommandControllerInterface> interface,
        std::shared_ptr<Scratchpad> const& scratchpad,
        std::shared_ptr<OutputManager> const& output_manager);

    bool try_request_horizontal(std::vector<ContainerScope> const& scope);
    bool try_request_vertical(std::vector<ContainerScope> const& scope);
    bool try_toggle_layout(bool cycle_through_all, std::vector<ContainerScope> const& scope);
    void try_toggle_resize_mode();
    bool try_resize(Direction direction, int pixels, std::vector<ContainerScope> const& scope);
    bool try_set_size(std::optional<int> const& width, std::optional<int> const& height, std::vector<ContainerScope> const& scope);
    bool try_move(Direction direction, std::vector<ContainerScope> const& scope);
    bool try_move_by(Direction direction, int pixels, std::vector<ContainerScope> const& scope);
    bool try_move_to(int x, int y, std::vector<ContainerScope> const& scope);
    bool try_select(std::vector<ContainerScope> const& scope);
    bool try_select(Direction direction, std::vector<ContainerScope> const& scope);
    bool try_select_parent(std::vector<ContainerScope> const& scope);
    bool try_select_child(std::vector<ContainerScope> const& scope);
    bool try_select_floating(std::vector<ContainerScope> const& scope);
    bool try_select_tiling(std::vector<ContainerScope> const& scope);
    bool try_select_toggle(std::vector<ContainerScope> const& scope);
    bool try_close_window(std::vector<ContainerScope> const& scope);
    bool quit();
    bool try_toggle_fullscreen(std::vector<ContainerScope> const& scope);
    bool select_workspace(int number, bool back_and_forth = true);
    bool select_workspace(std::string const& name, bool back_and_forth);
    bool select_workspace_with_scope(std::vector<ContainerScope> const& scope);
    bool next_workspace();
    bool prev_workspace();
    bool back_and_forth_workspace();
    bool next_workspace_on_output(OutputInterface const&);
    bool prev_workspace_on_output(OutputInterface const&);
    bool move_active_to_workspace(int number, bool back_and_forth = true);
    bool move_active_to_workspace_named(std::string const&, bool back_and_forth);
    bool move_active_to_next_workspace();
    bool move_active_to_prev_workspace();
    bool move_active_to_back_and_forth();
    bool move_to_scratchpad();
    bool show_scratchpad();
    bool toggle_floating(std::vector<ContainerScope> const& scope = {});
    bool toggle_pinned_to_workspace(std::vector<ContainerScope> const& scope = {});
    bool set_is_pinned(bool, std::vector<ContainerScope> const& scope = {});
    bool toggle_tabbing(std::vector<ContainerScope> const& scope = {});
    bool toggle_stacking(std::vector<ContainerScope> const& scope = {});
    bool set_layout(LayoutScheme scheme, std::vector<ContainerScope> const& scope = {});
    bool set_layout_default(std::vector<ContainerScope> const& scope = {});
    void move_cursor_to_output(OutputInterface const&);
    bool try_select_next_output();
    bool try_select_prev_output();
    bool try_select_output(Direction direction);
    bool try_select_output(std::vector<std::string> const& names);
    bool try_move_active_to_output(Direction direction);
    bool try_move_active_to_current();
    bool try_move_active_to_primary();
    bool try_move_active_to_nonprimary();
    bool try_move_active_to_next();
    bool try_move_active(std::vector<std::string> const& names);
    bool reload_config();
    void set_mode(WindowManagerMode mode);
    void select_container(std::shared_ptr<Container> const&);
    [[nodiscard]] nlohmann::json to_json() const;
    [[nodiscard]] nlohmann::json outputs_json() const;
    [[nodiscard]] nlohmann::json workspaces_json() const;
    [[nodiscard]] nlohmann::json workspace_to_json(uint32_t) const;
    [[nodiscard]] nlohmann::json mode_to_json() const;

private:
    std::shared_ptr<Config> config;
    std::recursive_mutex& mutex;
    std::shared_ptr<CompositorState> state;
    std::shared_ptr<WindowController> window_controller;
    std::shared_ptr<WorkspaceManager> workspace_manager;
    std::shared_ptr<ModeObserverRegistrar> mode_observer_registrar;
    std::unique_ptr<CommandControllerInterface> interface;
    std::shared_ptr<Scratchpad> scratchpad_;
    std::shared_ptr<OutputManager> output_manager;

    bool can_move_container() const;
    bool can_set_layout() const;

    /// Given the provided scope, this method will figure out what containers meet that criteria.
    std::vector<std::shared_ptr<Container>> resolve_scope(std::vector<ContainerScope> const&);

    /// Floats the container and returns the new [ParentContainer] of that container.
    std::shared_ptr<ParentContainer> toggle_floating_internal(std::shared_ptr<Container> const& container);

    OutputInterface* _next_output_in_list(std::vector<std::string> const& names);
    OutputInterface* _next_output_in_direction(Direction direction);
};
}

#endif // MIRACLE_WM_COMMAND_CONTROLLER_H
