use std::{ops::Rem, usize};

use indexmap::IndexMap;
use log::error;
use num_traits::Zero;

use crate::backends::structs::WindowType;
use crate::util::BuildIdentityHasher;
use crate::util::{Point, Size};

mod client {
    use std::hash::{Hash, Hasher};

    use crate::{
        backends::structs::WindowType,
        util::{Point, Size},
    };
    use x11::xlib::Window;

    #[derive(Clone, Debug)]
    pub struct Client {
        pub(crate) window: Window,
        pub(crate) size: Size<i32>,
        pub(crate) position: Point<i32>,
        pub(crate) parent_window: Option<Window>,
        pub(crate) window_type: WindowType,
        pub(crate) fullscreen: bool,
    }

    impl Default for Client {
        fn default() -> Self {
            Self {
                window: 0,
                size: (100, 100).into(),
                position: (0, 0).into(),
                parent_window: None,
                fullscreen: false,
                window_type: WindowType::Normal,
            }
        }
    }

    impl Client {
        #[allow(dead_code)]
        pub fn new(
            window: Window,
            size: Size<i32>,
            position: Point<i32>,
        ) -> Self {
            Self {
                window,
                size,
                position,
                ..Self::default()
            }
        }

        pub fn new_dialog(
            window: Window,
            size: Size<i32>,
            transient: Window,
        ) -> Self {
            Self {
                window,
                size,
                parent_window: Some(transient),
                ..Default::default()
            }
        }

        pub fn new_default(window: Window) -> Self {
            Self {
                window,
                ..Default::default()
            }
        }

        pub fn with_window_type(self, window_type: WindowType) -> Self {
            Self {
                window_type,
                ..self
            }
        }

        pub fn with_parent_window(self, parent_window: Option<Window>) -> Self {
            Self {
                parent_window,
                ..self
            }
        }

        pub fn with_size(self, size: Size<i32>) -> Self {
            Self { size, ..self }
        }

        /// toggles the clients fullscreen flag.
        /// returns `true` if the client is now fullscreen.
        pub fn toggle_fullscreen(&mut self) -> bool {
            self.fullscreen = !self.fullscreen;

            self.is_fullscreen()
        }

        pub fn set_fullscreen(&mut self, fullscreen: bool) -> bool {
            if self.fullscreen == fullscreen {
                false
            } else {
                self.fullscreen = fullscreen;
                true
            }
        }

        pub fn is_fullscreen(&self) -> bool {
            self.fullscreen
        }

        pub fn has_parent_window(&self) -> bool {
            self.parent_window.is_some()
        }
    }

    impl Hash for Client {
        fn hash<H: Hasher>(&self, state: &mut H) {
            self.window.hash(state);
        }
    }

    impl PartialEq for Client {
        fn eq(&self, other: &Self) -> bool {
            self.window == other.window
        }
    }

    impl Eq for Client {}

    pub trait ClientKey {
        fn key(&self) -> u64;
    }

    impl<'a> PartialEq for (dyn ClientKey + 'a) {
        fn eq(&self, other: &Self) -> bool {
            self.key() == other.key()
        }
    }

    impl<'a> Eq for (dyn ClientKey + 'a) {}

    impl<'a> Hash for (dyn ClientKey + 'a) {
        fn hash<H: Hasher>(&self, state: &mut H) {
            self.key().hash(state);
        }
    }

    impl ClientKey for Client {
        fn key(&self) -> u64 {
            self.window
        }
    }

    impl ClientKey for Window {
        fn key(&self) -> u64 {
            self.to_owned()
        }
    }
}

pub use client::*;

type Clients = IndexMap<u64, Client, BuildIdentityHasher>;
type ClientRef = u64;
type ClientRefs = Vec<ClientRef>;

#[derive(Debug)]
/// Used to wrap a `&` or `&mut` to a Client type.
pub enum ClientEntry<T> {
    /// Entry of a tiled client in the `ClientList`
    Tiled(T),
    /// Entry of a floating client in the `ClientList`
    Floating(T),
    /// `None` variant equivalent
    Vacant,
}

