use std::rc::Rc;

use log::{error, info};

use x11::xlib::{self, ShiftMask, Window, XButtonEvent, XEvent, XKeyEvent, XMotionEvent};
use xlib::{
    ButtonPressMask, ButtonReleaseMask, Mod1Mask, PointerMotionMask, XConfigureRequestEvent,
    XCrossingEvent, XDestroyWindowEvent, XMapRequestEvent, XUnmapEvent,
};

use crate::{
    clients::{Client, ClientKey, ClientState},
    xlib::KeyOrButton,
    xlib::XLib,
};

pub struct WindowManager {
    clients: ClientState,
    move_window: Option<MoveWindow>,
    resize_window: Option<MoveWindow>,
    keybinds: Vec<KeyBinding>,
    xlib: XLib,
}

#[derive(Debug)]
pub enum Direction {
    Left,
    Right,
}

struct MoveWindow {
    key: Window,
    cached_cursor_position: (i32, i32),
}

#[derive(Clone)]
struct KeyBinding {
    key: KeyOrButton,
    closure: Rc<dyn Fn(&mut WindowManager, &XKeyEvent)>,
}

impl WindowManager {
    pub fn new() -> Self {
        let clients = ClientState::with_virtualscreens(3);
        let xlib = XLib::new();

        Self {
            clients,
            move_window: None,
            resize_window: None,
            keybinds: Vec::new(),
            xlib,
        }
    }

    pub fn init(mut self) -> Self {
        self.xlib.add_global_keybind(KeyOrButton::button(
            1,
            Mod1Mask,
            ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
        ));
        self.xlib.add_global_keybind(KeyOrButton::button(
            2,
            Mod1Mask,
            ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
        ));
        self.xlib.add_global_keybind(KeyOrButton::button(
            3,
            Mod1Mask,
            ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
        ));

        self.add_keybind(KeyBinding::new(
            self.xlib.make_key("M", Mod1Mask),
            Self::handle_switch_stack,
        ));

        self.add_keybind(KeyBinding::new(
            self.xlib.make_key("Q", Mod1Mask),
            Self::kill_client,
        ));

        self.add_keybind(KeyBinding::new(
            self.xlib.make_key("Q", Mod1Mask | ShiftMask),
            |wm, _| wm.quit(),
        ));

        self.add_keybind(KeyBinding::new(
            self.xlib.make_key("T", Mod1Mask),
            |wm, _| wm.spawn("xterm", &[]),
        ));

        self.add_keybind(KeyBinding::new(
            self.xlib.make_key("Return", Mod1Mask | ShiftMask),
            |wm, _| wm.spawn("xterm", &[]),
        ));

        self.add_keybind(KeyBinding::new(
            self.xlib.make_key("Left", Mod1Mask),
            |wm, _| wm.rotate_virtual_screen(Direction::Left),
        ));

        self.add_keybind(KeyBinding::new(
            self.xlib.make_key("Right", Mod1Mask),
            |wm, _| wm.rotate_virtual_screen(Direction::Right),
        ));

        self.xlib.init();

        self
    }

    fn add_keybind(&mut self, keybind: KeyBinding) {
        self.xlib.add_global_keybind(keybind.key);
        self.keybinds.push(keybind);
    }

    pub fn run(mut self) -> ! {
        loop {
            let event = self.xlib.next_event();

            self.handle_toggle_floating(&event);
            self.handle_move_window(&event);
            self.handle_resize_client(&event);

            match event.get_type() {
                xlib::MapRequest => self.map_request(&event),
                xlib::UnmapNotify => self.unmap_notify(&event),
                xlib::ConfigureRequest => self.configure_request(&event),
                xlib::EnterNotify => self.enter_notify(&event),
                xlib::DestroyNotify => self.destroy_notify(&event),
                xlib::ButtonPress => self.button_notify(&event),
                xlib::KeyPress => self.handle_keybinds(event.as_ref()),
                _ => {}
            }
        }
    }

    fn quit(&self) -> ! {
        self.xlib.close_dpy();

        info!("Goodbye.");

        std::process::exit(0);
    }

    fn kill_client(&mut self, event: &XKeyEvent) {
        if let Some(client) = self.clients.get(&event.subwindow).into_option() {
            self.xlib.kill_client(client);
        }
    }

