//! Window abstraction
use std::cell::RefCell;
use std::fmt;
use std::rc::Rc;
use std::sync::Arc;

use wayland_client::protocol::{
    wl_compositor, wl_output, wl_seat, wl_shm, wl_subcompositor, wl_surface,
};
use wayland_client::{Attached, DispatchData};

use wayland_protocols::xdg_shell::client::xdg_toplevel::ResizeEdge;
pub use wayland_protocols::xdg_shell::client::xdg_toplevel::State;

use wayland_protocols::unstable::xdg_decoration::v1::client::{
    zxdg_decoration_manager_v1::ZxdgDecorationManagerV1,
    zxdg_toplevel_decoration_v1::{self, ZxdgToplevelDecorationV1},
};

use crate::{
    environment::{Environment, GlobalHandler, MultiGlobalHandler},
    seat::pointer::ThemeManager,
    shell,
};

mod fallback_frame;
pub use self::fallback_frame::FallbackFrame;

// Defines the minimum window size. Minimum width is set to 2 pixels to circumvent
// a bug in mutter - https://gitlab.gnome.org/GNOME/mutter/issues/259
const MIN_WINDOW_SIZE: (u32, u32) = (2, 1);

/// Represents the status of a button
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ButtonState {
    /// Button is being hovered over by pointer
    Hovered,
    /// Button is not being hovered over by pointer
    Idle,
    /// Button is disabled
    Disabled,
}

/// Represents the status of a window
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum WindowState {
    /// The window is active, in the foreground
    Active,
    /// The window is inactive, in the background
    Inactive,
}

impl From<bool> for WindowState {
    fn from(b: bool) -> WindowState {
        if b {
            WindowState::Active
        } else {
            WindowState::Inactive
        }
    }
}

impl From<WindowState> for bool {
    fn from(s: WindowState) -> bool {
        match s {
            WindowState::Active => true,
            WindowState::Inactive => false,
        }
    }
}

/// Possible events generated by a window that you need to handle
#[derive(Clone, Debug)]
pub enum Event {
    /// The state of your window has been changed
    Configure {
        /// Optional new size for your *inner* surface
        ///
        /// This is the new size of the contents of your window
        /// as suggested by the server. You can ignore it and choose
        /// a new size if you want better control on the possible
        /// sizes of your window.
        ///
        /// The size is expressed in logical pixels, you need to multiply it by
        /// your buffer scale to get the actual number of pixels to draw.
        ///
        /// In all cases, these events can be generated in large batches
        /// during an interactive resize, and you should buffer them before
        /// processing them. You only need to handle the last one of a batch.
        new_size: Option<(u32, u32)>,
        /// New combination of states of your window
        ///
        /// Typically tells you if your surface is active/inactive, maximized,
        /// etc...
        states: Vec<State>,
    },
    /// A close request has been received
    ///
    /// Most likely the user has clicked on the close button of the decorations
    /// or something equivalent
    Close,
    /// The decorations need to be refreshed
    Refresh,
}

/// Possible decoration modes for a Window
///
/// This represents what your application requests from the server.
///
/// In any case, the compositor may override your requests. In that case SCTK
/// will follow its decision.
///
/// If you don't care about it, you should use `FollowServer` (which is the
/// SCTK default). It'd be the most ergonomic for your users.
#[derive(Debug, PartialEq, Eq)]
pub enum Decorations {
    /// Request server-side decorations
    ServerSide,
    /// Force using the client-side `Frame`
    ClientSide,
    /// Follow the preference of the compositor
    FollowServer,
    /// Don't decorate the Window
    None,
}

struct WindowInner<F> {
    frame: Rc<RefCell<F>>,
    shell_surface: Arc<Box<dyn shell::ShellSurface>>,
    user_impl: Box<dyn FnMut(Event, DispatchData)>,
    min_size: (u32, u32),
    max_size: Option<(u32, u32)>,
    current_size: (u32, u32),
    old_size: Option<(u32, u32)>,
    decorated: bool,
}

impl<F> fmt::Debug for WindowInner<F>
where
    F: fmt::Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("WindowInner")
            .field("frame", &self.frame)
            .field("shell_surface", &self.shell_surface)
            .field("user_impl", &"Fn() -> { ... }")
            .field("min_size", &self.min_size)
            .field("max_size", &self.max_size)
            .field("current_size", &self.current_size)
            .field("old_size", &self.old_size)
            .field("decorated", &self.decorated)
            .finish()
    }
}

