use {
    crate::{
        client::Client,
        cursor::KnownCursor,
        fixed::Fixed,
        ifs::{
            wl_seat::{NodeSeatState, SeatId, WlSeatGlobal},
            wl_surface::{SurfaceExt, SurfaceRole, WlSurface, WlSurfaceError},
        },
        rect::Rect,
        render::Renderer,
        state::State,
        tree::{
            Direction, FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, StackedNode,
            ToplevelData, ToplevelNode,
        },
        utils::{clonecell::CloneCell, copyhashmap::CopyHashMap, linkedlist::LinkedNode},
        wire::WlSurfaceId,
        wire_xcon::CreateNotify,
        xwayland::XWaylandEvent,
    },
    bstr::BString,
    std::{
        cell::{Cell, RefCell},
        ops::{Deref, Not},
        rc::Rc,
    },
    thiserror::Error,
};

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum XInputModel {
    None,
    Passive,
    Local,
    Global,
}

impl Default for XInputModel {
    fn default() -> Self {
        Self::Passive
    }
}

#[derive(Default, Debug)]
pub struct IcccmHints {
    pub flags: Cell<i32>,
    pub input: Cell<bool>,
    pub initial_state: Cell<i32>,
    pub icon_pixmap: Cell<u32>,
    pub icon_window: Cell<u32>,
    pub icon_x: Cell<i32>,
    pub icon_y: Cell<i32>,
    pub icon_mask: Cell<u32>,
    pub window_group: Cell<u32>,
}

#[derive(Default, Debug)]
pub struct SizeHints {
    pub flags: Cell<u32>,
    pub x: Cell<i32>,
    pub y: Cell<i32>,
    pub width: Cell<i32>,
    pub height: Cell<i32>,
    pub min_width: Cell<i32>,
    pub min_height: Cell<i32>,
    pub max_width: Cell<i32>,
    pub max_height: Cell<i32>,
    pub width_inc: Cell<i32>,
    pub height_inc: Cell<i32>,
    pub min_aspect_num: Cell<i32>,
    pub min_aspect_den: Cell<i32>,
    pub max_aspect_num: Cell<i32>,
    pub max_aspect_den: Cell<i32>,
    pub base_width: Cell<i32>,
    pub base_height: Cell<i32>,
    pub win_gravity: Cell<u32>,
}

#[derive(Default, Debug)]
pub struct MotifHints {
    pub flags: Cell<u32>,
    pub decorations: Cell<u32>,
}

#[derive(Default, Debug)]
pub struct XwindowInfo {
    pub has_alpha: Cell<bool>,
    pub override_redirect: Cell<bool>,
    pub extents: Cell<Rect>,
    pub pending_extents: Cell<Rect>,
    pub instance: RefCell<Option<BString>>,
    pub class: RefCell<Option<BString>>,
    pub title: RefCell<Option<String>>,
    pub role: RefCell<Option<BString>>,
    pub protocols: CopyHashMap<u32, ()>,
    pub window_types: CopyHashMap<u32, ()>,
    pub never_focus: Cell<bool>,
    pub utf8_title: Cell<bool>,
    pub icccm_hints: IcccmHints,
    pub normal_hints: SizeHints,
    pub motif_hints: MotifHints,
    pub startup_id: RefCell<Option<BString>>,
    pub fullscreen: Cell<bool>,
    pub modal: Cell<bool>,
    pub maximized_vert: Cell<bool>,
    pub maximized_horz: Cell<bool>,
    pub minimized: Cell<bool>,
    pub pid: Cell<Option<u32>>,
    pub input_model: Cell<XInputModel>,
    pub mapped: Cell<bool>,
    pub wants_floating: Cell<bool>,
}

pub struct XwindowData {
    pub state: Rc<State>,
    pub window_id: u32,
    pub client: Rc<Client>,
    pub surface_id: Cell<Option<WlSurfaceId>>,
    pub window: CloneCell<Option<Rc<Xwindow>>>,
    pub info: XwindowInfo,
    pub children: CopyHashMap<u32, Rc<XwindowData>>,
    pub parent: CloneCell<Option<Rc<XwindowData>>>,
    pub stack_link: RefCell<Option<LinkedNode<Rc<XwindowData>>>>,
    pub map_link: Cell<Option<LinkedNode<Rc<XwindowData>>>>,
    pub startup_info: RefCell<Vec<u8>>,
    pub destroyed: Cell<bool>,
}

tree_id!(XwindowId);
pub struct Xwindow {
    pub id: XwindowId,
    pub seat_state: NodeSeatState,
    pub data: Rc<XwindowData>,
    pub surface: Rc<WlSurface>,
    pub display_link: RefCell<Option<LinkedNode<Rc<dyn StackedNode>>>>,
    pub toplevel_data: ToplevelData,
}