    // TODO: change this somehow cuz I'm not a big fan of this "hardcoded" keybind stuff
    fn handle_keybinds(&mut self, event: &XKeyEvent) {
        let clean_mask = self.xlib.get_clean_mask();
        for kb in self.keybinds.clone().into_iter() {
            if let KeyOrButton::Key(keycode, modmask) = kb.key {
                if keycode as u32 == event.keycode
                    && modmask & clean_mask == event.state & clean_mask
                {
                    (kb.closure)(self, event);
                }
            }
        }
    }

    fn handle_toggle_floating(&mut self, event: &XEvent) {
        if event.get_type() == xlib::ButtonPress {
            let event: &XButtonEvent = event.as_ref();
            let clean_mask = self.xlib.get_clean_mask();

            if event.button == 2 && event.state & clean_mask == Mod1Mask & clean_mask {
                if self.clients.contains(&event.subwindow) {
                    info!("toggleing floating for {:?}", event.subwindow);

                    self.clients.toggle_floating(&event.subwindow);

                    self.arrange_clients();
                }
            }
        }
    }

    fn handle_switch_stack(&mut self, event: &XKeyEvent) {
        self.clients.switch_stack_for_client(&event.subwindow);

        self.arrange_clients();
    }

    fn handle_move_window(&mut self, event: &XEvent) {
        let clean_mask = self.xlib.get_clean_mask();

        match event.get_type() {
            xlib::ButtonPress => {
                let event: &XButtonEvent = event.as_ref();

                if self.move_window.is_none()
                    && event.button == 1
                    && event.state & clean_mask == Mod1Mask & clean_mask
                    && self.clients.contains(&event.subwindow)
                {
                    // if client is tiled, set to floating
                    if self.clients.set_floating(&event.subwindow) {
                        self.arrange_clients();
                    }

                    self.move_window = Some(MoveWindow {
                        key: event.subwindow,
                        cached_cursor_position: (event.x, event.y),
                    });
                }
            }

            // reset on release
            xlib::ButtonRelease => {
                let event: &XButtonEvent = event.as_ref();

                if event.button == 1 && self.move_window.is_some() {
                    self.move_window = None;
                }
            }

            xlib::MotionNotify => {
                let event: &XMotionEvent = event.as_ref();

                if let Some(move_window) = &mut self.move_window {
                    let (x, y) = (
                        event.x - move_window.cached_cursor_position.0,
                        event.y - move_window.cached_cursor_position.1,
                    );

                    move_window.cached_cursor_position = (event.x, event.y);

                    if let Some(client) = self.clients.get_mut(&move_window.key).into_option() {
                        let position = &mut client.position;
                        position.0 += x;
                        position.1 += y;

                        self.xlib.move_client(client);
                    }
                }
            }
            _ => {}
        }
    }

    fn handle_resize_client(&mut self, event: &XEvent) {
        let clean_mask = self.xlib.get_clean_mask();

        match event.get_type() {
            xlib::ButtonPress => {
                let event: &XButtonEvent = event.as_ref();

                if self.resize_window.is_none()
                    && event.button == 3
                    && event.state & clean_mask == Mod1Mask & clean_mask
                    && self.clients.contains(&event.subwindow)
                {
                    // if client is tiled, set to floating
                    if self.clients.set_floating(&event.subwindow) {
                        self.arrange_clients();
                    }

                    let client = self.clients.get(&event.subwindow).unwrap();

                    let position = {
                        (
                            client.position.0 + client.size.0,
                            client.position.1 + client.size.1,
                        )
                    };

                    self.xlib.move_cursor(client.window, position);

                    self.resize_window = Some(MoveWindow {
                        key: event.subwindow,
                        cached_cursor_position: position,
                    });
                }
            }

            // reset on release
            xlib::ButtonRelease => {
                let event: &XButtonEvent = event.as_ref();

                if event.button == 3 && self.resize_window.is_some() {
                    self.resize_window = None;
                }
            }

            xlib::MotionNotify => {
                let event: &XMotionEvent = event.as_ref();

                if let Some(resize_window) = &mut self.resize_window {
                    let (x, y) = (
                        event.x - resize_window.cached_cursor_position.0,
                        event.y - resize_window.cached_cursor_position.1,
                    );

                    resize_window.cached_cursor_position = (event.x, event.y);

                    if let Some(client) = self.clients.get_mut(&resize_window.key).into_option() {
                        let size = &mut client.size;

                        size.0 = std::cmp::max(1, size.0 + x);
                        size.1 = std::cmp::max(1, size.1 + y);

                        self.xlib.resize_client(client);
                    }
                }
            }
            _ => {}
        }
    }

