use super::xdo::EnigoXdo;
use crate::{Key, KeyboardControllable, MouseButton, MouseControllable};
use std::io::Read;
use tfc::{traits::*, Context as TFC_Context, Key as TFC_Key};

/// The main struct for handling the event emitting
// #[derive(Default)]
pub struct Enigo {
    xdo: EnigoXdo,
    is_x11: bool,
    tfc: TFC_Context,
    uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
    uinput_mouse: Option<Box<dyn MouseControllable + Send>>,
}

impl Enigo {
    /// Get delay of xdo implementation.
    pub fn delay(&self) -> u64 {
        self.xdo.delay()
    }
    /// Set delay of xdo implemetation.
    pub fn set_delay(&mut self, delay: u64) {
        self.xdo.set_delay(delay)
    }
    /// Set uinput keyboard.
    pub fn set_uinput_keyboard(
        &mut self,
        uinput_keyboard: Option<Box<dyn KeyboardControllable + Send>>,
    ) {
        self.uinput_keyboard = uinput_keyboard
    }
    /// Set uinput mouse.
    pub fn set_uinput_mouse(&mut self, uinput_mouse: Option<Box<dyn MouseControllable + Send>>) {
        self.uinput_mouse = uinput_mouse
    }

    fn tfc_key_down_or_up(&mut self, key: Key, down: bool, up: bool) -> bool {
        if let Key::Layout(chr) = key {
            if down {
                if let Err(_) = self.tfc.unicode_char_down(chr) {
                    return false;
                }
            }
            if up {
                if let Err(_) = self.tfc.unicode_char_up(chr) {
                    return false;
                }
            }
            return true;
        }

        let key = match convert_to_tfc_key(key) {
            Some(key) => key,
            None => {
                return false;
            }
        };

        if down {
            if let Err(_) = self.tfc.key_down(key) {
                return false;
            }
        };
        if up {
            if let Err(_) = self.tfc.key_up(key) {
                return false;
            }
        };
        return true;
    }
}

impl Default for Enigo {
    fn default() -> Self {
        Self {
            is_x11: "x11" == hbb_common::platform::linux::get_display_server(),
            tfc: TFC_Context::new().expect("kbd context error"),
            uinput_keyboard: None,
            uinput_mouse: None,
            xdo: EnigoXdo::default(),
        }
    }
}

impl MouseControllable for Enigo {
    fn mouse_move_to(&mut self, x: i32, y: i32) {
        if self.is_x11 {
            self.xdo.mouse_move_to(x, y);
        } else {
            if let Some(mouse) = &mut self.uinput_mouse {
                mouse.mouse_move_to(x, y)
            }
        }
    }
    fn mouse_move_relative(&mut self, x: i32, y: i32) {
        if self.is_x11 {
            self.xdo.mouse_move_relative(x, y);
        } else {
            if let Some(mouse) = &mut self.uinput_mouse {
                mouse.mouse_move_relative(x, y)
            }
        }
    }
    fn mouse_down(&mut self, button: MouseButton) -> crate::ResultType {
        if self.is_x11 {
            self.xdo.mouse_down(button)
        } else {
            if let Some(mouse) = &mut self.uinput_mouse {
                mouse.mouse_down(button)
            } else {
                Ok(())
            }
        }
    }
    fn mouse_up(&mut self, button: MouseButton) {
        if self.is_x11 {
            self.xdo.mouse_up(button)
        } else {
            if let Some(mouse) = &mut self.uinput_mouse {
                mouse.mouse_up(button)
            }
        }
    }
    fn mouse_click(&mut self, button: MouseButton) {
        if self.is_x11 {
            self.xdo.mouse_click(button)
        } else {
            if let Some(mouse) = &mut self.uinput_mouse {
                mouse.mouse_click(button)
            }
        }
    }
    fn mouse_scroll_x(&mut self, length: i32) {
        if self.is_x11 {
            self.xdo.mouse_scroll_x(length)
        } else {
            if let Some(mouse) = &mut self.uinput_mouse {
                mouse.mouse_scroll_x(length)
            }
        }
    }
    fn mouse_scroll_y(&mut self, length: i32) {
        if self.is_x11 {
            self.xdo.mouse_scroll_y(length)
        } else {
            if let Some(mouse) = &mut self.uinput_mouse {
                mouse.mouse_scroll_y(length)
            }
        }
    }
}

fn get_led_state(key: Key) -> bool {
    let led_file = match key {
        Key::CapsLock => "/sys/class/leds/input1::capslock/brightness",
        Key::NumLock => "/sys/class/leds/input1::numlock/brightness",
        _ => {
            return false;
        }
    };

    let status = if let Ok(mut file) = std::fs::File::open(&led_file) {
        let mut content = String::new();
        file.read_to_string(&mut content).ok();
        let status = content.trim_end().to_string().parse::<i32>().unwrap_or(0);
        status
    } else {
        0
    };
    status == 1
}

