use gdk4::{Key, ModifierType};
use glib::{Properties, prelude::*, subclass::prelude::*};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::default::Default;
use std::str;

mod imp {
    use super::*;

    #[derive(Default, Debug, Serialize, Deserialize, Properties)]
    #[properties(wrapper_type = super::Keybindings)]
    pub struct Keybindings {
        #[property(get, set)]
        pub general: RefCell<KeybindingsGeneral>,

        #[property(get, set, name = "article-view")]
        pub article_view: RefCell<KeybindingsArticleView>,

        #[property(get, set, name = "article-list")]
        pub article_list: RefCell<KeybindingsArticleList>,

        #[property(get, set, name = "feed-list")]
        pub feed_list: RefCell<KeybindingsFeedList>,
    }

    #[glib::object_subclass]
    impl ObjectSubclass for Keybindings {
        const NAME: &'static str = "Keybindings";
        type Type = super::Keybindings;
    }

    #[glib::derived_properties]
    impl ObjectImpl for Keybindings {}
}

glib::wrapper! {
    pub struct Keybindings(ObjectSubclass<imp::Keybindings>);
}

impl Serialize for Keybindings {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.imp().serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for Keybindings {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let inner = imp::Keybindings::deserialize(deserializer)?;
        Ok(inner.into())
    }
}

impl From<imp::Keybindings> for Keybindings {
    fn from(inner: imp::Keybindings) -> Self {
        glib::Object::builder()
            .property("general", inner.general.borrow().clone())
            .property("article-view", inner.article_view.borrow().clone())
            .property("feed-list", inner.feed_list.borrow().clone())
            .property("article-list", inner.article_list.borrow().clone())
            .build()
    }
}

impl Default for Keybindings {
    fn default() -> Self {
        imp::Keybindings::default().into()
    }
}

impl Keybindings {
    pub fn parse_shortcut_string(keybinding: &str) -> Option<String> {
        if let Some((keyval, modifier)) = gtk4::accelerator_parse(keybinding) {
            Self::parse_shortcut(keyval, modifier)
        } else {
            None
        }
    }

    pub fn parse_shortcut(keyval: Key, modifier: ModifierType) -> Option<String> {
        let keyval = Self::parse_keyval(keyval);
        let modifier = Self::parse_modifiers(modifier);
        match keyval {
            None => None,
            Some(keyval) => match modifier {
                Some(mut modifier) => {
                    modifier.push_str(&keyval);
                    Some(modifier)
                }
                None => Some(keyval),
            },
        }
    }

    pub fn parse_keyval(keyval: Key) -> Option<String> {
        let manual_parsed = match keyval.to_lower() {
            Key::Shift_L
            | Key::Control_L
            | Key::Alt_L
            | Key::Meta_L
            | Key::Super_L
            | Key::Hyper_L
            | Key::Shift_R
            | Key::Control_R
            | Key::Alt_R
            | Key::Meta_R
            | Key::Super_R
            | Key::Hyper_R => Self::get_modifier_label(&keyval),
            Key::Left => Some("←".to_owned()),
            Key::Right => Some("→".to_owned()),
            Key::Down => Some("↓".to_owned()),
            Key::Up => Some("↑".to_owned()),
            Key::space => Some("␣".to_owned()),
            Key::Return => Some("⏎".to_owned()),
            Key::Delete => Some("Del".to_owned()),
            Key::Page_Up => Some("⇑".to_owned()),
            Key::Page_Down => Some("⇓".to_owned()),
            Key::BackSpace => Some("Backspace".to_owned()),
            Key::F1 => Some("F1".to_owned()),
            Key::F2 => Some("F2".to_owned()),
            Key::F3 => Some("F3".to_owned()),
            Key::F4 => Some("F4".to_owned()),
            Key::F5 => Some("F5".to_owned()),
            Key::F6 => Some("F6".to_owned()),
            Key::F7 => Some("F7".to_owned()),
            Key::F8 => Some("F8".to_owned()),
            Key::F9 => Some("F9".to_owned()),
            Key::F10 => Some("F10".to_owned()),
            Key::F11 => Some("F11".to_owned()),
            Key::F12 => Some("F12".to_owned()),
            _ => None,
        };

        if manual_parsed.is_some() {
            return manual_parsed;
        }

        match keyval.to_unicode() {
            Some(keyval) => {
                let mut buffer: [u8; 4] = [0; 4];
                Some(keyval.encode_utf8(&mut buffer).to_owned())
            }
            None => None,
        }
    }

