extern crate xcb;
extern crate regex;

use std::collections::HashMap;
use xcb::xproto;
use xcb::randr;
use std::error::Error;
use std::cmp::max;
use regex::Regex;
use std::process::Command;

type XmodmapPke = HashMap<u8, Vec<String>>;

fn xmodmap_pke() -> Result<XmodmapPke, Box<dyn Error>> {
    let output = Command::new("xmodmap").arg("-pke").output()?;
    let pattern = Regex::new(r"(\d+) = (.*)")?;
    let lines = String::from_utf8(output.stdout)?
        .lines()
        .filter_map(|line| pattern.captures(line))
        .map(|cap| (cap[1].parse().unwrap(), cap[2].split(" ").map(|s| s.to_string()).collect()))
        .collect::<HashMap<u8, Vec<String>>>();
    Ok(lines)
}

pub enum Actions {
    SwitchWindow, CloseWindow, ChangeLayout, ToggleGap
}

pub enum Meta {
    Mod1, Mod4
}

#[derive(Clone, Debug, PartialEq)]
enum Layout {
    BSPV,
    Monocle,
    BSPH
}

type Window = u32;

pub type Key = String;

type WorkspaceName = Key;

pub type CustomAction = Box<dyn Fn() -> ()>;

type Color = u32;

#[derive(Clone)]
pub struct Geometry(u32, u32, u32, u32);

pub struct WindowBorder {
    pub width: u32,
    pub focus_color: Color,
    pub normal_color: Color,
}

#[derive(Clone, Debug)]
struct Workspace {
    layout: Layout,
    windows: Vec<Window>,
    focus: usize,
}

#[derive(Clone, Debug)]
pub struct DisplayBorder {
    pub left: u32,
    pub right: u32,
    pub bottom: u32,
    pub top: u32,
    pub gap: u32,
}

type DisplayId = usize;

pub type OnChangeWorkspace = Option<Box<dyn Fn(WorkspaceName, DisplayId) -> ()>>;

pub struct EventsCallbacks {
    pub on_change_workspace: OnChangeWorkspace,
}

pub struct Conf {
    pub meta: Meta,
    pub border: WindowBorder,
    pub display_borders: Vec<DisplayBorder>,
    pub workspaces_names: Vec<Vec<WorkspaceName>>,
    pub custom_actions: HashMap<Key, CustomAction>,
    pub wm_actions: HashMap<Key, Actions>,
    pub ignore_classes: Vec<String>,
    pub float_classes: Vec<String>,
    pub events_callbacks: EventsCallbacks,
    pub with_gap: bool,
}

#[derive(Clone)]
struct MouseMoveStart {
    root_x: i16,
    root_y: i16,
    child: Window,
    detail: u8,
}

pub struct My {
    conf: Conf,
    current_workspace: WorkspaceName,
    float_windows: Vec<Window>,
    workspaces: HashMap<WorkspaceName, Workspace>,
    conn: xcb::Connection,
    mouse_move_start: Option<MouseMoveStart>,
    button_press_geometry: Option<Geometry>,
    xmodmap_pke: XmodmapPke,
    displays_geometries: Vec<Geometry>,
    randr_base: u8,
    previous_display: DisplayId,
}

fn keycode_to_key(xmodmap_pke: &XmodmapPke, keycode: u8) -> Option<Key> {
    match xmodmap_pke.get(&keycode) {
        Some(x) => {
            if x.len() > 0 {
                Some(x[0].to_string())
            }
            else {
                None
            }
        },
        None => None
    }
}

fn key_to_keycode(xmodmap_pke: &XmodmapPke, key: &Key) -> Option<u8> {
    for (keycode, symbols) in xmodmap_pke.into_iter() {
        if symbols.contains(&key) {
            return Some(*keycode);
        }
    }
    None
}


fn unmap_workspace_windows(conn: &xcb::Connection, windows: &mut Vec<Window>, focus: usize, move_window: bool, same_display: bool) -> Option<Window> {
    let mut window_to_move = None;
    for (i, window) in windows.iter().enumerate() {
        if move_window && i == focus {
            window_to_move = Some(*window);
        }
        else {
            if same_display {
                xcb::unmap_window(conn, *window);
            }
        }
    }
    window_to_move
}