#[derive(Debug)]
pub struct ClientState {
    pub(self) clients: Clients,
    pub(self) floating_clients: Clients,
    focused: Option<ClientRef>,
    pub(self) virtual_screens: VirtualScreenStore,

    pub(self) gap: i32,
    pub(self) screen_size: Size<i32>,
    pub(self) master_size: f32,
    border_size: i32,
}

#[derive(Debug, Clone)]
struct VirtualScreen {
    master: ClientRefs,
    aux: ClientRefs,
}

#[derive(Debug)]
struct VirtualScreenStore {
    screens: Vec<VirtualScreen>,
    current_idx: usize,
    last_idx: Option<usize>,
}

impl Default for ClientState {
    fn default() -> Self {
        Self {
            clients: Default::default(),
            floating_clients: Default::default(),
            focused: None,
            virtual_screens: VirtualScreenStore::new(1),
            gap: 0,
            screen_size: (1, 1).into(),
            master_size: 1.0,
            border_size: 0,
        }
    }
}

impl ClientState {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_gap(self, gap: i32) -> Self {
        Self { gap, ..self }
    }

    pub fn with_border(self, border: i32) -> Self {
        Self {
            border_size: border,
            ..self
        }
    }

    pub fn with_screen_size(self, screen_size: Size<i32>) -> Self {
        Self {
            screen_size,
            ..self
        }
    }

    pub fn with_virtualscreens(self, num: usize) -> Self {
        Self {
            virtual_screens: VirtualScreenStore::new(num),
            ..self
        }
    }

    pub fn get_border(&self) -> i32 {
        self.border_size
    }

    #[allow(dead_code)]
    pub fn set_border_mut(&mut self, new: i32) {
        self.border_size = new;
    }

    pub fn insert(&mut self, mut client: Client) -> Option<&Client> {
        let key = client.key();

        match client.window_type {
            // idk how to handle docks and desktops, for now they float innit
            WindowType::Splash
            | WindowType::Dialog
            | WindowType::Utility
            | WindowType::Menu
            | WindowType::Toolbar
            | WindowType::Dock
            | WindowType::Desktop => {
                if let Some(parent) = client
                    .parent_window
                    .and_then(|window| self.get(&window).into_option())
                {
                    client.position = {
                        (
                            parent.position.x
                                + (parent.size.width - client.size.width) / 2,
                            parent.position.y
                                + (parent.size.height - client.size.height) / 2,
                        )
                            .into()
                    };
                }

                client.size = client.size.clamp(
                    self.screen_size
                        - Size::new(self.border_size * 2, self.border_size * 2),
                );

                self.floating_clients.insert(key, client);
            }
            WindowType::Normal => {
                self.clients.insert(key, client);
                self.virtual_screens.get_mut_current().insert(&key);
            }
        }

        // adding a client changes the liling layout, rearrange
        self.arrange_virtual_screen();

        // TODO: eventually make this function return a `ClientEntry` instead of an `Option`.
        self.get(&key).into_option()
    }

    pub fn remove<K>(&mut self, key: &K)
    where
        K: ClientKey,
    {
        if let Some(focused_client) = self.focused {
            if focused_client == key.key() {
                self.focused = None;
            }
        }

        self.remove_from_virtual_screens(key);

        self.clients.remove(&key.key());
        self.floating_clients.remove(&key.key());

        // removing a client changes the liling layout, rearrange
        self.arrange_virtual_screen();
    }

    pub fn contains<K>(&self, key: &K) -> bool
    where
        K: ClientKey,
    {
        let key = key.key();

        self.clients.contains_key(&key)
            || self.floating_clients.contains_key(&key)
    }

    pub fn iter_floating(&self) -> impl Iterator<Item = (&u64, &Client)> {
        self.floating_clients.iter()
    }