    fn get_modifier_label(keyval: &Key) -> Option<String> {
        let mut mod_string = String::new();
        match keyval {
            &Key::Shift_L | &Key::Control_L | &Key::Alt_L | &Key::Meta_L | &Key::Super_L | &Key::Hyper_L => {
                mod_string.push('L')
            }
            &Key::Shift_R | &Key::Control_R | &Key::Alt_R | &Key::Meta_R | &Key::Super_R | &Key::Hyper_R => {
                mod_string.push('R')
            }
            _ => return None,
        }

        match keyval {
            &Key::Shift_L | &Key::Shift_R => mod_string.push_str("Shift"),
            &Key::Control_L | &Key::Control_R => mod_string.push_str("Ctrl"),
            &Key::Meta_L | &Key::Meta_R => mod_string.push_str("Meta"),
            &Key::Super_L | &Key::Super_R => mod_string.push_str("Super"),
            &Key::Hyper_L | &Key::Hyper_R => mod_string.push_str("Hyper"),
            _ => return None,
        }

        Some(mod_string)
    }

    fn parse_modifiers(modifier: ModifierType) -> Option<String> {
        let mut mod_string = String::new();

        if modifier.contains(ModifierType::SHIFT_MASK) {
            mod_string.push_str("Shift+");
        }
        if modifier.contains(ModifierType::CONTROL_MASK) {
            mod_string.push_str("Ctrl+");
        }
        if modifier.contains(ModifierType::ALT_MASK) {
            mod_string.push_str("Alt+");
        }
        if modifier.contains(ModifierType::SUPER_MASK) {
            mod_string.push_str("Super+");
        }
        if modifier.contains(ModifierType::HYPER_MASK) {
            mod_string.push_str("Hyper+");
        }
        if modifier.contains(ModifierType::META_MASK) {
            mod_string.push_str("Meta+");
        }

        if mod_string.is_empty() {
            return None;
        }

        Some(mod_string)
    }

    pub fn clean_modifier(mut modifier: ModifierType) -> ModifierType {
        modifier.remove(ModifierType::LOCK_MASK);
        modifier.remove(ModifierType::BUTTON1_MASK);
        modifier.remove(ModifierType::BUTTON2_MASK);
        modifier.remove(ModifierType::BUTTON3_MASK);
        modifier.remove(ModifierType::BUTTON4_MASK);
        modifier.remove(ModifierType::BUTTON5_MASK);

        modifier
    }
}

//--------------------------------------------
// General
//--------------------------------------------
mod imp_general {
    use super::*;

    #[derive(Debug, Serialize, Deserialize, Properties)]
    #[properties(wrapper_type = super::KeybindingsGeneral)]
    pub struct KeybindingsGeneral {
        #[property(get, set, nullable)]
        #[serde(default)]
        pub refresh: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub search: RefCell<Option<String>>,

        #[property(get, set, name = "all-articles", nullable)]
        #[serde(default)]
        pub all_articles: RefCell<Option<String>>,

        #[property(get, set, name = "only-unread", nullable)]
        #[serde(default)]
        pub only_unread: RefCell<Option<String>>,

        #[property(get, set, name = "only-starred", nullable)]
        #[serde(default)]
        pub only_starred: RefCell<Option<String>>,
    }