fn change_workspace(conn: &xcb::Connection, workspaces: &mut HashMap<WorkspaceName, Workspace>, previous_workspace: WorkspaceName, next_workspace: WorkspaceName, move_window: bool, same_display: bool) -> Result<WorkspaceName, Box<dyn Error>> {
    let workspace = workspaces.get_mut(&previous_workspace).ok_or("workspace not found")?;
    let window_to_move = unmap_workspace_windows(conn, &mut workspace.windows, workspace.focus, move_window, same_display);
    match window_to_move {
        Some(w) => {
            workspace.windows.retain( |x| *x != w );
            if workspace.windows.len() > 0 {
                workspace.focus = workspace.windows.len() - 1;
            }
            else {
                workspace.focus = 0;
            }
        },
        None => {},
    };
    let workspace = workspaces.get_mut(&next_workspace).ok_or("workspace not found")?;
    for window in &workspace.windows {
        xcb::map_window(conn, *window);
    }
    window_to_move.map( 
        |w| { 
            workspace.windows.push(w);
            workspace.focus = workspace.windows.len() - 1;
        }
    );
    Ok(next_workspace)
}

    fn geometries_bsp(i: usize, window_count: usize, left: u32, top: u32, width: u32, height: u32, vertical: usize) -> Vec<Geometry> {
        if window_count == 0 {
            vec![]
        }
        else if window_count == 1 {
            vec![Geometry(left, top, width, height)]
        }
        else if i % 2 == vertical {
            let mut res = vec![Geometry(left, top, width, height / 2)];
            res.append(
                &mut geometries_bsp(i + 1, window_count - 1, left, top + height / 2, width, height / 2, vertical));
            res
        }
        else {
            let mut res = vec![Geometry(left, top, width / 2, height)];
            res.append(
                &mut geometries_bsp(i + 1, window_count - 1, left + width / 2, top, width / 2, height, vertical));
            res
        }
    }

    fn window_types_from_list(conn: &xcb::Connection, types_names: &Vec<String>) -> Vec<xcb::Atom> {
        types_names.into_iter().map(|x| {
            let name = format!("_NET_WM_WINDOW_TYPE_{}", x.to_uppercase());
            let res = xcb::intern_atom(&conn, true, name.as_str()).get_reply().map(|x| x.atom());
            res.ok()
        }
        ).flatten().collect()
    }

impl My {

    pub fn get_displays_geometries(&mut self) -> Result<Vec<Geometry>, Box<dyn Error>> {
        let conn = &self.conn;
        let setup = self.conn.get_setup();
        let screen = setup.roots().nth(0).unwrap();
        let window_dummy = conn.generate_id();
        xcb::create_window(&conn, 0, window_dummy, screen.root(), 0, 0, 1, 1, 0, 0, 0, &[]);
        let screen_res_cookie =
            randr::get_screen_resources(&conn, window_dummy);
        let screen_res_reply = screen_res_cookie.get_reply().unwrap();
        let crtcs = screen_res_reply.crtcs();

        let mut crtc_cookies = Vec::with_capacity(crtcs.len());
        for crtc in crtcs {
            crtc_cookies.push(randr::get_crtc_info(&conn, *crtc, 0));
        }

        let mut result = Vec::new();
        for (i, crtc_cookie) in crtc_cookies.into_iter().enumerate() {
            if let Ok(reply) = crtc_cookie.get_reply() {
                if reply.width() > 0 {
                if i != 0 { println!(""); }
                    println!("CRTC[{}] INFO:", i);
                    println!(" x-off\t: {}", reply.x());
                    println!(" y-off\t: {}", reply.y());
                    println!(" width\t: {}", reply.width());
                    println!(" height\t: {}", reply.height());
                    result.push(Geometry(reply.x() as u32, reply.y() as u32, reply.width() as u32, reply.height() as u32))
                }
            }
        }
        Ok(result)
    }


    fn get_display_border(&mut self, display: usize)-> DisplayBorder {
        let display_border_i = if display >= self.conf.display_borders.len() {
            self.conf.display_borders.len() - 1
        }
        else {
            display
        };
        self.conf.display_borders[display_border_i].clone()
    }


