use std::{fmt, hash::Hash, iter};

use libcolors::message::WindowType;

use super::{
    layout::{Direction, NodeLayout, Rect},
    workspace::Workspace,
};

pub struct Display<T> {
    workspaces: Vec<Workspace<T>>,
    width: u32,
    height: u32,
    reservations_top: Vec<Reservation<T>>,
    reservation_height: u32,
    pub current_workspace: usize,
}

pub struct Reservation<T> {
    layout: NodeLayout,
    size: u32,
    wid: T,
}

impl<T: Eq + Hash + Copy> Display<T> {
    pub const MAX: usize = 10;

    pub fn new(width: u32, height: u32) -> Self {
        let workspaces = (0..Self::MAX)
            .map(|_| Workspace::new(width, height))
            .collect();
        Self {
            workspaces,
            width,
            height,
            reservations_top: Vec::new(),
            reservation_height: 0,
            current_workspace: 0,
        }
    }

    // Returns a mask, where each bit means a corresponding workspace has at least one window
    pub fn activity_mask(&self) -> u32 {
        let mut mask = 0;
        for (i, workspace) in self.workspaces.iter().enumerate() {
            if workspace.has_windows() {
                mask |= 1 << i;
            }
        }
        mask
    }

    pub fn resize(&mut self, width: u32, height: u32) {
        self.width = width;
        self.height = height;

        // Re-do reservation layout
        self.update_reservation_layout();

        for workspace in self.workspaces.iter_mut() {
            workspace.resize(width, height, self.reservation_height);
        }
    }

    pub fn workspace(&self, index: usize) -> &Workspace<T> {
        &self.workspaces[index]
    }

    pub fn workspace_mut(&mut self, index: usize) -> &mut Workspace<T> {
        &mut self.workspaces[index]
    }

    pub fn current_workspace(&self) -> &Workspace<T> {
        &self.workspaces[self.current_workspace]
    }

    pub fn current_workspace_mut(&mut self) -> &mut Workspace<T> {
        &mut self.workspaces[self.current_workspace]
    }

    pub fn all_windows(&self) -> impl Iterator<Item = (T, &NodeLayout)> + '_ {
        let reservation_windows = self.reservation_windows();
        let workspace_windows = self.current_workspace().all_windows();
        iter::chain(reservation_windows, workspace_windows)
    }

    pub fn dirty_windows(&self) -> impl Iterator<Item = (T, Rect)> + '_ {
        let reservation_windows = self.dirty_reservation_windows();
        let workspace_windows = self.current_workspace().dirty_windows();
        iter::chain(reservation_windows, workspace_windows)
    }

    pub fn window_frame(&self, wid: T) -> Option<Rect> {
        if let Some(reservation) = self.lookup_reservation(wid) {
            return reservation.layout.get();
        }
        self.current_workspace().window_frame(wid)
    }

    pub fn create_window(&mut self, wid: T, ty: &WindowType) -> Option<(bool, Option<usize>)> {
        match ty {
            &WindowType::Reservation(height) => {
                self.add_reservation(wid, height);
                Some((false, None))
            }
            WindowType::Default => {
                if !self.current_workspace_mut().create_window(wid) {
                    return None;
                }
                self.update_reservation_layout();
                Some((true, Some(self.current_workspace)))
            }
        }
    }

    pub fn move_window(&mut self, direction: Direction) -> bool
    where
        T: fmt::Debug,
    {
        if self.current_workspace_mut().move_window(direction) {
            self.update_reservation_layout();
            true
        } else {
            false
        }
    }

    pub fn remove_window(&mut self, wid: T) -> (bool, Option<usize>) {
        // TODO check if the window is a reservation
        let mut reservation_removed = false;
        self.reservations_top.retain(|res| {
            if res.wid == wid {
                reservation_removed = true;
                false
            } else {
                true
            }
        });

        if reservation_removed {
            self.update_layout(true);
            return (true, None);
        }

        for (i, workspace) in self.workspaces.iter_mut().enumerate() {
            if workspace.remove_window(wid) {
                self.update_reservation_layout();
                return (true, Some(i));
            }
        }

        (false, None)
    }

    pub fn send_focused_window(&mut self, target: usize) -> bool {
        let Some(focus_wid) = self.current_workspace().focused_window() else {
            return false;
        };
        if !self.workspaces[target].create_window(focus_wid) {
            return false;
        }
        self.workspaces[target].focus_window(focus_wid);
        self.current_workspace_mut().remove_window(focus_wid);
        true
    }

    pub fn add_reservation(&mut self, wid: T, height: u32) {
        self.reservations_top.push(Reservation {
            layout: NodeLayout::empty(),
            size: height,
            wid,
        });
        self.update_layout(true);
    }

    fn dirty_reservation_windows(&self) -> impl Iterator<Item = (T, Rect)> + '_ {
        self.reservation_windows().filter_map(|(wid, layout)| {
            if !layout.clear_dirty() {
                return None;
            }
            let rect = layout.get()?;
            Some((wid, rect))
        })
    }

    fn reservation_windows(&self) -> impl Iterator<Item = (T, &NodeLayout)> + '_ {
        self.reservations_top
            .iter()
            .map(|res| (res.wid, &res.layout))
    }

    fn lookup_reservation(&self, wid: T) -> Option<&Reservation<T>> {
        self.reservations_top.iter().find(|r| r.wid == wid)
    }

    // Will also update the workspaces' layouts if reservation height changes
    fn update_reservation_layout(&mut self) -> bool {
        let mut res_y = 0;
        for reservation in self.reservations_top.iter() {
            reservation.layout.set(Rect {
                x: 0,
                y: res_y,
                w: self.width,
                h: reservation.size,
            });
            res_y += reservation.size;
        }
        let changed = res_y != self.reservation_height;
        self.reservation_height = res_y;
        changed
    }

    fn update_layout(&mut self, force: bool) {
        if self.update_reservation_layout() || force {
            for workspace in self.workspaces.iter_mut() {
                workspace.resize(self.width, self.height, self.reservation_height);
            }
        }
    }

    // pub fn add_reservation(&mut self, wid: T, height: u32) {
    // }
}