impl KeyboardControllable for Enigo {
    fn get_key_state(&mut self, key: Key) -> bool {
        if self.is_x11 {
            self.xdo.get_key_state(key)
        } else {
            if let Some(keyboard) = &mut self.uinput_keyboard {
                keyboard.get_key_state(key)
            } else {
                get_led_state(key)
            }
        }
    }

    fn key_sequence(&mut self, sequence: &str) {
        if self.is_x11 {
            self.xdo.key_sequence(sequence)
        } else {
            if let Some(keyboard) = &mut self.uinput_keyboard {
                keyboard.key_sequence(sequence)
            }
        }
    }

    fn key_down(&mut self, key: Key) -> crate::ResultType {
        if self.is_x11 {
            let has_down = self.tfc_key_down_or_up(key, true, false);
            if !has_down {
                self.xdo.key_down(key)
            } else {
                Ok(())
            }
        } else {
            if let Some(keyboard) = &mut self.uinput_keyboard {
                keyboard.key_down(key)
            } else {
                Ok(())
            }
        }
    }
    fn key_up(&mut self, key: Key) {
        if self.is_x11 {
            let has_down = self.tfc_key_down_or_up(key, false, true);
            if !has_down {
                self.xdo.key_up(key)
            }
        } else {
            if let Some(keyboard) = &mut self.uinput_keyboard {
                keyboard.key_up(key)
            }
        }
    }
    fn key_click(&mut self, key: Key) {
        self.key_down(key).ok();
        self.key_up(key);
    }
}

fn convert_to_tfc_key(key: Key) -> Option<TFC_Key> {
    let key = match key {
        Key::Alt => TFC_Key::Alt,
        Key::Backspace => TFC_Key::DeleteOrBackspace,
        Key::CapsLock => TFC_Key::CapsLock,
        Key::Control => TFC_Key::Control,
        Key::Delete => TFC_Key::ForwardDelete,
        Key::DownArrow => TFC_Key::DownArrow,
        Key::End => TFC_Key::End,
        Key::Escape => TFC_Key::Escape,
        Key::F1 => TFC_Key::F1,
        Key::F10 => TFC_Key::F10,
        Key::F11 => TFC_Key::F11,
        Key::F12 => TFC_Key::F12,
        Key::F2 => TFC_Key::F2,
        Key::F3 => TFC_Key::F3,
        Key::F4 => TFC_Key::F4,
        Key::F5 => TFC_Key::F5,
        Key::F6 => TFC_Key::F6,
        Key::F7 => TFC_Key::F7,
        Key::F8 => TFC_Key::F8,
        Key::F9 => TFC_Key::F9,
        Key::Home => TFC_Key::Home,
        Key::LeftArrow => TFC_Key::LeftArrow,
        Key::PageDown => TFC_Key::PageDown,
        Key::PageUp => TFC_Key::PageUp,
        Key::Return => TFC_Key::ReturnOrEnter,
        Key::RightArrow => TFC_Key::RightArrow,
        Key::Shift => TFC_Key::Shift,
        Key::Space => TFC_Key::Space,
        Key::Tab => TFC_Key::Tab,
        Key::UpArrow => TFC_Key::UpArrow,
        Key::Numpad0 => TFC_Key::N0,
        Key::Numpad1 => TFC_Key::N1,
        Key::Numpad2 => TFC_Key::N2,
        Key::Numpad3 => TFC_Key::N3,
        Key::Numpad4 => TFC_Key::N4,
        Key::Numpad5 => TFC_Key::N5,
        Key::Numpad6 => TFC_Key::N6,
        Key::Numpad7 => TFC_Key::N7,
        Key::Numpad8 => TFC_Key::N8,
        Key::Numpad9 => TFC_Key::N9,
        Key::Decimal => TFC_Key::NumpadDecimal,
        Key::Clear => TFC_Key::NumpadClear,
        Key::Pause => TFC_Key::PlayPause,
        Key::Print => TFC_Key::Print,
        Key::Snapshot => TFC_Key::PrintScreen,
        Key::Insert => TFC_Key::Insert,
        Key::Scroll => TFC_Key::ScrollLock,
        Key::NumLock => TFC_Key::NumLock,
        Key::RWin => TFC_Key::Meta,
        Key::Apps => TFC_Key::Apps,
        Key::Multiply => TFC_Key::NumpadMultiply,
        Key::Add => TFC_Key::NumpadPlus,
        Key::Subtract => TFC_Key::NumpadMinus,
        Key::Divide => TFC_Key::NumpadDivide,
        Key::Equals => TFC_Key::NumpadEquals,
        Key::NumpadEnter => TFC_Key::NumpadEnter,
        Key::RightShift => TFC_Key::RightShift,
        Key::RightControl => TFC_Key::RightControl,
        Key::RightAlt => TFC_Key::RightAlt,
        Key::Command | Key::Super | Key::Windows | Key::Meta => TFC_Key::Meta,
        _ => {
            return None;
        }
    };
    Some(key)
}