    fn resize_workspace_windows(&mut self, workspace: &Workspace, mut display: usize) {
        let mut non_float_windows = workspace.windows.clone();
        non_float_windows.retain(|w| !self.float_windows.contains(&w));
        let count = non_float_windows.len();
        if count == 0 || self.displays_geometries.len() <= 0 {
            return
        }
        if display >= self.displays_geometries.len() {
            display = self.displays_geometries.len() - 1;
        }
        let display_border = self.get_display_border(display);
        let display_geometry = self.displays_geometries.get(display).unwrap();
        let width = display_geometry.2 as u32 - display_border.right - display_border.left;
        let height = display_geometry.3 as u32 - display_border.top - display_border.bottom;
        let left = display_geometry.0 + display_border.left;
        let top = display_geometry.1 + display_border.top;
        let gap = if self.conf.with_gap {
            display_border.gap
        }
        else {
            0
        };
        let geos = match workspace.layout {
            Layout::BSPV => {
                geometries_bsp(0, count, left, top, width, height, 1)},
            Layout::BSPH => {
                geometries_bsp(0, count, left, top, width, height, 0)},
            Layout::Monocle => {
                geometries_bsp(0, 1, left, top, width, height, 1)},
        };
        match workspace.layout {
            Layout::BSPV | Layout::BSPH => {
                for (i, geo) in geos.iter().enumerate() {
                    match non_float_windows.get(i) {
                        Some(window) => {xcb::configure_window(&self.conn, *window, &[
                            (xcb::CONFIG_WINDOW_X as u16, geo.0 + gap),
                            (xcb::CONFIG_WINDOW_Y as u16, geo.1 + gap),
                            (xcb::CONFIG_WINDOW_WIDTH as u16, geo.2.saturating_sub(2 * self.conf.border.width + 2 * gap)),
                            (xcb::CONFIG_WINDOW_HEIGHT as u16, geo.3.saturating_sub(2 * self.conf.border.width + 2 * gap)),
                            (xcb::CONFIG_WINDOW_BORDER_WIDTH as u16, self.conf.border.width),
                        ]
                        );
                        },
                        None => {}
                    }
                }
            }
            Layout::Monocle => {
                match workspace.windows.get(workspace.focus) {
                    Some(window) => {xcb::configure_window(&self.conn, *window, &[
                        (xcb::CONFIG_WINDOW_X as u16, geos[0].0 + gap),
                        (xcb::CONFIG_WINDOW_Y as u16, geos[0].1 + gap),
                        (xcb::CONFIG_WINDOW_WIDTH as u16, geos[0].2.saturating_sub(2 * self.conf.border.width + 2 * gap)),
                        (xcb::CONFIG_WINDOW_HEIGHT as u16, geos[0].3.saturating_sub(2 * self.conf.border.width + 2 * gap)),
                        (xcb::CONFIG_WINDOW_BORDER_WIDTH as u16, self.conf.border.width),
                        (xcb::CONFIG_WINDOW_STACK_MODE as u16, xcb::STACK_MODE_ABOVE),
                    ]
                    );
                    },
                    None => {}
                }
            }
        }
        for (i, _) in workspace.windows.iter().enumerate() {
            match workspace.windows.get(i) {
                Some(window) => {
                    let _ = self.focus_unfocus(window, i == workspace.focus);
                },
                None =>{}
            }
        }
    }