/// A window
///
/// This wrapper handles for you the decoration of your window
/// and the interaction with the server regarding the shell protocol.
///
/// You are still entirely responsible for drawing the contents of your
/// window.
///
/// Note also that as the dimensions of wayland surfaces is defined by
/// their attached buffer, you need to keep the decorations in sync with
/// your contents via the `resize(..)` method.
///
/// Different kind of decorations can be used by customizing the type
/// parameter. A few are provided in this crate if the `frames` cargo feature
/// is enabled, but any type implementing the `Frame` trait can do.
pub struct Window<F: Frame> {
    frame: Rc<RefCell<F>>,
    surface: wl_surface::WlSurface,
    decoration: Option<ZxdgToplevelDecorationV1>,
    shell_surface: Arc<Box<dyn shell::ShellSurface>>,
    inner: Rc<RefCell<Option<WindowInner<F>>>>,
    _seat_listener: crate::seat::SeatListener,
}

impl<F: Frame + 'static> Window<F> {
    /// Create a new window wrapping a given wayland surface as its main content and
    /// following the compositor's preference regarding server-side decorations
    ///
    /// It can fail if the initialization of the frame fails (for example if the
    /// frame class fails to initialize its SHM).
    ///
    /// Providing non `None` value for `theme_manager` should prevent theming pointer
    /// over the `surface`.
    fn init_with_decorations<Impl, E>(
        env: &crate::environment::Environment<E>,
        surface: wl_surface::WlSurface,
        theme_manager: Option<ThemeManager>,
        initial_dims: (u32, u32),
        implementation: Impl,
    ) -> Result<Window<F>, F::Error>
    where
        Impl: FnMut(Event, DispatchData) + 'static,
        E: GlobalHandler<wl_compositor::WlCompositor>
            + GlobalHandler<wl_subcompositor::WlSubcompositor>
            + GlobalHandler<wl_shm::WlShm>
            + crate::shell::ShellHandling
            + MultiGlobalHandler<wl_seat::WlSeat>
            + GlobalHandler<ZxdgDecorationManagerV1>
            + crate::seat::SeatHandling,
    {
        let compositor = env.require_global::<wl_compositor::WlCompositor>();
        let subcompositor = env.require_global::<wl_subcompositor::WlSubcompositor>();
        let shm = env.require_global::<wl_shm::WlShm>();
        let shell = env
            .get_shell()
            .expect("[SCTK] Cannot create a window if the compositor advertized no shell.");

        let inner = Rc::new(RefCell::new(None::<WindowInner<F>>));
        let frame_inner = inner.clone();
        let shell_inner = inner.clone();
        let mut frame = F::init(
            &surface,
            &compositor,
            &subcompositor,
            &shm,
            theme_manager,
            Box::new(move |req, serial, ddata: DispatchData| {
                if let Some(ref mut inner) = *shell_inner.borrow_mut() {
                    match req {
                        FrameRequest::Minimize => inner.shell_surface.set_minimized(),
                        FrameRequest::Maximize => inner.shell_surface.set_maximized(),
                        FrameRequest::UnMaximize => inner.shell_surface.unset_maximized(),
                        FrameRequest::Move(seat) => inner.shell_surface.move_(&seat, serial),
                        FrameRequest::Resize(seat, edges) => {
                            inner.shell_surface.resize(&seat, serial, edges)
                        }
                        FrameRequest::ShowMenu(seat, x, y) => {
                            inner.shell_surface.show_window_menu(&seat, serial, x, y)
                        }
                        FrameRequest::Close => (inner.user_impl)(Event::Close, ddata),
                        FrameRequest::Refresh => (inner.user_impl)(Event::Refresh, ddata),
                    }
                }
            }) as Box<_>,
        )?;

        let decoration_mgr = env.get_global::<ZxdgDecorationManagerV1>();
        if decoration_mgr.is_none() {
            // We don't have ServerSide decorations, so we'll be using CSD, and so should
            // mark frame as not hidden.
            frame.set_hidden(false);
        }

        frame.resize(initial_dims);
        let frame = Rc::new(RefCell::new(frame));
        let shell_surface = Arc::new(shell::create_shell_surface(
            &shell,
            &surface,
            move |event, mut ddata: DispatchData| {
                let mut frame_inner = frame_inner.borrow_mut();
                let mut inner = match frame_inner.as_mut() {
                    Some(inner) => inner,
                    None => return,
                };

                match event {
                    shell::Event::Configure { states, mut new_size } => {
                        let mut frame = inner.frame.borrow_mut();

                        // Populate frame changes. We should do it before performing new_size
                        // recalculation, since we should account for a fullscreen state.
                        let need_refresh = frame.set_states(&states);

                        // Clamp size.
                        new_size = new_size.map(|(w, h)| {
                            use std::cmp::{max, min};
                            let (mut w, mut h) = frame.subtract_borders(w as i32, h as i32);
                            let (minw, minh) = inner.min_size;
                            w = max(w, minw as i32);
                            h = max(h, minh as i32);
                            if let Some((maxw, maxh)) = inner.max_size {
                                w = min(w, maxw as i32);
                                h = min(h, maxh as i32);
                            }
                            (max(w, 1) as u32, max(h, 1) as u32)
                        });

                        // Check whether we should save old size for later restoration.
                        let should_stash_size = states
                            .iter()
                            .find(|s| {
                                matches!(
                                    *s,
                                    State::Maximized
                                        | State::Fullscreen
                                        | State::TiledTop
                                        | State::TiledRight
                                        | State::TiledBottom
                                        | State::TiledLeft
                                )
                            })
                            .map(|_| true)
                            .unwrap_or(false);

                        if should_stash_size {
                            if inner.old_size.is_none() {
                                // We are getting maximized/fullscreened, store the size for
                                // restoration.
                                inner.old_size = Some(inner.current_size);
                            }
                        } else if new_size.is_none() {
                            // We are getting de-maximized/de-fullscreened/un-tiled, restore the
                            // size, if we were not previously maximized/fullscreened, old_size is
                            // None and this does nothing.
                            new_size = inner.old_size.take();
                        } else {
                            // We are neither maximized nor fullscreened, but are given a size,
                            // respect it and forget about the old size.
                            inner.old_size = None;
                        }

                        if need_refresh {
                            (inner.user_impl)(Event::Refresh, ddata.reborrow());
                        }
                        (inner.user_impl)(Event::Configure { states, new_size }, ddata);
                    }
                    shell::Event::Close => {
                        (inner.user_impl)(Event::Close, ddata);
                    }
                }
            },
        ));

        // setup size and geometry
        {
            let frame = frame.borrow_mut();
            let (minw, minh) =
                frame.add_borders(MIN_WINDOW_SIZE.0 as i32, MIN_WINDOW_SIZE.1 as i32);
            shell_surface.set_min_size(Some((minw, minh)));
            let (w, h) = frame.add_borders(initial_dims.0 as i32, initial_dims.1 as i32);
            let (x, y) = frame.location();
            shell_surface.set_geometry(x, y, w, h);
        }

        // initial seat setup
        let mut seats = Vec::<wl_seat::WlSeat>::new();
        for seat in env.get_all_seats() {
            crate::seat::with_seat_data(&seat, |seat_data| {
                if seat_data.has_pointer && !seat_data.defunct {
                    seats.push(seat.detach());
                    frame.borrow_mut().new_seat(&seat);
                }
            });
        }

        // setup seat_listener
        let seat_frame = frame.clone();
        let seat_listener = env.listen_for_seats(move |seat, seat_data, _| {
            let is_known = seats.contains(&seat);
            if !is_known && seat_data.has_pointer && !seat_data.defunct {
                seat_frame.borrow_mut().new_seat(&seat);
                seats.push(seat.detach());
            } else if is_known && ((!seat_data.has_pointer) || seat_data.defunct) {
                seat_frame.borrow_mut().remove_seat(&seat);
                seats.retain(|s| s != &*seat);
            }
        });

        *inner.borrow_mut() = Some(WindowInner {
            frame: frame.clone(),
            shell_surface: shell_surface.clone(),
            user_impl: Box::new(implementation) as Box<_>,
            min_size: (MIN_WINDOW_SIZE.0, MIN_WINDOW_SIZE.1),
            max_size: None,
            current_size: initial_dims,
            old_size: None,
            decorated: true,
        });

        // Setup window decorations if applicable.
        let decoration = Self::setup_decorations_handler(
            &decoration_mgr,
            &shell_surface,
            frame.clone(),
            inner.clone(),
        );

        let window = Window {
            frame,
            shell_surface,
            decoration,
            surface,
            inner,
            _seat_listener: seat_listener,
        };

        Ok(window)
    }

    /// Setup handling for zxdg_toplevel_decoration_v1 in case protocol is available.
    fn setup_decorations_handler(
        decoration_mgr: &Option<Attached<ZxdgDecorationManagerV1>>,
        shell_surface: &Arc<Box<dyn shell::ShellSurface>>,
        decoration_frame: Rc<RefCell<F>>,
        decoration_inner: Rc<RefCell<Option<WindowInner<F>>>>,
    ) -> Option<ZxdgToplevelDecorationV1> {
        let (toplevel, mgr) = match (shell_surface.get_xdg(), decoration_mgr) {
            (Some(toplevel), Some(ref mgr)) => (toplevel, mgr),
            _ => {
                return None;
            }
        };

        let decoration = mgr.get_toplevel_decoration(toplevel);

        decoration.quick_assign(move |_, event, _| {
            use self::zxdg_toplevel_decoration_v1::{Event, Mode};
            let mode = if let Event::Configure { mode } = event { mode } else { unreachable!() };

            match mode {
                Mode::ServerSide => {
                    decoration_frame.borrow_mut().set_hidden(true);
                }
                Mode::ClientSide => {
                    let want_decorate = decoration_inner
                        .borrow_mut()
                        .as_ref()
                        .map(|inner| inner.decorated)
                        .unwrap_or(false);
                    decoration_frame.borrow_mut().set_hidden(!want_decorate);
                }
                _ => unreachable!(),
            }
        });

        Some(decoration.detach())
    }

    /// Access the surface wrapped in this Window
    pub fn surface(&self) -> &wl_surface::WlSurface {
        &self.surface
    }

    /// Refreshes the frame
    ///
    /// Redraws the frame to match its requested state (dimensions, presence/
    /// absence of decorations, ...)
    ///
    /// You need to call this method after every change to the dimensions or state
    /// of the decorations of your window, otherwise the drawn decorations may go
    /// out of sync with the state of your content.
    ///
    /// Your implementation will also receive `Refresh` events when the frame requests
    /// to be redrawn (to provide some frame animations for example).
    pub fn refresh(&mut self) {
        self.frame.borrow_mut().redraw();
    }

    /// Set a short title for the window.
    ///
    /// This string may be used to identify the surface in a task bar, window list, or other
    /// user interface elements provided by the compositor.
    ///
    /// You need to call `refresh()` afterwards for this to properly
    /// take effect.
    pub fn set_title(&self, mut title: String) {
        // Truncate the title to at most 1024 bytes, so that it does not blow up the protocol
        // messages
        if title.len() > 1024 {
            let mut new_len = 1024;
            while !title.is_char_boundary(new_len) {
                new_len -= 1;
            }
            title.truncate(new_len);
        }
        self.frame.borrow_mut().set_title(title.clone());
        self.shell_surface.set_title(title);
    }

    /// Set an app id for the surface.
    ///
    /// The surface class identifies the general class of applications to which the surface
    /// belongs.
    ///
    /// Several wayland compositors will try to find a `.desktop` file matching this name
    /// to find metadata about your apps.
    pub fn set_app_id(&self, app_id: String) {
        self.shell_surface.set_app_id(app_id);
    }

    /// Set whether the window should be decorated or not.
    ///
    /// If `zxdg_toplevel_decoration_v1` object is presented and alive, requesting `None`
    /// decorations will result in setting `ClientSide` decorations with hidden frame, and if
    /// `ClientSide` decorations were requested, it'll result in destroying
    /// `zxdg_toplevel_decoration_v1` object, meaning that you won't be able to get `ServerSide`
    /// decorations back.
    ///
    /// In case `zxdg_toplevel_decoration_v1` is not available or the corresponding object is not
    /// alive anymore, `decorate` with `ServerSide` or `FollowServer` values will always result in
    /// `ClientSide` decorations being used.
    ///
    /// You need to call `refresh()` afterwards for this to properly
    /// take effect.
    pub fn set_decorate(&mut self, decorate: Decorations) {
        use self::zxdg_toplevel_decoration_v1::Mode;

        // Update inner.decorated state.
        if let Some(inner) = self.inner.borrow_mut().as_mut() {
            if Decorations::None == decorate {
                inner.decorated = false;
            } else {
                inner.decorated = true;
            }
        }

        match self.decoration.as_ref() {
            // Server side decorations are there.
            Some(decoration) => {
                match decorate {
                    Decorations::ClientSide => {
                        // The user explicitly requested `ClientSide` decorations, we should destroy
                        // the server side decorations if some are presented.
                        decoration.destroy();
                        self.decoration = None;
                        self.frame.borrow_mut().set_hidden(false);
                    }
                    Decorations::ServerSide => {
                        decoration.set_mode(Mode::ServerSide);
                    }
                    Decorations::FollowServer => {
                        decoration.unset_mode();
                    }
                    Decorations::None => {
                        // The user explicitly requested `None` decorations, however
                        // since we can't destroy and recreate decoration object on the fly switch
                        // them to `ClientSide` with the hidden frame. The server is free to ignore
                        // us with such request, but not that we can do much about it.
                        decoration.set_mode(Mode::ClientSide);
                        self.frame.borrow_mut().set_hidden(true);
                    }
                }
            }
            // Server side decorations are not presented or were destroyed.
            None => {
                match decorate {
                    // We map `ServerSide` and `FollowServer` decorations to `ClientSide`, since
                    // server side decorations are no longer available.
                    Decorations::ClientSide
                    | Decorations::ServerSide
                    | Decorations::FollowServer => {
                        self.frame.borrow_mut().set_hidden(false);
                    }
                    Decorations::None => {
                        self.frame.borrow_mut().set_hidden(true);
                    }
                }
            }
        }
    }

    /// Set whether the window should be resizeable by the user
    ///
    /// This is not an hard blocking, as the compositor can always
    /// resize you forcibly if it wants. However it signals it that
    /// you don't want this window to be resized.
    ///
    /// Additionally, the decorations will stop suggesting the user
    /// to resize by dragging the borders if you set the window as
    /// non-resizable.
    ///
    /// When re-activating resizability, any previously set min/max
    /// sizes are restored.
    pub fn set_resizable(&self, resizable: bool) {
        let mut frame = self.frame.borrow_mut();
        frame.set_resizable(resizable);
        let mut inner = self.inner.borrow_mut();
        if let Some(ref mut inner) = *inner {
            if resizable {
                // restore the min/max sizes
                self.shell_surface.set_min_size(
                    Some(inner.min_size).map(|(w, h)| frame.add_borders(w as i32, h as i32)),
                );
                self.shell_surface.set_max_size(
                    inner.max_size.map(|(w, h)| frame.add_borders(w as i32, h as i32)),
                );
            } else {
                // Lock the min/max sizes to current size.
                let (w, h) = inner.current_size;
                self.shell_surface.set_min_size(Some(frame.add_borders(w as i32, h as i32)));
                self.shell_surface.set_max_size(Some(frame.add_borders(w as i32, h as i32)));
            }
        }
    }

    /// Resize the decorations
    ///
    /// You should call this whenever you change the size of the contents
    /// of your window, with the new _inner size_ of your window.
    ///
    /// This size is expressed in logical pixels, like the one received
    /// in [`Event::Configure`](enum.Event.html).
    ///
    /// You need to call `refresh()` afterwards for this to properly
    /// take effect.
    pub fn resize(&mut self, w: u32, h: u32) {
        use std::cmp::max;
        let w = max(w, 1);
        let h = max(h, 1);
        if let Some(ref mut inner) = *self.inner.borrow_mut() {
            inner.current_size = (w, h);
        }
        let mut frame = self.frame.borrow_mut();
        frame.resize((w, h));
        let (w, h) = frame.add_borders(w as i32, h as i32);
        let (x, y) = frame.location();
        self.shell_surface.set_geometry(x, y, w, h);
    }

    /// Request the window to be maximized
    pub fn set_maximized(&self) {
        self.shell_surface.set_maximized();
    }

    /// Request the window to be un-maximized
    pub fn unset_maximized(&self) {
        self.shell_surface.unset_maximized();
    }

    /// Request the window to be minimized
    pub fn set_minimized(&self) {
        self.shell_surface.set_minimized();
    }

    /// Request the window to be set fullscreen
    ///
    /// Note: The decorations hiding behavior is `Frame` dependant.
    /// To check whether you need to hide them consult your frame documentation.
    pub fn set_fullscreen(&self, output: Option<&wl_output::WlOutput>) {
        self.shell_surface.set_fullscreen(output);
    }

    /// Request the window to quit fullscreen mode
    pub fn unset_fullscreen(&self) {
        self.shell_surface.unset_fullscreen();
    }

    /// Sets the minimum possible size for this window
    ///
    /// Provide either a tuple `Some((width, height))` or `None` to unset the
    /// minimum size.
    ///
    /// Setting either value in the tuple to `0` means that this axis should not
    /// be limited.
    ///
    /// The provided size is the interior size, not counting decorations.
    ///
    /// This size is expressed in logical pixels, like the one received
    /// in [`Event::Configure`](enum.Event.html).
    pub fn set_min_size(&mut self, size: Option<(u32, u32)>) {
        let (w, h) = size.unwrap_or(MIN_WINDOW_SIZE);
        let (w, h) = self.frame.borrow_mut().add_borders(w as i32, h as i32);
        self.shell_surface.set_min_size(Some((w, h)));
        if let Some(ref mut inner) = *self.inner.borrow_mut() {
            inner.min_size = size.unwrap_or(MIN_WINDOW_SIZE)
        }
    }

    /// Sets the maximum possible size for this window
    ///
    /// Provide either a tuple `Some((width, height))` or `None` to unset the
    /// maximum size.
    ///
    /// Setting either value in the tuple to `0` means that this axis should not
    /// be limited.
    ///
    /// The provided size is the interior size, not counting decorations.
    ///
    /// This size is expressed in logical pixels, like the one received
    /// in [`Event::Configure`](enum.Event.html).
    pub fn set_max_size(&mut self, size: Option<(u32, u32)>) {
        let max_size = size.map(|(w, h)| self.frame.borrow_mut().add_borders(w as i32, h as i32));
        self.shell_surface.set_max_size(max_size);
        if let Some(ref mut inner) = *self.inner.borrow_mut() {
            inner.max_size = size.map(|(w, h)| (w as u32, h as u32));
        }
    }

    /// Sets the frame configuration for the window
    ///
    /// This allows to configure the frame at runtime if it supports
    /// it. See the documentation of your `Frame` implementation for
    /// details about what configuration it supports.
    pub fn set_frame_config(&mut self, config: F::Config) {
        self.frame.borrow_mut().set_config(config)
    }

    /// Start an interactive, user-driven move of the surface
    ///
    /// This request must be used in response to some sort of user action
    /// like a button press, key press, or touch down event. The passed
    /// serial is used to determine the type of interactive move (touch,
    /// pointer, etc).
    ///
    /// The server may ignore move requests depending on the state of
    /// the surface (e.g. fullscreen or maximized), or if the passed serial
    /// is no longer valid.
    pub fn start_interactive_move(&self, seat: &wl_seat::WlSeat, serial: u32) {
        self.shell_surface.move_(seat, serial);
    }
}

