/*
 * Copyright © Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 or 3 as
 * published by the Free Software Foundation.
 *
 * 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 MIRAL_WINDOW_SPECIFICATION_H
#define MIRAL_WINDOW_SPECIFICATION_H

#include <mir_toolkit/common.h>

#include <mir/geometry/displacement.h>
#include <mir/geometry/rectangles.h>
#include <mir/int_wrapper.h>
#include <mir/flags.h>

#include <memory>
#include <optional>

namespace mir
{
namespace scene { class Surface; }
namespace shell { struct SurfaceSpecification; }
}

namespace miral
{
using namespace mir::geometry;
namespace detail { struct SessionsBufferStreamIdTag; }
using BufferStreamId = mir::IntWrapper<detail::SessionsBufferStreamIdTag>;

/// The window specification class describes a request of changes to be made
/// on a #miral::Window.
///
/// Instances of this class may originate either from the client or the compositor
/// itself. Typically, changes will be made from #miral::WindowManagementPolicy
/// and sent to #miral::WindowManagerTools::modify_window to enact the changes.
///
/// The class itself consists of optional values. If a value is set, then a change
/// on that value will occur. If the value is unset, then no change will happen.
///
/// \sa miral::WindowManagementPolicy - handles specifications from both the client and compositor
/// \sa miral::WindowManagerTools::modify_window - the method used to modify a window using a specification
class WindowSpecification
{
public:
    /// Describes the input reception mode.
    ///
    /// Used by #WindowSpecification::input_mode.
    enum class InputReceptionMode
    {
        normal,
        receives_all_input
    };

    /// Describes the aspect ratio.
    ///
    /// Used by #WindowSpecification::min_aspect and #WindowSpecification::max_aspect.
    struct AspectRatio
    {
        unsigned width;
        unsigned height;
        bool operator==(AspectRatio const&) const = default;
    };

    /// Construct a new window specification.
    WindowSpecification();

    /// Construct a copy of the given specification.
    ///
    /// \param that to copy
    WindowSpecification(WindowSpecification const& that);
    auto operator=(WindowSpecification const& that) -> WindowSpecification&;

    /// Construct a window specification from a surface specification.
    ///
    /// For internal use only.
    ///
    /// \param spec the surface specification
    WindowSpecification(mir::shell::SurfaceSpecification const& spec);

    ~WindowSpecification();

    /// The top left corner of the window.
    ///
    /// \returns the top left point
    auto top_left() const& -> std::optional<Point> const&;

    /// The size of the window for window management purposes.
    ///
    /// It may differ from the buffer size drawn by the client as it may include decorations
    ///  and/or exclude drop-shadows
    ///
    /// \note If the #WindowSpecification is applied using #WindowManagementTools::modify_client
    /// then value will be adjusted based on #min_width(), #WindowInfo::max_width(),
    /// #WindowInfo::min_height(), #WindowInfo::max_height(), #WindowInfo::min_aspect(),
    /// #WindowInfo::max_aspect(), #WindowInfo::width_inc() and WindowInfo::height_inc().
    /// Set these properties to their default values if they should be ignored.
    /// Note that the position of the window may also be adjusted if the new size violates
    /// the size constraints.
    ///
    /// This value is **not** guaranteed to be honored by the client.
    ///
    /// \returns the size
    auto size() const& -> std::optional<Size> const&;

    /// The name of the window.
    ///
    /// \returns the name
    auto name() const& -> std::optional<std::string> const&;

    /// The output id optionally associated with a fullscreen window.
    ///
    /// \returns the output id
    /// \sa miral::Output - the class that holds this output id
    auto output_id() const& -> std::optional<int> const&;

    /// The type of the window.
    ///
    /// \returns the type
    auto type() const& -> std::optional<MirWindowType> const&;

    /// The state of the window.
    ///
    /// \returns the state
    auto state() const& -> std::optional<MirWindowState> const&;

    /// The preferred orientation of the window.
    ///
    /// This is often used when the buffer of the window is provided by the client to match
    /// the current orientation of the output.
    ///
    /// \returns a the orientation.
    auto preferred_orientation() const& -> std::optional<MirOrientationMode> const&;

    /// Describes the auxiliary rectangle.
    ///
    /// A window can be positioned relative to its parent window using four fields:
    ///
    /// - `#aux_rect` specifies a reference rectangle on the parent window.
    /// - `#aux_rect_placement_gravity` defines a specific point on `#aux_rect`.
    /// - `#aux_rect_placement_offset`: an offset on `#aux_rect` relative to `#aux_rect`
    /// - `#window_placement_gravity` defines a point on the child window.
    ///
    /// The child window is positioned so that the point defined by
    /// `#window_placement_gravity` on the child window aligns with the point
    /// defined by `#aux_rect_placement_gravity` on the parent window's `#aux_rect`.
    /// The final position is offset by `#aux_rect`.
    ///
    /// \returns the auxiliary rectangle
    auto aux_rect() const& -> std::optional<Rectangle> const&;

    /// The placement hint describes how child window placement should be
    /// adjusted they cannot be placed in the requested position
    ///
    /// \returns the placement hints
    auto placement_hints() const& -> std::optional<MirPlacementHints> const&;

    /// Describes the placement gravity.
    ///
    /// A window can be positioned relative to its parent window using four fields:
    ///
    /// - `#aux_rect` specifies a reference rectangle on the parent window.
    /// - `#aux_rect_placement_gravity` defines a specific point on `#aux_rect`.
    /// - `#aux_rect_placement_offset`: an offset on `#aux_rect` relative to `#aux_rect`
    /// - `#window_placement_gravity` defines a point on the child window.
    ///
    /// The child window is positioned so that the point defined by
    /// `#window_placement_gravity` on the child window aligns with the point
    /// defined by `#aux_rect_placement_gravity` on the parent window's `#aux_rect`.
    /// The final position is offset by `#aux_rect`.
    ///
    /// \returns the placement gravity
    auto window_placement_gravity() const& -> std::optional<MirPlacementGravity> const&;

    /// Describes the auxiliary rectangle placement gravity.
    ///
    /// A window can be positioned relative to its parent window using four fields:
    ///
    /// - `#aux_rect` specifies a reference rectangle on the parent window.
    /// - `#aux_rect_placement_gravity` defines a specific point on `#aux_rect`.
    /// - `#aux_rect_placement_offset`: an offset on `#aux_rect` relative to `#aux_rect`
    /// - `#window_placement_gravity` defines a point on the child window.
    ///
    /// The child window is positioned so that the point defined by
    /// `#window_placement_gravity` on the child window aligns with the point
    /// defined by `#aux_rect_placement_gravity` on the parent window's `#aux_rect`.
    /// The final position is offset by `#aux_rect`.
    ///
    /// \returns the auxiliary placement gravity
    auto aux_rect_placement_gravity() const& -> std::optional<MirPlacementGravity> const&;

    /// Describes the auxiliary rectangle placement offset.
    ///
    /// A window can be positioned relative to its parent window using four fields:
    ///
    /// - `#aux_rect` specifies a reference rectangle on the parent window.
    /// - `#aux_rect_placement_gravity` defines a specific point on `#aux_rect`.
    /// - `#aux_rect_placement_offset`: an offset on `#aux_rect` relative to `#aux_rect`
    /// - `#window_placement_gravity` defines a point on the child window.
    ///
    /// The child window is positioned so that the point defined by
    /// `#window_placement_gravity` on the child window aligns with the point
    /// defined by `#aux_rect_placement_gravity` on the parent window's `#aux_rect`.
    /// The final position is offset by `#aux_rect`.
    ///
    /// \returns the auxiliary placement gravity
    auto aux_rect_placement_offset() const& -> std::optional<Displacement> const&;

    /// The minimum width of the window.
    ///
    /// \returns the minimum width
    auto min_width() const& -> std::optional<Width> const&;

    /// The minimum height of the window.
    ///
    /// \returns the minimum height
    auto min_height() const& -> std::optional<Height> const&;

    /// The maximum width of the window.
    ///
    /// \returns the maximum width
    auto max_width() const& -> std::optional<Width> const&;

    /// The maximum height of the window.
    ///
    /// \returns a const reference to the maximum height
    auto max_height() const& -> std::optional<Height> const&;

    /// The size increments of the window in the X direction.
    ///
    /// This is used in cases such as a terminal that can only be resized
    /// character-by-character.
    ///
    /// Wayland protocols do not support this property, so it generally will not be requested by clients.
    ///
    /// \returns the width increment.
    auto width_inc() const& -> std::optional<DeltaX> const&;

    /// The size increments of the window in the Y direction.
    ///
    /// This is used in cases such as a terminal that can only be resized
    /// character-by-character.
    ///
    /// Wayland protocols do not support this property, so it generally will not be requested by clients.
    ///
    /// \returns the height increment
    auto height_inc() const& -> std::optional<DeltaY> const&;

    /// The minimum aspect ratio.
    ///
    /// Wayland protocols do not support this property, so it generally will not be requested by clients.
    ///
    /// \returns the min aspect ratio
    auto min_aspect() const& -> std::optional<AspectRatio> const&;

    /// The maximum aspect ratio.
    ///
    /// Wayland protocols do not support this property, so it generally will not be requested by clients.
    ///
    /// \returns the max aspect ratio
    auto max_aspect() const& -> std::optional<AspectRatio> const&;

    /// The parent surface of the window.
    ///
    /// \returns the pending parent surface
    auto parent() const& -> std::optional<std::weak_ptr<mir::scene::Surface>> const&;

    /// The input shape of the window.
    ///
    /// This is an area in world coordinates that describes the input region
    /// for the window.
    ///
    /// \returns the list of rectangles that describes the input region
    auto input_shape() const& -> std::optional<std::vector<Rectangle>> const&;

    /// The input mode of the window.
    ///
    /// \returns the input mode
    auto input_mode() const& -> std::optional<InputReceptionMode> const&;

    /// The shell chrome of the window.
    ///
    /// This is currently unused.
    ///
    /// \returns the shell chrome
    auto shell_chrome() const& -> std::optional<MirShellChrome> const&;

    /// The pointer confinement of the window.
    ///
    /// \returns the pointer confinement
    auto confine_pointer() const& -> std::optional<MirPointerConfinementState> const&;

    /// Custom userdata set on the window.
    ///
    /// This payload is set by the compositor author. The author may use this to
    /// set any information that they would like associated with the window.
    ///
    /// \returns the userdata
    auto userdata() const& -> std::optional<std::shared_ptr<void>> const&;

    /// The new position of the window frame.
    ///
    /// \returns a reference to  the top left point
    auto top_left() & -> std::optional<Point>&;

    /// The size of the window for window management purposes.
    ///
    /// It may differ from the buffer size drawn by the client as it may include decorations
    ///  and/or exclude drop-shadows
    ///
    /// \note If the #WindowSpecification is applied using #WindowManagementTools::modify_client
    /// then value will be adjusted based on #min_width(), #WindowInfo::max_width(),
    /// #WindowInfo::min_height(), #WindowInfo::max_height(), #WindowInfo::min_aspect(),
    /// #WindowInfo::max_aspect(), #WindowInfo::width_inc() and WindowInfo::height_inc().
    /// Set these properties to their default values if they should be ignored.
    /// Note that the position of the window may also be adjusted if the new size violates
    /// the size constraints.
    ///
    /// This value is **not** guaranteed to be honored by the client.
    ///
    /// \returns the size
    auto size() & -> std::optional<Size>&;

    /// The name of the window.
    ///
    /// \returns the name of the window
    auto name() & -> std::optional<std::string>&;

    /// The output id of the window.
    ///
    /// \returns the output id
    /// \sa miral::Output - the class that holds this output id
    auto output_id() & -> std::optional<int>&;

    /// The type of the window.
    ///
    /// \returns the type of the window
    auto type() & -> std::optional<MirWindowType>&;

    /// The state of the window.
    ///
    /// \returns the state of the window
    auto state() & -> std::optional<MirWindowState>&;

    /// The preferred orientation of the window.
    ///
    /// This is often used when the buffer of the window is provided by the client to match
    /// the current orientation of the output.
    ///
    /// \returns the orientation of the window.
    auto preferred_orientation() & -> std::optional<MirOrientationMode>&;

    /// Describes the auxiliary rectangle.
    ///
    /// A window can be positioned relative to its parent window using four fields:
    ///
    /// - `#aux_rect` specifies a reference rectangle on the parent window.
    /// - `#aux_rect_placement_gravity` defines a specific point on `#aux_rect`.
    /// - `#aux_rect_placement_offset`: an offset on `#aux_rect` relative to `#aux_rect`
    /// - `#window_placement_gravity` defines a point on the child window.
    ///
    /// The child window is positioned so that the point defined by
    /// `#window_placement_gravity` on the child window aligns with the point
    /// defined by `#aux_rect_placement_gravity` on the parent window's `#aux_rect`.
    /// The final position is offset by `#aux_rect`.
    ///
    /// \returns the auxiliary rectangle
    auto aux_rect() & -> std::optional<Rectangle>&;

    /// The placement hint describes how windows with type #mir_window_type_menu,
    /// #mir_window_type_satellite or #mir_window_type_tip should be adjusted when
    /// their placement would cause them to extend beyond their current output.
    ///
    /// \returns the placement hints
    auto placement_hints() & -> std::optional<MirPlacementHints>&;

    /// Describes the placement gravity.
    ///
    /// A window can be positioned relative to its parent window using four fields:
    ///
    /// - `#aux_rect` specifies a reference rectangle on the parent window.
    /// - `#aux_rect_placement_gravity` defines a specific point on `#aux_rect`.
    /// - `#aux_rect_placement_offset`: an offset on `#aux_rect` relative to `#aux_rect`
    /// - `#window_placement_gravity` defines a point on the child window.
    ///
    /// The child window is positioned so that the point defined by
    /// `#window_placement_gravity` on the child window aligns with the point
    /// defined by `#aux_rect_placement_gravity` on the parent window's `#aux_rect`.
    /// The final position is offset by `#aux_rect`.
    ///
    /// \returns the placement gravity
    auto window_placement_gravity() & -> std::optional<MirPlacementGravity>&;

    /// Describes the auxiliary rectangle placement gravity.
    ///
    /// A window can be positioned relative to its parent window using four fields:
    ///
    /// - `#aux_rect` specifies a reference rectangle on the parent window.
    /// - `#aux_rect_placement_gravity` defines a specific point on `#aux_rect`.
    /// - `#aux_rect_placement_offset`: an offset on `#aux_rect` relative to `#aux_rect`
    /// - `#window_placement_gravity` defines a point on the child window.
    ///
    /// The child window is positioned so that the point defined by
    /// `#window_placement_gravity` on the child window aligns with the point
    /// defined by `#aux_rect_placement_gravity` on the parent window's `#aux_rect`.
    /// The final position is offset by `#aux_rect`.
    ///
    /// \returns the auxiliary placement gravity
    auto aux_rect_placement_gravity() & -> std::optional<MirPlacementGravity>&;

    /// Describes the auxiliary rectangle placement offset.
    ///
    /// A window can be positioned relative to its parent window using four fields:
    ///
    /// - `#aux_rect` specifies a reference rectangle on the parent window.
    /// - `#aux_rect_placement_gravity` defines a specific point on `#aux_rect`.
    /// - `#aux_rect_placement_offset`: an offset on `#aux_rect` relative to `#aux_rect`
    /// - `#window_placement_gravity` defines a point on the child window.
    ///
    /// The child window is positioned so that the point defined by
    /// `#window_placement_gravity` on the child window aligns with the point
    /// defined by `#aux_rect_placement_gravity` on the parent window's `#aux_rect`.
    /// The final position is offset by `#aux_rect`.
    ///
    /// \returns the auxiliary placement gravity
    auto aux_rect_placement_offset() & -> std::optional<Displacement>&;

    /// The minimum width of the window.
    ///
    /// \returns the minimum width
    auto min_width() & -> std::optional<Width>&;

    /// The minimum height of the window.
    ///
    /// \returns the minimum height
    auto min_height() & -> std::optional<Height>&;

    /// The maximum width of the window.
    ///
    /// \returns the maximum width
    auto max_width() & -> std::optional<Width>&;

    /// The maximum height of the window
    ///
    /// \returns a reference to the maximum height
    auto max_height() & -> std::optional<Height>&;

    /// The size increments of the window in the X direction.
    ///
    /// This is used in cases such as a terminal that can only be resized
    /// character-by-character.
    ///
    /// Wayland protocols do not support this property, so it generally will not be requested by clients.
    ///
    /// \returns the width increment
    auto width_inc() & -> std::optional<DeltaX>&;

    /// The size increments of the window in the Y direction.
    ///
    /// This is used in cases such as a terminal that can only be resized
    /// character-by-character.
    ///
    /// Wayland protocols do not support this property, so it generally will not be requested by clients.
    ///
    /// \returns the height increment
    auto height_inc() & -> std::optional<DeltaY>&;

    /// The minimum aspect ratio.
    ///
    /// Wayland protocols do not support this property, so it generally will not be requested by clients.
    ///
    /// \returns the min aspect ratio
    auto min_aspect() & -> std::optional<AspectRatio>&;

    /// The maximum aspect ratio.
    ///
    /// Wayland protocols do not support this property, so it generally will not be requested by clients.
    ///
    /// \returns the max aspect ratio
    auto max_aspect() & -> std::optional<AspectRatio>&;

    /// The parent of this window.
    ///
    /// \returns the parent of this window
    auto parent() & -> std::optional<std::weak_ptr<mir::scene::Surface>>&;

    /// The input shape of the window.
    ///
    /// This is an area in world coordinates that describes the input region
    /// for the window.
    ///
    /// \returns the list of rectangles that describes the input region
    auto input_shape() & -> std::optional<std::vector<Rectangle>>&;

    /// The input mode of the window.
    ///
    /// \returns the input mode
    auto input_mode() & -> std::optional<InputReceptionMode>&;

    /// The shell chrome of the window.
    ///
    /// This is currently unused.
    ///
    /// \returns the shell chrome
    auto shell_chrome() & -> std::optional<MirShellChrome>&;

    /// The pointer confinement of the window.
    ///
    /// \returns the pointer confinement
    auto confine_pointer() & -> std::optional<MirPointerConfinementState>&;

    /// Custom userdata set on the window.
    ///
    /// This payload is set by the compositor author. The author may use this to
    /// set any information that they would like associated with the window.
    ///
    /// \returns the userdata
    auto userdata() & -> std::optional<std::shared_ptr<void>>&;

    /// The depth layer of a child window is updated with the depth layer of its parent, but can be overridden.
    ///
    /// \returns the depth layer
    auto depth_layer() const& -> std::optional<MirDepthLayer> const&;

    /// The depth layer of a child window is updated with the depth layer of its parent, but can be overridden.
    ///
    /// \returns the depth layer
    auto depth_layer() & -> std::optional<MirDepthLayer>&;

    /// The set of window edges that are attached to edges of the output.
    ///
    /// If attached to perpendicular edges, it is attached to the corner where the two edges intersect
    /// If attached to oposite edges (eg left and right), it is stretched across the output in that direction
    /// If all edges are specified, it takes up the entire output
    ///
    /// \returns the edges
    auto attached_edges() const& -> std::optional<MirPlacementGravity> const&;

    /// The set of window edges that are attached to edges of the output.
    ///
    /// If attached to perpendicular edges, it is attached to the corner where the two edges intersect
    /// If attached to oposite edges (eg left and right), it is stretched across the output in that direction
    /// If all edges are specified, it takes up the entire output
    ///
    /// \returns the edges
    auto attached_edges() & -> std::optional<MirPlacementGravity>&;

    /// The area over which the window should not be occluded.
    ///
    /// This is only meaningful for windows attached to an edge.
    /// If the outer optional is unset (the default), the window's exclusive rect is not changed by this spec
    /// If the outer optional is set but the inner is not, the window's exclusive rect is cleared
    ///
    /// \returns the exclusive rectangle
    auto exclusive_rect() const& -> std::optional<std::optional<mir::geometry::Rectangle>> const&;

    /// The area over which the window should not be occluded.
    ///
    /// This is only meaningful for windows attached to an edge.
    /// If the outer optional is unset (the default), the window's exclusive rect is not changed by this spec
    /// If the outer optional is set but the inner is not, the window's exclusive rect is cleared
    ///
    /// \returns the exclusive rectangle
    auto exclusive_rect() & -> std::optional<std::optional<mir::geometry::Rectangle>>&;

    /// Decides whether this window should ignore the exclusion zones set by other windows.
    ///
    /// This is only meaningful for windows attached to an edge.
    ///
    /// \returns the flag
    auto ignore_exclusion_zones() const& -> std::optional<bool> const&;

    /// Decides whether this window should ignore the exclusion zones set by other windows.
    ///
    /// This is only meaningful for windows attached to an edge.
    ///
    /// \returns the flag
    auto ignore_exclusion_zones() & -> std::optional<bool>&;

    /// The D-bus service name and basename of the app's .desktop file.
    ///
    /// See https://specifications.freedesktop.org/desktop-entry-spec/
    ///
    /// \returns the application id
    auto application_id() const& -> std::optional<std::string> const&;

    /// The D-bus service name and basename of the app's .desktop file.
    ///
    /// See https://specifications.freedesktop.org/desktop-entry-spec/
    ///
    /// \returns the application id
    auto application_id() & -> std::optional<std::string>&;

    /// If this window should have server-side decorations provided by Mir
    /// Currently, Mir only respects this value during surface construction
    ///
    /// \returns the flag
    auto server_side_decorated() const& -> std::optional<bool> const&;

    /// If this window should have server-side decorations provided by Mir
    /// Currently, Mir only respects this value during surface construction
    ///
    /// \returns the flag
    auto server_side_decorated() & -> std::optional<bool>&;

    /// Describes how the window should gain and lose focus.
    ///
    /// \returns the focus mode
    auto focus_mode() const& -> std::optional<MirFocusMode> const&;

    /// Describes how the window should gain and lose focus.
    ///
    /// \returns the focus mode
    auto focus_mode() & -> std::optional<MirFocusMode>&;

    /// If this surface should be shown while the compositor is locked.
    ///
    /// \returns the flag
    auto visible_on_lock_screen() const& -> std::optional<bool> const&;

    /// If this surface should be shown while the compositor is locked.
    ///
    /// \returns the flag
    auto visible_on_lock_screen() & -> std::optional<bool>&;

    /// Describes which edges are touching part of the tiling grid.
    ///
    /// \returns the tiled edges
    /// \remark Since MirAL 5.3
    auto tiled_edges() const& -> std::optional<mir::Flags<MirTiledEdge>> const&;

    /// Describes which edges are touching part of the tiling grid.
    ///
    /// \returns the tiled edges
    /// \remark Since MirAL 5.3
    auto tiled_edges() & -> std::optional<mir::Flags<MirTiledEdge>>&;

    /// The alpha of the window.
    ///
    /// This value is between [0, 1].
    ///
    /// \returns the alpha
    /// \remark Since MirAL 5.7
    auto alpha() & -> std::optional<float>&;

    /// The alpha of the window.
    ///
    /// This value is between [0, 1].
    ///
    /// \returns the alpha
    /// \remark Since MirAL 5.7
    auto alpha() const& -> std::optional<float> const&;

    /// The expected size of the parent window for constrained popup placement.
    ///
    /// Set when the compositor should use a specific parent geometry (rather than
    /// the current parent geometry) to constrain the popup placement. This is used
    /// when repositioning a popup in response to a parent resize.
    ///
    /// \returns the expected parent size
    /// \remark Since MirAL 5.8
    auto parent_size() & -> std::optional<Size>&;

    /// The expected size of the parent window for constrained popup placement.
    ///
    /// Set when the compositor should use a specific parent geometry (rather than
    /// the current parent geometry) to constrain the popup placement. This is used
    /// when repositioning a popup in response to a parent resize.
    ///
    /// \returns the expected parent size
    /// \remark Since MirAL 5.8
    auto parent_size() const& -> std::optional<Size> const&;

    /// Create a [mir::shell::SurfaceSpecification] from this window spec.
    ///
    /// \returns a surface specification
    /// \remark Since MirAL 5.3
    auto to_surface_specification() const -> mir::shell::SurfaceSpecification;

private:
    friend auto make_surface_spec(WindowSpecification const& miral_spec) -> mir::shell::SurfaceSpecification;
    struct Self;
    std::unique_ptr<Self> self;
};
}

#endif //MIRAL_WINDOW_SPECIFICATION_H