    fn init(&mut self) {
        self.displays_geometries = self.get_displays_geometries().unwrap();
        let screen = self.conn.get_setup().roots().nth(0).unwrap();
        let mod_key = match self.conf.meta {
             Meta::Mod4 => xcb::MOD_MASK_4,
             Meta::Mod1 => xcb::MOD_MASK_1
        };
        self.randr_base = self.conn.get_extension_data(&mut randr::id()).unwrap().first_event();
        let _ = randr::select_input(&self.conn, screen.root(), randr::NOTIFY_MASK_CRTC_CHANGE as u16)
            .request_check();
        for mod_mask in vec![mod_key, mod_key | xcb::MOD_MASK_SHIFT] {
            for workspace_name_in_display in &self.conf.workspaces_names {
                for workspace_name in workspace_name_in_display {
                    key_to_keycode(&self.xmodmap_pke, workspace_name).map ( |keycode|
                        xcb::grab_key(&self.conn, false, screen.root(), mod_mask as u16, keycode, xcb::GRAB_MODE_ASYNC as u8, xcb::GRAB_MODE_ASYNC as u8)
                    );
                }
            }
            for custom_action_key in self.conf.custom_actions.keys() {
                key_to_keycode(&self.xmodmap_pke, custom_action_key).map ( |keycode|
                    xcb::grab_key(&self.conn, false, screen.root(), mod_mask as u16, keycode, xcb::GRAB_MODE_ASYNC as u8, xcb::GRAB_MODE_ASYNC as u8)
                );
            }
            for custom_action_key in self.conf.wm_actions.keys() {
                key_to_keycode(&self.xmodmap_pke, custom_action_key).map ( |keycode|
                    xcb::grab_key(&self.conn, false, screen.root(), mod_mask as u16, keycode, xcb::GRAB_MODE_ASYNC as u8, xcb::GRAB_MODE_ASYNC as u8)
                );
            }
        }
        for button in vec![1, 3] {
            xcb::grab_button(&self.conn, false, screen.root(), (xcb::EVENT_MASK_BUTTON_PRESS | xcb::EVENT_MASK_BUTTON_RELEASE | xcb::EVENT_MASK_POINTER_MOTION) as u16, xcb::GRAB_MODE_ASYNC as u8, xcb::GRAB_MODE_ASYNC as u8, xcb::NONE, xcb::NONE, button as u8, mod_key as u16);
        }
        xcb::change_window_attributes(&self.conn, screen.root(), &[(xcb::CW_EVENT_MASK, xcb::EVENT_MASK_SUBSTRUCTURE_NOTIFY as u32)]);
        self.conn.flush();
    }

    fn focus_unfocus(&mut self, window: &xcb::Window, do_focus: bool) -> Result<(), Box<dyn Error>> {
        let mut border_focus = false;
        if do_focus {
            xcb::set_input_focus(&self.conn, xcb::INPUT_FOCUS_PARENT as u8, *window, 0);
            let workspace = self.workspaces.get_mut(&self.current_workspace).ok_or("workspace not found")?;
            workspace.windows.iter().position(|x| x == window).map(|i| workspace.focus = i );
            border_focus = !((workspace.windows.len() == 1 || workspace.layout == Layout::Monocle) && self.displays_geometries.len() == 1);
            let net_active_window = xcb::intern_atom(&self.conn, false, "_NET_ACTIVE_WINDOW").get_reply()?.atom();
            let setup = self.conn.get_setup();
            let root = setup.roots().nth(0).ok_or("roots 0 not found")?.root();
            let data = vec![*window];
            xproto::change_property(&self.conn, xcb::PROP_MODE_REPLACE as u8, root, net_active_window, xproto::ATOM_WINDOW, 32, &data[..]);

        }
        xcb::change_window_attributes(&self.conn, *window, &[
            (xcb::CW_BORDER_PIXEL, 
             if border_focus {
                 self.conf.border.focus_color
             } else {
                 self.conf.border.normal_color
             }
            ),
        ]);
        Ok(())
    }