impl<F: Frame> Drop for Window<F> {
    fn drop(&mut self) {
        self.inner.borrow_mut().take();

        // Destroy decorations manager, so the inner frame could be dropped.
        if let Some(decoration) = self.decoration.take() {
            decoration.destroy();
        }
    }
}

impl<F: Frame> fmt::Debug for Window<F>
where
    F: fmt::Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Window")
            .field("frame", &self.frame)
            .field("surface", &self.surface)
            .field("decoration", &self.decoration)
            .field("shell_surface", &self.shell_surface)
            .field("inner", &self.inner)
            .field("_seat_listener", &self._seat_listener)
            .finish()
    }
}

/// Request generated by a Frame
///
/// These requests are generated by a Frame and the Window will
/// forward them appropriately to the server.
#[derive(Debug)]
pub enum FrameRequest {
    /// The window should be minimized
    Minimize,
    /// The window should be maximized
    Maximize,
    /// The window should be unmaximized
    UnMaximize,
    /// The window should be closed
    Close,
    /// An interactive move should be started
    Move(wl_seat::WlSeat),
    /// An interactive resize should be started
    Resize(wl_seat::WlSeat, ResizeEdge),
    /// Show window menu.
    ShowMenu(wl_seat::WlSeat, i32, i32),
    /// The frame requests to be refreshed
    Refresh,
}