    impl Default for KeybindingsGeneral {
        fn default() -> Self {
            KeybindingsGeneral {
                refresh: RefCell::new(Some("F5".to_owned())),
                search: RefCell::new(Some("<ctl>F".to_owned())),
                all_articles: RefCell::new(Some("<ctl>1".to_owned())),
                only_unread: RefCell::new(Some("<ctl>2".to_owned())),
                only_starred: RefCell::new(Some("<ctl>3".to_owned())),
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for KeybindingsGeneral {
        const NAME: &'static str = "KeybindingsGeneral";
        type Type = super::KeybindingsGeneral;
    }

    #[glib::derived_properties]
    impl ObjectImpl for KeybindingsGeneral {}
}

glib::wrapper! {
    pub struct KeybindingsGeneral(ObjectSubclass<imp_general::KeybindingsGeneral>);
}

impl Serialize for KeybindingsGeneral {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.imp().serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for KeybindingsGeneral {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let inner = imp_general::KeybindingsGeneral::deserialize(deserializer)?;
        Ok(inner.into())
    }
}

impl From<imp_general::KeybindingsGeneral> for KeybindingsGeneral {
    fn from(inner: imp_general::KeybindingsGeneral) -> Self {
        glib::Object::builder()
            .property("refresh", inner.refresh.borrow().clone())
            .property("search", inner.search.borrow().clone())
            .property("all-articles", inner.all_articles.borrow().clone())
            .property("only-unread", inner.only_unread.borrow().clone())
            .property("only-starred", inner.only_starred.borrow().clone())
            .build()
    }
}

impl Default for KeybindingsGeneral {
    fn default() -> Self {
        imp_general::KeybindingsGeneral::default().into()
    }
}

//--------------------------------------------
// Article View
//--------------------------------------------
mod imp_article_view {
    use super::*;
    #[derive(Debug, Serialize, Deserialize, Properties)]
    #[properties(wrapper_type = super::KeybindingsArticleView)]
    pub struct KeybindingsArticleView {
        #[property(get, set, name = "scroll-up", nullable)]
        #[serde(default)]
        pub scroll_up: RefCell<Option<String>>,

        #[property(get, set, name = "scroll-down", nullable)]
        #[serde(default)]
        pub scroll_down: RefCell<Option<String>>,

        #[property(get, set, name = "scrap-content", nullable)]
        #[serde(default)]
        pub scrap_content: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub tag: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub fullscreen: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub close: RefCell<Option<String>>,
    }

    impl Default for KeybindingsArticleView {
        fn default() -> Self {
            KeybindingsArticleView {
                scroll_up: RefCell::new(Some("I".into())),
                scroll_down: RefCell::new(Some("U".into())),
                scrap_content: RefCell::new(Some("<Shift>C".into())),
                tag: RefCell::new(Some("<ctl>T".into())),
                fullscreen: RefCell::new(Some("<Shift><alt>X".into())),
                close: RefCell::new(None),
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for KeybindingsArticleView {
        const NAME: &'static str = "KeybindingsArticleView";
        type Type = super::KeybindingsArticleView;
    }

    #[glib::derived_properties]
    impl ObjectImpl for KeybindingsArticleView {}
}

glib::wrapper! {
    pub struct KeybindingsArticleView(ObjectSubclass<imp_article_view::KeybindingsArticleView>);
}

impl Serialize for KeybindingsArticleView {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.imp().serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for KeybindingsArticleView {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let inner = imp_article_view::KeybindingsArticleView::deserialize(deserializer)?;
        Ok(inner.into())
    }
}

impl From<imp_article_view::KeybindingsArticleView> for KeybindingsArticleView {
    fn from(inner: imp_article_view::KeybindingsArticleView) -> Self {
        glib::Object::builder()
            .property("scroll-up", inner.scroll_up.borrow().clone())
            .property("scroll-down", inner.scroll_down.borrow().clone())
            .property("scrap-content", inner.scrap_content.borrow().clone())
            .property("tag", inner.tag.borrow().clone())
            .property("fullscreen", inner.fullscreen.borrow().clone())
            .property("close", inner.close.borrow().clone())
            .build()
    }
}

impl Default for KeybindingsArticleView {
    fn default() -> Self {
        imp_article_view::KeybindingsArticleView::default().into()
    }
}

//--------------------------------------------
// Article List
//--------------------------------------------
mod imp_article_list {
    use super::*;

    #[derive(Debug, Serialize, Deserialize, Properties)]
    #[properties(wrapper_type = super::KeybindingsArticleList)]
    pub struct KeybindingsArticleList {
        #[property(get, set, nullable)]
        #[serde(default)]
        pub next: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub prev: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub read: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub mark: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub open: RefCell<Option<String>>,

        #[property(get, set, name = "copy-url", nullable)]
        #[serde(default)]
        pub copy_url: RefCell<Option<String>>,
    }

    impl Default for KeybindingsArticleList {
        fn default() -> Self {
            KeybindingsArticleList {
                next: RefCell::new(Some("J".to_owned())),
                prev: RefCell::new(Some("K".to_owned())),
                read: RefCell::new(Some("R".to_owned())),
                mark: RefCell::new(Some("M".to_owned())),
                open: RefCell::new(Some("O".to_owned())),
                copy_url: RefCell::new(Some("<ctl><Shift>C".to_owned())),
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for KeybindingsArticleList {
        const NAME: &'static str = "KeybindingsArticleList";
        type Type = super::KeybindingsArticleList;
    }

    #[glib::derived_properties]
    impl ObjectImpl for KeybindingsArticleList {}
}

glib::wrapper! {
    pub struct KeybindingsArticleList(ObjectSubclass<imp_article_list::KeybindingsArticleList>);
}

impl Serialize for KeybindingsArticleList {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.imp().serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for KeybindingsArticleList {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let inner = imp_article_list::KeybindingsArticleList::deserialize(deserializer)?;
        Ok(inner.into())
    }
}

impl From<imp_article_list::KeybindingsArticleList> for KeybindingsArticleList {
    fn from(inner: imp_article_list::KeybindingsArticleList) -> Self {
        glib::Object::builder()
            .property("next", inner.next.borrow().clone())
            .property("prev", inner.prev.borrow().clone())
            .property("read", inner.read.borrow().clone())
            .property("mark", inner.mark.borrow().clone())
            .property("open", inner.open.borrow().clone())
            .property("copy-url", inner.copy_url.borrow().clone())
            .build()
    }
}

impl Default for KeybindingsArticleList {
    fn default() -> Self {
        imp_article_list::KeybindingsArticleList::default().into()
    }
}

//--------------------------------------------
// Feed List
//--------------------------------------------
mod imp_feed_list {
    use super::*;
    #[derive(Debug, Serialize, Deserialize, Properties)]
    #[properties(wrapper_type = super::KeybindingsFeedList)]
    pub struct KeybindingsFeedList {
        #[property(get, set, nullable)]
        #[serde(default)]
        pub next: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub prev: RefCell<Option<String>>,

        #[property(get, set, nullable, name = "toggle-expanded")]
        #[serde(default)]
        pub toggle_expanded: RefCell<Option<String>>,

        #[property(get, set, nullable)]
        #[serde(default)]
        pub read: RefCell<Option<String>>,
    }

    impl Default for KeybindingsFeedList {
        fn default() -> Self {
            KeybindingsFeedList {
                next: RefCell::new(Some("<ctl>J".to_owned())),
                prev: RefCell::new(Some("<ctl>K".to_owned())),
                toggle_expanded: RefCell::new(Some("C".to_owned())),
                read: RefCell::new(Some("<Shift>A".to_owned())),
            }
        }
    }

    #[glib::object_subclass]
    impl ObjectSubclass for KeybindingsFeedList {
        const NAME: &'static str = "KeybindingsFeedList";
        type Type = super::KeybindingsFeedList;
    }

    #[glib::derived_properties]
    impl ObjectImpl for KeybindingsFeedList {}
}

glib::wrapper! {
    pub struct KeybindingsFeedList(ObjectSubclass<imp_feed_list::KeybindingsFeedList>);
}

impl Serialize for KeybindingsFeedList {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        self.imp().serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for KeybindingsFeedList {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let inner = imp_feed_list::KeybindingsFeedList::deserialize(deserializer)?;
        Ok(inner.into())
    }
}

impl From<imp_feed_list::KeybindingsFeedList> for KeybindingsFeedList {
    fn from(inner: imp_feed_list::KeybindingsFeedList) -> Self {
        glib::Object::builder()
            .property("next", inner.next.borrow().clone())
            .property("prev", inner.prev.borrow().clone())
            .property("toggle-expanded", inner.toggle_expanded.borrow().clone())
            .property("read", inner.read.borrow().clone())
            .build()
    }
}

impl Default for KeybindingsFeedList {
    fn default() -> Self {
        imp_feed_list::KeybindingsFeedList::default().into()
    }
}