    pub fn iter_floating_visible(
        &self,
    ) -> impl Iterator<Item = (&u64, &Client)> {
        self.floating_clients
            .iter()
            .filter(move |&(k, _)| self.is_client_visible(k))
    }

    pub fn iter_all_clients(&self) -> impl Iterator<Item = (&u64, &Client)> {
        self.floating_clients.iter().chain(self.clients.iter())
    }

    pub fn iter_hidden(&self) -> impl Iterator<Item = (&u64, &Client)> {
        self.iter_all_clients()
            .filter(move |&(k, _)| !self.is_client_visible(k))
    }

    pub fn iter_transient(&self) -> impl Iterator<Item = (&u64, &Client)> {
        self.iter_floating().filter(|&(_, c)| c.has_parent_window())
    }

    pub fn iter_by_window_type(
        &self,
        window_type: WindowType,
    ) -> impl Iterator<Item = (&u64, &Client)> {
        self.iter_floating()
            .filter(move |&(_, c)| c.window_type == window_type)
    }

    pub fn iter_visible(&self) -> impl Iterator<Item = (&u64, &Client)> {
        self.iter_all_clients()
            .filter(move |&(k, _)| self.is_client_visible(k))
    }

    #[allow(dead_code)]
    pub fn iter_current_screen(&self) -> impl Iterator<Item = (&u64, &Client)> {
        self.clients.iter().filter(move |&(k, _)| {
            self.virtual_screens.get_current().contains(k)
        })
    }

    pub fn iter_master_stack(&self) -> impl Iterator<Item = (&u64, &Client)> {
        self.virtual_screens
            .get_current()
            .master
            .iter()
            .map(move |k| (k, self.get(k).unwrap()))
    }

    pub fn iter_aux_stack(&self) -> impl Iterator<Item = (&u64, &Client)> {
        self.virtual_screens
            .get_current()
            .aux
            .iter()
            .map(move |k| (k, self.get(k).unwrap()))
    }

    fn is_client_visible<K>(&self, key: &K) -> bool
    where
        K: ClientKey,
    {
        match self.get(key) {
            ClientEntry::Floating(c) => {
                if let Some(transient_for) = c.parent_window {
                    self.is_client_visible(&transient_for)
                } else {
                    true
                }
            }
            ClientEntry::Tiled(_) => {
                self.virtual_screens.get_current().contains(key)
            }
            _ => false,
        }
    }

    pub fn get<K>(&self, key: &K) -> ClientEntry<&Client>
    where
        K: ClientKey,
    {
        match self.clients.get(&key.key()) {
            Some(client) => ClientEntry::Tiled(client),
            None => match self.floating_clients.get(&key.key()) {
                Some(client) => ClientEntry::Floating(client),
                None => ClientEntry::Vacant,
            },
        }
    }

    pub fn get_mut<K>(&mut self, key: &K) -> ClientEntry<&mut Client>
    where
        K: ClientKey,
    {
        match self.clients.get_mut(&key.key()) {
            Some(client) => ClientEntry::Tiled(client),
            None => match self.floating_clients.get_mut(&key.key()) {
                Some(client) => ClientEntry::Floating(client),
                None => ClientEntry::Vacant,
            },
        }
    }

    pub fn get_focused(&self) -> ClientEntry<&Client> {
        if let Some(focused) = self.focused {
            self.get(&focused)
        } else {
            ClientEntry::Vacant
        }
    }

    pub fn go_to_nth_virtualscreen(&mut self, n: usize) {
        self.virtual_screens.go_to_nth(n);

        self.arrange_virtual_screen();
    }

    pub fn rotate_right(&mut self, n: usize) {
        self.virtual_screens
            .rotate_right(n.rem(self.virtual_screens.len()));

        self.arrange_virtual_screen();
    }

    pub fn rotate_left(&mut self, n: usize) {
        self.virtual_screens
            .rotate_left(n.rem(self.virtual_screens.len()));

        self.arrange_virtual_screen();
    }