/// Interface for defining the drawing of decorations
///
/// A type implementing this trait can be used to define custom
/// decorations additionnaly to the ones provided by this crate
/// and be used with `Window`.
pub trait Frame: Sized {
    /// Type of errors that may occur when attempting to create a frame
    type Error;
    /// Configuration for this frame
    type Config;
    /// Initialize the Frame.
    ///
    /// Providing non `None` to `theme_manager` should prevent `Frame` to theme pointer
    /// over `base_surface` surface.
    fn init(
        base_surface: &wl_surface::WlSurface,
        compositor: &Attached<wl_compositor::WlCompositor>,
        subcompositor: &Attached<wl_subcompositor::WlSubcompositor>,
        shm: &Attached<wl_shm::WlShm>,
        theme_manager: Option<ThemeManager>,
        callback: Box<dyn FnMut(FrameRequest, u32, DispatchData)>,
    ) -> Result<Self, Self::Error>;
    /// Set the Window XDG states for the frame
    ///
    /// This notably includes information about whether the window is
    /// maximized, active, or tiled, and can affect the way decorations
    /// are drawn.
    ///
    /// Calling this should *not* trigger a redraw, but return `true` if
    /// a redraw is needed.
    fn set_states(&mut self, states: &[State]) -> bool;
    /// Hide or show the decorations
    ///
    /// Calling this should *not* trigger a redraw
    fn set_hidden(&mut self, hidden: bool);
    /// Set whether interactive resize hints should be displayed
    /// and reacted to
    fn set_resizable(&mut self, resizable: bool);
    /// Notify that a new wl_seat should be handled
    ///
    /// This seat is guaranteed to have pointer capability
    fn new_seat(&mut self, seat: &Attached<wl_seat::WlSeat>);
    /// Notify that this seat has lost the pointer capability or
    /// has been lost
    fn remove_seat(&mut self, seat: &wl_seat::WlSeat);
    /// Change the size of the decorations
    ///
    /// Calling this should *not* trigger a redraw
    fn resize(&mut self, newsize: (u32, u32));
    /// Redraw the decorations
    fn redraw(&mut self);
    /// Subtracts the border dimensions from the given dimensions.
    fn subtract_borders(&self, width: i32, height: i32) -> (i32, i32);
    /// Adds the border dimensions to the given dimensions.
    fn add_borders(&self, width: i32, height: i32) -> (i32, i32);
    /// Returns the coordinates of the top-left corner of the borders relative to the content
    ///
    /// Values should thus be negative
    fn location(&self) -> (i32, i32) {
        (0, 0)
    }
    /// Sets the configuration for the frame
    fn set_config(&mut self, config: Self::Config);