impl XwindowData {
    pub fn new(state: &Rc<State>, event: &CreateNotify, client: &Rc<Client>) -> Self {
        let extents = Rect::new_sized(
            event.x as _,
            event.y as _,
            event.width as _,
            event.height as _,
        )
        .unwrap();
        // log::info!("xwin {} new {:?} or {}", event.window, extents, event.override_redirect);
        Self {
            state: state.clone(),
            window_id: event.window,
            client: client.clone(),
            surface_id: Cell::new(None),
            window: Default::default(),
            info: XwindowInfo {
                override_redirect: Cell::new(event.override_redirect != 0),
                pending_extents: Cell::new(extents),
                ..Default::default()
            },
            children: Default::default(),
            parent: Default::default(),
            stack_link: Default::default(),
            map_link: Default::default(),
            startup_info: Default::default(),
            destroyed: Cell::new(false),
        }
    }

    pub fn is_ancestor_of(&self, mut other: Rc<Self>) -> bool {
        loop {
            if other.window_id == self.window_id {
                return true;
            }
            other = match other.parent.get() {
                Some(p) => p,
                _ => return false,
            }
        }
    }

    pub fn title_changed(&self) {
        let title = self.info.title.borrow_mut();
        if let Some(w) = self.window.get() {
            if let Some(p) = w.toplevel_data.parent.get() {
                p.node_child_title_changed(w.deref(), title.as_deref().unwrap_or(""));
            }
        }
    }
}

pub enum Change {
    None,
    Map,
    Unmap,
}

impl Xwindow {
    pub fn new(data: &Rc<XwindowData>, surface: &Rc<WlSurface>) -> Self {
        let tld = ToplevelData::new(
            &data.state,
            data.info.title.borrow_mut().clone().unwrap_or_default(),
            Some(surface.client.clone()),
        );
        tld.pos.set(surface.extents.get());
        Self {
            id: data.state.node_ids.next(),
            seat_state: Default::default(),
            data: data.clone(),
            surface: surface.clone(),
            display_link: Default::default(),
            toplevel_data: tld,
        }
    }

    pub fn destroy(&self) {
        self.break_loops();
        self.data.window.take();
    }

    pub fn break_loops(&self) {
        self.tl_destroy();
        self.surface.set_toplevel(None);
    }

    pub fn install(self: &Rc<Self>) -> Result<(), XWindowError> {
        self.surface.set_role(SurfaceRole::XSurface)?;
        if self.surface.ext.get().is_some() {
            return Err(XWindowError::AlreadyAttached);
        }
        self.surface.ext.set(self.clone());
        self.surface.set_toplevel(Some(self.clone()));
        Ok(())
    }

    pub fn is_mapped(&self) -> bool {
        self.toplevel_data.parent.get().is_some() || self.display_link.borrow_mut().is_some()
    }

    pub fn may_be_mapped(&self) -> bool {
        self.surface.buffer.get().is_some() && self.data.info.mapped.get()
    }

    fn map_change(&self) -> Change {
        match (self.may_be_mapped(), self.is_mapped()) {
            (true, false) => Change::Map,
            (false, true) => Change::Unmap,
            _ => Change::None,
        }
    }

    pub fn map_status_changed(self: &Rc<Self>) {
        let map_change = self.map_change();
        match map_change {
            Change::None => return,
            Change::Unmap => {
                self.data
                    .info
                    .pending_extents
                    .set(self.data.info.extents.take());
                self.tl_destroy();
            }
            Change::Map if self.data.info.override_redirect.get() => {
                self.clone()
                    .tl_change_extents(&self.data.info.pending_extents.get());
                *self.display_link.borrow_mut() =
                    Some(self.data.state.root.stacked.add_last(self.clone()));
                self.data.state.tree_changed();
            }
            Change::Map if self.data.info.wants_floating.get() => {
                let ws = self.data.state.float_map_ws();
                let ext = self.data.info.pending_extents.get();
                self.data
                    .state
                    .map_floating(self.clone(), ext.width(), ext.height(), &ws);
                self.data.title_changed();
            }
            Change::Map => {
                self.data.state.map_tiled(self.clone());
                self.data.title_changed();
            }
        }
        match map_change {
            Change::Unmap => self.tl_set_visible(false),
            Change::Map => self.tl_set_visible(true),
            Change::None => {}
        }
        self.data.state.tree_changed();
    }
}

impl SurfaceExt for Xwindow {
    fn post_commit(self: Rc<Self>) {
        self.map_status_changed();
    }