    pub fn rotate_back(&mut self) {
        self.virtual_screens.go_back();

        self.arrange_virtual_screen();
    }

    pub fn set_fullscreen<K>(&mut self, key: &K, fullscreen: bool) -> bool
    where
        K: ClientKey,
    {
        self.get(key)
            .into_option()
            .map(|client| client.is_fullscreen() != fullscreen)
            .unwrap_or(false)
            .then(|| self.toggle_fullscreen(key))
            .unwrap_or(false)
    }

    /// returns `true` if window layout changed
    pub fn toggle_fullscreen<K>(&mut self, key: &K) -> bool
    where
        K: ClientKey,
    {
        if let Some(_new_fullscreen_state) = self.inner_toggle_fullscreen(key) {
            self.arrange_virtual_screen();
            true
        } else {
            false
        }
    }

    fn inner_toggle_fullscreen<K>(&mut self, key: &K) -> Option<bool>
    where
        K: ClientKey,
    {
        let fullscreen_size = self.screen_size;

        self.get_mut(key).into_option().map(|client| {
            if client.toggle_fullscreen() {
                client.size = fullscreen_size;
                client.position = Point::zero();

                true
            } else {
                false
            }
        })
    }

    /**
    Sets a tiled client to floating and returns true, does nothing for a floating client and
    returns false. If this function returns `true` you have to call `arrange_clients` after.
    */
    pub fn set_floating<K>(&mut self, key: &K) -> bool
    where
        K: ClientKey,
    {
        if self.get(key).is_tiled() {
            self.toggle_floating(key);

            true
        } else {
            false
        }
    }

    /**
    Sets a floating client to tiled and returns true, does nothing for a tiled client and
    returns false. If this function returns `true` you have to call `arrange_clients` after.
     */
    pub fn set_tiled<K>(&mut self, key: &K) -> bool
    where
        K: ClientKey,
    {
        if self.get(key).is_floating() {
            self.toggle_floating(key);

            true
        } else {
            false
        }
    }

    /**
    This function invalidates the tiling, call `arrange_clients` to fix it again (it doesn't do it
    automatically since xlib has to move and resize all windows anyways).
    */
    pub fn toggle_floating<K>(&mut self, key: &K)
    where
        K: ClientKey,
    {
        // do nothing if either no client matches the key or the client is fullscreen.
        // FIXME: this should probably disable fullscreen mode (but that has to
        // be handled in the wm state so that the backend can notify the client
        // that it is no longer fullscreen)
        if !self
            .get(key)
            .into_option()
            .map(|c| c.is_fullscreen())
            .unwrap_or(true)
        {
            let key = key.key();
            let client = self.clients.remove(&key);
            let floating_client = self.floating_clients.remove(&key);

            match (client, floating_client) {
                (Some(client), None) => {
                    self.floating_clients.insert(key, client);
                    self.remove_from_virtual_screens(&key);
                }
                (None, Some(floating_client)) => {
                    // transient clients cannot be tiled
                    // only normal windows can be tiled
                    match floating_client.window_type {
                        WindowType::Normal => {
                            self.clients.insert(key, floating_client);
                            self.virtual_screens.get_mut_current().insert(&key);
                        }
                        _ => {
                            self.floating_clients.insert(key, floating_client);
                        }
                    }
                }
                _ => {
                    error!(
                        "wtf? Client was present in tiled and floating list."
                    )
                }
            };

            // we added or removed a client from the tiling so the layout changed, rearrange
            self.arrange_virtual_screen();
        }
    }

    fn remove_from_virtual_screens<K>(&mut self, key: &K)
    where
        K: ClientKey,
    {
        if self.contains(key) {
            if let Some(vs) = self.get_mut_virtualscreen_for_client(key) {
                vs.remove(key);

                // we removed a client so the layout changed, rearrange
                self.arrange_virtual_screen();
            }
        }
    }