    fn run_wm_action(&mut self, key: &Key) -> Result<(), Box<dyn Error>> {
        let workspaces_names_by_display = self.conf.workspaces_names.clone();
        let action = self.conf.wm_actions.get(&key.to_string()).ok_or("action not found")?;
        let workspace = self.workspaces.get_mut(&self.current_workspace).ok_or("workspace not found")?;
        match action {
            Actions::CloseWindow => {
                let window = workspace.windows.get(workspace.focus).ok_or("window not found")?;
                let wm_delete_window = xcb::intern_atom(&self.conn, false, "WM_DELETE_WINDOW").get_reply()?.atom();
                let wm_protocols = xcb::intern_atom(&self.conn, false, "WM_PROTOCOLS").get_reply()?.atom();
                let data = xcb::ClientMessageData::from_data32([
                    wm_delete_window,
                    xcb::CURRENT_TIME,
                    0, 0, 0,
                ]);
                let ev = xcb::ClientMessageEvent::new(32, *window,
                    wm_protocols, data);
                xcb::send_event(&self.conn, false, *window, xcb::EVENT_MASK_NO_EVENT, &ev);
                self.conn.flush();
            },
            Actions::SwitchWindow => {
                if workspace.windows.len() > 0 {
                    workspace.focus = (workspace.focus + 1) % workspace.windows.len();
                }
            },
            Actions::ChangeLayout => {
                workspace.layout = match workspace.layout {
                    Layout::BSPV => Layout::Monocle,
                    Layout::Monocle => Layout::BSPH,
                    Layout::BSPH => Layout::BSPV,
                }
            },
            Actions::ToggleGap => {
               self.conf.with_gap =  !self.conf.with_gap;
            },
        };
        for (display, workspaces_names) in workspaces_names_by_display.iter().enumerate() {
            if workspaces_names.contains(&self.current_workspace) {
                let workspace = self.workspaces.get(&self.current_workspace).ok_or("workspace not found")?.clone();
                self.resize_workspace_windows(&workspace, display);
            }
        }
        Ok(())
    }



    fn get_str_property(&mut self, window: u32, name: &str) -> Option<String> {
        let _net_wm_window_type = xcb::intern_atom(&self.conn, false, name).get_reply().unwrap().atom();
        let cookie = xcb::get_property(&self.conn, false, window, _net_wm_window_type, xcb::ATOM_ANY, 0, 1024);
        if let Ok(reply) = cookie.get_reply() {
            Some(std::str::from_utf8(reply.value()).unwrap().to_string())
        } else {
            None
        }
    }

    fn get_atom_property(&mut self, id: u32, name: &str) -> Result<u32, Box<dyn Error>> {
        let window: xproto::Window = id;
        let ident = xcb::intern_atom(&self.conn, true, name).get_reply()?.atom();
        let reply = xproto::get_property(&self.conn, false, window, ident, xproto::ATOM_ATOM, 0, 1024).get_reply()?;
        if reply.value_len() <= 0 {
            Ok(42)
        }
        else {
            Ok(reply.value()[0])
        }
    }

    fn setup_new_window(&mut self, window: u32) -> Result<(), Box<dyn Error>> {
        for (_, workspace) in &self.workspaces {
            for workspace_window in &workspace.windows {
                if &window == workspace_window {
                    /* the window already exist in a workspace */
                    return Ok(())
                }
            }
        }
        let wm_class = self.get_str_property(window, "WM_CLASS").ok_or("failed getting wm class")?;
        let window_type = self.get_atom_property(window, "_NET_WM_WINDOW_TYPE")?;
        let window_types = window_types_from_list(&self.conn, &vec![
            "menu".to_string(),
            "popup_menu".to_string(),
            "dropdown_menu".to_string(),
            "tooltip".to_string(),

            "utility".to_string(),
            "notification".to_string(),
            "toolbar".to_string(),
            "splash".to_string(),
            "dialog".to_string(),
            "dock".to_string(),
        ]);
        let wm_class : Vec<&str> = wm_class.split('\0').collect();
        println!("UGUU: {} {}", xcb::get_atom_name(&self.conn, window_type).get_reply()?.name(), wm_class.join("-"));
        if window_types.contains(&window_type) {
            return Ok(())
        } else {
            if "_KDE_NET_WM_WINDOW_TYPE_OVERRIDE" == xcb::get_atom_name(&self.conn, window_type).get_reply()?.name() || xcb::get_atom_name(&self.conn, window_type).get_reply()?.name() == "WM_ZOOM_HINTS" {
                return Ok(())
            }
        }
        if wm_class.len() != 0 {
            for i in 0..wm_class.len() {
                if wm_class[i] == "xscreensaver" || self.conf.ignore_classes.contains(&wm_class[i].to_string()) { 
                    return Ok(())
                }
            }
        }
        match self.workspaces.get_mut(&self.current_workspace) {
            Some(workspace) => {
                if !workspace.windows.contains(&window) {
                    if wm_class.len() != 0 && self.conf.float_classes.contains(&wm_class[0].to_string()) && !self.float_windows.contains(&window) { 
                        self.float_windows.push(window);
                    }
                    workspace.windows.push(window);
                    workspace.focus = workspace.windows.len() - 1;
                    let workspace2 = workspace.clone();
                    let workspaces_names_by_display = self.conf.workspaces_names.clone();
                    for (display, workspaces_names) in workspaces_names_by_display.iter().enumerate() {
                        if workspaces_names.contains(&self.current_workspace) {
                            self.resize_workspace_windows(&workspace2, display);
                        }
                    }
                }
            },
            None => {
            },
        }
        xcb::change_window_attributes(&self.conn, window, &[(xcb::CW_EVENT_MASK, xcb::EVENT_MASK_ENTER_WINDOW | xcb::EVENT_MASK_LEAVE_WINDOW)]);
        Ok(())
    }