    fn on_surface_destroy(&self) -> Result<(), WlSurfaceError> {
        self.tl_destroy();
        self.surface.unset_ext();
        self.data.window.set(None);
        self.data.surface_id.set(None);
        self.data
            .state
            .xwayland
            .queue
            .push(XWaylandEvent::SurfaceDestroyed(self.surface.id));
        Ok(())
    }

    fn extents_changed(&self) {
        self.toplevel_data.pos.set(self.surface.extents.get());
        self.tl_extents_changed();
    }
}

impl Node for Xwindow {
    fn node_id(&self) -> NodeId {
        self.id.into()
    }

    fn node_seat_state(&self) -> &NodeSeatState {
        &self.seat_state
    }

    fn node_visit(self: Rc<Self>, visitor: &mut dyn NodeVisitor) {
        visitor.visit_xwindow(&self);
    }

    fn node_visit_children(&self, visitor: &mut dyn NodeVisitor) {
        visitor.visit_surface(&self.surface);
    }

    fn node_visible(&self) -> bool {
        self.surface.visible.get()
    }

    fn node_absolute_position(&self) -> Rect {
        self.data.info.extents.get()
    }

    fn node_do_focus(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, _direction: Direction) {
        seat.focus_toplevel(self.clone());
    }

    fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec<FoundNode>) -> FindTreeResult {
        if let Some(buffer) = self.surface.buffer.get() {
            if x < buffer.rect.width() && y < buffer.rect.height() {
                tree.push(FoundNode {
                    node: self.surface.clone(),
                    x,
                    y,
                });
                return FindTreeResult::AcceptsInput;
            }
        }
        FindTreeResult::Other
    }

    fn node_render(&self, renderer: &mut Renderer, x: i32, y: i32) {
        renderer.render_surface(&self.surface, x, y)
    }

    fn node_client(&self) -> Option<Rc<Client>> {
        Some(self.data.client.clone())
    }

    fn node_toplevel(self: Rc<Self>) -> Option<Rc<dyn ToplevelNode>> {
        Some(self)
    }

    fn node_on_pointer_enter(self: Rc<Self>, seat: &Rc<WlSeatGlobal>, _x: Fixed, _y: Fixed) {
        seat.enter_toplevel(self.clone());
    }

    fn node_on_pointer_focus(&self, seat: &Rc<WlSeatGlobal>) {
        // log::info!("wl-surface focus");
        seat.set_known_cursor(KnownCursor::Default);
    }
}

impl ToplevelNode for Xwindow {
    tl_node_impl!();

    fn tl_data(&self) -> &ToplevelData {
        &self.toplevel_data
    }

    fn tl_accepts_keyboard_focus(&self) -> bool {
        self.data.info.never_focus.get().not()
            && self.data.info.input_model.get() != XInputModel::None
    }

    fn tl_on_activate(&self) {
        self.data
            .state
            .xwayland
            .queue
            .push(XWaylandEvent::Activate(self.data.clone()));
    }

    fn tl_focus_child(&self, _seat: SeatId) -> Option<Rc<dyn Node>> {
        Some(self.surface.clone())
    }

    fn tl_change_extents(self: Rc<Self>, rect: &Rect) {
        // log::info!("xwin {} change_extents {:?}", self.data.window_id, rect);
        let old = self.data.info.extents.replace(*rect);
        if old != *rect {
            if self.toplevel_data.is_floating.get() {
                self.toplevel_data.float_width.set(rect.width());
                self.toplevel_data.float_height.set(rect.height());
            }
            if !self.data.info.override_redirect.get() {
                self.data
                    .state
                    .xwayland
                    .queue
                    .push(XWaylandEvent::Configure(self.clone()));
            }
            if old.position() != rect.position() {
                self.surface.set_absolute_position(rect.x1(), rect.y1());
            }
        }
    }

    fn tl_close(self: Rc<Self>) {
        self.data
            .state
            .xwayland
            .queue
            .push(XWaylandEvent::Close(self.data.clone()));
    }

    fn tl_set_visible(&self, visible: bool) {
        self.surface.set_visible(visible);
        self.seat_state.set_visible(self, visible);
    }

    fn tl_destroy(&self) {
        self.toplevel_data.destroy_node(self);
        self.display_link.borrow_mut().take();
        self.surface.destroy_node();
    }
}

impl StackedNode for Xwindow {
    stacked_node_impl!();

    fn stacked_set_visible(&self, visible: bool) {
        self.tl_set_visible(visible);
    }
}

#[derive(Debug, Error)]
pub enum XWindowError {
    #[error("The surface is already attached")]
    AlreadyAttached,
    #[error(transparent)]
    WlSurfaceError(#[from] WlSurfaceError),
}