    fn get_virtualscreen_for_client<K>(&self, key: &K) -> Option<&VirtualScreen>
    where
        K: ClientKey,
    {
        self.virtual_screens.iter().find_map(|vs| {
            if vs.contains(key) {
                Some(vs)
            } else {
                None
            }
        })
    }

    fn get_mut_virtualscreen_for_client<K>(
        &mut self,
        key: &K,
    ) -> Option<&mut VirtualScreen>
    where
        K: ClientKey,
    {
        self.virtual_screens.iter_mut().find_map(|vs| {
            if vs.contains(key) {
                Some(vs)
            } else {
                None
            }
        })
    }

    pub fn get_stack_for_client<K>(&self, key: &K) -> Option<&Vec<u64>>
    where
        K: ClientKey,
    {
        if let Some(vs) = self.get_virtualscreen_for_client(key) {
            if vs.is_in_aux(key) {
                Some(&vs.aux)
            } else if vs.is_in_master(key) {
                Some(&vs.master)
            } else {
                None
            }
        } else {
            None
        }
    }

    /**
    focuses client `key` if it contains `key` and returns a reference to the  newly and the previously
    focused clients if any.
    */
    pub fn focus_client<K>(
        &mut self,
        key: &K,
    ) -> (ClientEntry<&Client>, ClientEntry<&Client>)
    where
        K: ClientKey,
    {
        let (new, old) = self.focus_client_inner(key);

        if !(new.is_vacant() && old.is_vacant()) {
            //info!("Swapping focus: new({:?}) old({:?})", new, old);
        }

        (new, old)
    }

    fn focus_client_inner<K>(
        &mut self,
        key: &K,
    ) -> (ClientEntry<&Client>, ClientEntry<&Client>)
    where
        K: ClientKey,
    {
        // if `key` is not a valid entry into the client list, do nothing
        if self.contains(key) {
            // check if we currently have a client focused
            match self.focused {
                Some(focused) => {
                    if focused == key.key() {
                        // if `key` is a reference to the focused client
                        // dont bother unfocusing and refocusing the same client

                        (ClientEntry::Vacant, ClientEntry::Vacant)
                    } else {
                        // focus the new client and return reference to it
                        // and the previously focused client.

                        self.focused = Some(key.key());
                        (self.get(key), self.get(&focused))
                    }
                }

                None => {
                    // not currently focusing any client
                    // just focus and return the client `key` references

                    self.focused = Some(key.key());
                    (self.get(key), ClientEntry::Vacant)
                }
            }
        } else {
            // nothing happened, return nothing

            (ClientEntry::Vacant, ClientEntry::Vacant)
        }
    }

    /**
    sets `self.focused` to `None` and returns a reference to
    the previously focused Client if any.
    */
    #[allow(dead_code)]
    pub fn unfocus(&mut self) -> ClientEntry<&Client> {
        match self.focused {
            Some(focused) => {
                self.focused = None;
                self.get(&focused)
            }
            None => ClientEntry::Vacant,
        }
    }

    #[allow(dead_code)]
    pub fn is_focused<K>(&self, key: &K) -> bool
    where
        K: ClientKey,
    {
        match self.focused {
            Some(focused) => focused == key.key(),
            None => false,
        }
    }

    pub fn switch_stack_for_client<K>(&mut self, key: &K)
    where
        K: ClientKey,
    {
        if let Some(vs) = self.get_mut_virtualscreen_for_client(key) {
            vs.switch_stack_for_client(key);

            self.arrange_virtual_screen();
        }
    }