    fn resize_window(&mut self, event: &xcb::MotionNotifyEvent) -> Result<(), Box<dyn Error>> {
        let mouse_move_start = self.mouse_move_start.clone().ok_or("no mouse move start")?;
        let attr = self.button_press_geometry.clone().ok_or("no button press geometry")?;
        let xdiff = event.root_x() - mouse_move_start.root_x;
        let ydiff = event.root_y() - mouse_move_start.root_y;
        let x = attr.0 as i32 + if mouse_move_start.detail == 1 { xdiff as i32 } else { 0 };
        let y = attr.1 as i32 + if mouse_move_start.detail == 1 { ydiff as i32 } else { 0 };
        let width = max(1, attr.2 as i32 + if mouse_move_start.detail == 3 { xdiff as i32 } else { 0 });
        let height = max(1, attr.3 as i32 + if mouse_move_start.detail == 3 { ydiff as i32 } else { 0 });
        xcb::configure_window(&self.conn, mouse_move_start.child, &[
                            (xcb::CONFIG_WINDOW_X as u16, x as u32),
                            (xcb::CONFIG_WINDOW_Y as u16, y as u32),
                            (xcb::CONFIG_WINDOW_WIDTH as u16, width as u32),
                            (xcb::CONFIG_WINDOW_HEIGHT as u16, height as u32),
                        ]);
        Ok(())
    }

    fn destroy_window(&mut self, window: u32) {
        self.float_windows.retain(|&x| x != window);
        let mut workspace2 : Option<Workspace> = None;
        for (_, workspace) in &mut self.workspaces {
            if workspace.windows.contains(&window) {
                workspace.windows.retain(|&x| x != window);
                if workspace.focus > 0 {
                    workspace.focus = workspace.focus - 1;
                }
                workspace2 = Some(workspace.clone());
            }
        }
        let workspaces_names_by_display = self.conf.workspaces_names.clone();
        let mut dis = 0;
        for (display, workspaces_names) in workspaces_names_by_display.iter().enumerate() {
            if workspaces_names.contains(&self.current_workspace) {
                dis = display;
            }
        }

        workspace2.map(|workspace| { 
            workspace.windows.get(workspace.focus).map(|previous_window| self.focus_unfocus(previous_window, true));
            self.resize_workspace_windows(&workspace, dis);
        });
    }