    fn rotate_virtual_screen(&mut self, dir: Direction) {
        info!("rotateing VS: {:?}", dir);

        match dir {
            Direction::Left => self.clients.rotate_left(),
            Direction::Right => self.clients.rotate_right(),
        }

        self.clients
            .iter_hidden()
            .for_each(|(_, c)| self.xlib.hide_client(c));

        self.arrange_clients();

        // focus first client in all visible clients
        let to_focus = self.clients.iter_visible().next().map(|(k, _)| k).cloned();

        if let Some(key) = to_focus {
            self.focus_client(&key);
        }
    }

    fn arrange_clients(&mut self) {
        let (width, height) = self.xlib.dimensions();
        self.clients.arrange_virtual_screen(width, height, Some(2));

        self.clients
            .iter_current_screen()
            .for_each(|(_, c)| self.xlib.move_resize_client(c));

        self.clients
            .iter_floating()
            .for_each(|(_, c)| self.xlib.raise_client(c));
    }

    fn focus_client<K>(&mut self, key: &K)
    where
        K: ClientKey,
    {
        let (new, old) = self.clients.focus_client(key);

        if let Some(old) = old.into_option() {
            self.xlib.unfocus_client(old);
        }

        if let Some(new) = new.into_option() {
            self.xlib.focus_client(new);
        }
    }

    #[allow(dead_code)]
    fn unfocus_client(&mut self) {
        if let Some(client) = self.clients.unfocus().into_option() {
            self.xlib.unfocus_client(client);
        }
    }

    fn new_client(&mut self, window: Window) {
        self.clients.insert(Client::new_default(window)).unwrap();
        self.xlib.map_window(window);

        self.focus_client(&window);

        self.arrange_clients();
    }

    fn map_request(&mut self, event: &XEvent) {
        let event: &XMapRequestEvent = event.as_ref();

        if !self.clients.contains(&event.window) {
            info!("MapRequest: new client: {:?}", event.window);

            self.new_client(event.window);
        }
    }

    fn unmap_notify(&mut self, event: &XEvent) {
        let event: &XUnmapEvent = event.as_ref();

        self.clients.remove(&event.window);

        self.arrange_clients();
    }

    fn destroy_notify(&mut self, event: &XEvent) {
        let event: &XDestroyWindowEvent = event.as_ref();

        self.clients.remove(&event.window);

        self.arrange_clients();
    }

    fn configure_request(&mut self, event: &XEvent) {
        let event: &XConfigureRequestEvent = event.as_ref();

        match self.clients.get(&event.window).into_option() {
            Some(client) => self.xlib.configure_client(client),
            None => self.xlib.configure_window(event),
        }
    }

    fn enter_notify(&mut self, event: &XEvent) {
        let event: &XCrossingEvent = event.as_ref();

        self.focus_client(&event.window);
    }

    fn button_notify(&mut self, event: &XEvent) {
        let event: &XButtonEvent = event.as_ref();

        self.focus_client(&event.subwindow);
        if let Some(client) = self.clients.get(&event.subwindow).into_option() {
            self.xlib.raise_client(client);
        }
    }

    pub fn spawn(&self, command: &str, args: &[&str]) {
        info!("spawn: {:?} {:?}", command, args.join(" "));
        match std::process::Command::new(command).args(args).spawn() {
            Ok(_) => {}
            Err(err) => {
                error!("Failed to spawn {:?}: {:?}", command, err);
            }
        }
    }
}

impl KeyBinding {
    fn new<F>(key: KeyOrButton, closure: F) -> Self
    where
        F: Fn(&mut WindowManager, &XKeyEvent) + 'static,
    {
        Self {
            key,
            closure: Rc::new(closure),
        }
    }
}