    /**
    resizes and moves clients on the current virtual screen with `width` and `height` as
    screen width and screen height.
    Optionally adds a gap between windows `gap.unwrap_or(0)` pixels wide.
    */
    pub fn arrange_virtual_screen(&mut self) {
        let gap = self.gap;
        let (width, height) = self.screen_size.as_tuple();

        // should be fine to unwrap since we will always have at least 1 virtual screen
        let vs = self.virtual_screens.get_mut_current();
        // if aux is empty -> width : width / 2

        let vs_width = width - gap * 2;

        let master_position = Point::new(0, 0);
        let master_window_size = {
            let factor = if vs.aux.is_empty() {
                1.0
            } else {
                self.master_size / 2.0
            };

            let width = (vs_width as f32 * factor) as i32;

            // make sure we dont devide by 0
            // height is max height / number of clients in the stack
            let height = match vs.master.len() as i32 {
                0 => 1,
                n => (height - gap * 2) / n,
            };

            Size::new(width, height)
        };

        let aux_position = Point::new(master_window_size.width, 0);
        let aux_window_size = {
            let width = vs_width - master_window_size.width;

            // make sure we dont devide by 0
            // height is max height / number of clients in the stack
            let height = match vs.aux.len() as i32 {
                0 => 1,
                n => (height - gap * 2) / n,
            };

            Size::new(width, height)
        };

        fn calculate_window_dimensions(
            screen_size: Size<i32>,
            stack_size: Size<i32>,
            stack_position: Point<i32>,
            fullscreen: bool,
            nth: i32,
            gap: i32,
            border: i32,
        ) -> (Size<i32>, Point<i32>) {
            if fullscreen {
                let size = Size::new(screen_size.width, screen_size.height);
                let pos = Point::new(0, 0);
                (size, pos)
            } else {
                let size = Size::new(
                    stack_size.width - gap * 2 - border * 2,
                    stack_size.height - gap * 2 - border * 2,
                );
                let pos = Point::new(
                    stack_position.x + gap * 2,
                    stack_position.y + stack_size.height * nth + gap * 2,
                );
                (size, pos)
            }
        }

        // Master
        for (i, key) in vs.master.iter().enumerate() {
            if let Some(client) = self.clients.get_mut(key) {
                let (size, position) = calculate_window_dimensions(
                    self.screen_size.into(),
                    master_window_size,
                    master_position,
                    client.is_fullscreen(),
                    i as i32,
                    gap,
                    self.border_size,
                );

                *client = Client {
                    size: size.into(),
                    position,
                    ..*client
                };
            }
        }

        // Aux
        for (i, key) in vs.aux.iter().enumerate() {
            if let Some(client) = self.clients.get_mut(key) {
                let (size, position) = calculate_window_dimensions(
                    self.screen_size.into(),
                    aux_window_size,
                    aux_position,
                    client.is_fullscreen(),
                    i as i32,
                    gap,
                    self.border_size,
                );

                *client = Client {
                    size: size.into(),
                    position,
                    ..*client
                };
            }
        }

        // Should have xlib send those changes back to the x server after this function
    }

    pub fn change_master_size(&mut self, delta: f32) {
        let tmp = self.master_size + delta;
        self.master_size = f32::min(1.8, f32::max(0.2, tmp));

        self.arrange_virtual_screen();
    }
}

impl Default for VirtualScreen {
    fn default() -> Self {
        Self {
            master: Default::default(),
            aux: Default::default(),
        }
    }
}

impl VirtualScreen {
    fn contains<K>(&self, key: &K) -> bool
    where
        K: ClientKey,
    {
        self.master.contains(&key.key()) || self.aux.contains(&key.key())
    }

    fn is_in_master<K>(&self, key: &K) -> bool
    where
        K: ClientKey,
    {
        self.master.contains(&key.key())
    }

    fn is_in_aux<K>(&self, key: &K) -> bool
    where
        K: ClientKey,
    {
        self.aux.contains(&key.key())
    }

    fn insert<K>(&mut self, key: &K)
    where
        K: ClientKey,
    {
        self.aux.push(key.key());

        self.refresh();
    }

    fn remove<K>(&mut self, key: &K)
    where
        K: ClientKey,
    {
        let key = key.key();
        self.master.retain(|k| *k != key);
        self.aux.retain(|k| *k != key);

        self.refresh();
    }