    pub fn run(&mut self) {
        loop {
            match self.conn.wait_for_event() {
                Some(event) => {
                    let r = event.response_type();
                    if r == xcb::MAP_NOTIFY as u8 {
                        let map_notify : &xcb::MapNotifyEvent = unsafe {
                            xcb::cast_event(&event)
                        };
                        let _ = self.setup_new_window(map_notify.window());
                    }
                    if r == self.randr_base + randr::NOTIFY {
                        self.displays_geometries = self.get_displays_geometries().unwrap();
                    }
                    if r == xcb::DESTROY_NOTIFY as u8 {
                        let map_notify : &xcb::DestroyNotifyEvent = unsafe {
                            xcb::cast_event(&event)
                        };
                        self.destroy_window(map_notify.window());
                    }
                    else if r == xcb::BUTTON_PRESS as u8 {
                        let event : &xcb::ButtonPressEvent = unsafe {
                            xcb::cast_event(&event)
                        };
                        match xcb::get_geometry(&self.conn, event.child()).get_reply() {
                            Ok(geometry) => {self.button_press_geometry = Some(
                                Geometry(geometry.x() as u32, geometry.y() as u32, geometry.width() as u32, geometry.height() as u32)
                                );},
                            Err(_) => {},
                        }
                        self.mouse_move_start = Some(MouseMoveStart{
                            root_x: event.root_x(),
                            root_y: event.root_y(),
                            child: event.child(),
                            detail: event.detail(),
                        });
                    }
                    else if r == xcb::MOTION_NOTIFY as u8 {
                        let event : &xcb::MotionNotifyEvent = unsafe {
                            xcb::cast_event(&event)
                        };
                        let _ = self.resize_window(event);
                    }
                    else if r == xcb::LEAVE_NOTIFY as u8 {
                        let event : &xcb::LeaveNotifyEvent = unsafe {
                            xcb::cast_event(&event)
                        };
                        let _= self.focus_unfocus(&event.event(), false);
                    }
                    else if r == xcb::ENTER_NOTIFY as u8 {
                        let event : &xcb::EnterNotifyEvent = unsafe {
                            xcb::cast_event(&event)
                        };
                        let _ = self.focus_unfocus(&event.event(), true);
                    }
                    else if r == xcb::BUTTON_RELEASE as u8 {
                        self.mouse_move_start = None;
                    }
                    else if r == xcb::KEY_PRESS as u8 {
                        let key_press : &xcb::KeyPressEvent = unsafe {
                            xcb::cast_event(&event)
                        };
                        let keycode = key_press.detail();
                        match &keycode_to_key(&self.xmodmap_pke, keycode) {
                            Some(key) => {
                                let workspaces_names_by_display = self.conf.workspaces_names.clone();
                                for (display, workspaces_names) in workspaces_names_by_display.iter().enumerate() {
                                    if workspaces_names.contains(key) {
                                        match change_workspace(&self.conn, &mut self.workspaces, self.current_workspace.to_string(), key.to_string(), (key_press.state() as u32 ) & xcb::MOD_MASK_SHIFT != 0, 
                                            workspaces_names.contains(&self.current_workspace) || display >= self.displays_geometries.len() || self.previous_display >= self.displays_geometries.len()) {
                                            Ok(workspace) => { 
                                                self.previous_display = display;
                                                self.current_workspace = workspace;
                                                let workspace = self.workspaces.get(&self.current_workspace).ok_or("workspace not found").unwrap().clone();
                                                self.resize_workspace_windows(&workspace, display);
                                                let actual_display = if display >= self.displays_geometries.len() {
                                                    self.displays_geometries.len() - 1
                                                }
                                                else {
                                                    display
                                                };
                                                self.conf.events_callbacks.on_change_workspace.as_ref().map ( |callback|
                                                    callback(key.to_string(), actual_display)
                                                );
                                            },
                                            Err(_) => {},
                                        };
                                    }
                                }
                                if self.conf.wm_actions.contains_key(&key.to_string()) {
                                    let _ = self.run_wm_action(&key);
                                }
                                else if self.conf.custom_actions.contains_key(&key.to_string()) {
                                    match self.conf.custom_actions.get(&key.to_string()) {
                                        Some(action) => {
                                            action()
                                        },
                                        None => {},
                                    }
                                }
                            },
                            None => {
                            },

                        }
                    }
                },
                None => {}
            }
            self.conn.flush();
        }
    }
}

pub fn my(conf: Conf) -> My {
    let (conn, _) = xcb::Connection::connect(None).unwrap();
    let conf_workspaces_flatten : Vec<Key> = conf.workspaces_names.clone().into_iter().flatten().collect();
    let workspaces = conf_workspaces_flatten.into_iter().map( |x|
            (x, Workspace {
                layout: Layout::BSPV,
                windows: vec![],
                focus: 0,
        })).into_iter().collect();
    let xmodmap_pke = xmodmap_pke().unwrap();
    let current_workspace = conf.workspaces_names.get(0).unwrap()[0].to_string();
    let mut wm = My {
        conf: conf,
        current_workspace: current_workspace,
        float_windows: vec![],
        workspaces: workspaces,
        conn: conn,
        button_press_geometry: None,
        mouse_move_start: None,
        xmodmap_pke: xmodmap_pke,
        displays_geometries: Vec::new(),
        randr_base: 0,
        previous_display: 0,
    };
    wm.init();
    wm
}