    /// Sets the frames title
    fn set_title(&mut self, title: String);
}

impl<E> Environment<E>
where
    E: GlobalHandler<wl_compositor::WlCompositor>
        + GlobalHandler<wl_subcompositor::WlSubcompositor>
        + GlobalHandler<wl_shm::WlShm>
        + crate::shell::ShellHandling
        + MultiGlobalHandler<wl_seat::WlSeat>
        + GlobalHandler<ZxdgDecorationManagerV1>
        + crate::seat::SeatHandling,
{
    /// Create a new window wrapping given surface
    ///
    /// This window handles decorations for you, this includes
    /// drawing them if the compositor doe snot support them, resizing interactions
    /// and moving the window. It also provides close/maximize/minimize buttons.
    ///
    /// Many interactions still require your input, and are given to you via the
    /// callback you need to provide.
    pub fn create_window<F: Frame + 'static, CB>(
        &self,
        surface: wl_surface::WlSurface,
        theme_manager: Option<ThemeManager>,
        initial_dims: (u32, u32),
        callback: CB,
    ) -> Result<Window<F>, F::Error>
    where
        CB: FnMut(Event, DispatchData) + 'static,
    {
        Window::<F>::init_with_decorations(self, surface, theme_manager, initial_dims, callback)
    }
}