    fn switch_stack_for_client<K>(&mut self, key: &K)
    where
        K: ClientKey,
    {
        match self.master.iter().position(|&k| k == key.key()) {
            Some(index) => {
                self.aux.extend(self.master.drain(index..=index));
            }
            None => {
                let index =
                    self.aux.iter().position(|&k| k == key.key()).unwrap();
                self.master.extend(self.aux.drain(index..=index));
            }
        }

        self.refresh();
    }

    /**
    if `self.master` is empty but `self.aux` has at least one client, drain from aux to master
    this ensures that if only 1 `Client` is on this `VirtualScreen` it will be on the master stack
    */
    fn refresh(&mut self) {
        if self.master.is_empty() && !self.aux.is_empty() {
            self.master.extend(self.aux.drain(..1));
        }
    }
}

impl VirtualScreenStore {
    fn new(n: usize) -> Self {
        let mut screens = Vec::with_capacity(n);
        screens.resize_with(n, Default::default);

        Self {
            screens,
            current_idx: 0,
            last_idx: None,
        }
    }

    fn get_current(&self) -> &VirtualScreen {
        &self.screens[self.current_idx]
    }

    fn get_mut_current(&mut self) -> &mut VirtualScreen {
        &mut self.screens[self.current_idx]
    }

    fn len(&self) -> usize {
        self.screens.len()
    }

    fn iter(&self) -> impl Iterator<Item = &VirtualScreen> {
        self.screens.iter()
    }

    fn iter_mut(&mut self) -> impl Iterator<Item = &mut VirtualScreen> {
        self.screens.iter_mut()
    }

    fn go_back(&mut self) -> usize {
        self.last_idx
            .and_then(|n| Some(self.go_to_nth(n)))
            .unwrap_or(self.current_idx)
    }

    fn rotate_left(&mut self, n: usize) -> usize {
        self.last_idx = Some(self.current_idx);

        let l = self.screens.len();
        let a = n % l;
        let b = self.current_idx % l;

        self.current_idx = ((b + l) - a) % l;

        self.current_idx
    }

    fn rotate_right(&mut self, n: usize) -> usize {
        self.last_idx = Some(self.current_idx);

        let l = self.screens.len();
        let a = n % l;
        let b = self.current_idx % l;

        self.current_idx = ((b + l) + a) % l;

        self.current_idx
    }

    fn go_to_nth(&mut self, n: usize) -> usize {
        self.last_idx = Some(self.current_idx);

        self.current_idx = n.min(self.screens.len() - 1);

        self.current_idx
    }
}

impl<T> Into<Option<T>> for ClientEntry<T> {
    fn into(self) -> Option<T> {
        match self {
            Self::Vacant => None,
            Self::Tiled(client) | Self::Floating(client) => Some(client),
        }
    }
}

impl<T> ClientEntry<T> {
    pub fn into_option(self) -> Option<T> {
        self.into()
    }

    pub fn unwrap(self) -> T {
        self.into_option().unwrap()
    }

    pub fn is_vacant(&self) -> bool {
        match self {
            ClientEntry::Vacant => true,
            _ => false,
        }
    }

    #[allow(dead_code)]
    pub fn is_floating(&self) -> bool {
        match self {
            ClientEntry::Floating(_) => true,
            _ => false,
        }
    }

    pub fn is_tiled(&self) -> bool {
        match self {
            ClientEntry::Tiled(_) => true,
            _ => false,
        }
    }

    #[allow(dead_code)]
    pub fn is_occupied(&self) -> bool {
        !self.is_vacant()
    }
}

impl ClientEntry<&client::Client> {
    pub fn is_fullscreen(&self) -> bool {
        match self {
            ClientEntry::Tiled(c) | ClientEntry::Floating(c) => {
                c.is_fullscreen()
            }
            ClientEntry::Vacant => false,
        }
    }
}

impl ClientEntry<&mut client::Client> {
    pub fn is_fullscreen(&self) -> bool {
        match self {
            ClientEntry::Tiled(c) | ClientEntry::Floating(c) => {
                c.is_fullscreen()
            }
            ClientEntry::Vacant => false,
        }
    }
}