//! Keybinding map from configured key names to game actions.

use std::collections::HashMap;

use ratatui::crossterm::event::KeyCode;

use crate::sim::input::Action;

/// Parses a key name into a terminal key code.
pub fn parse_key(name: &str) -> Option<KeyCode> {
    match name.to_lowercase().as_str() {
        "left" => Some(KeyCode::Left),
        "right" => Some(KeyCode::Right),
        "up" => Some(KeyCode::Up),
        "down" => Some(KeyCode::Down),
        "space" => Some(KeyCode::Char(' ')),
        "esc" => Some(KeyCode::Esc),
        "enter" => Some(KeyCode::Enter),
        "tab" => Some(KeyCode::Tab),
        lower if lower.chars().count() == 1 => Some(KeyCode::Char(lower.chars().next().unwrap())),
        _ => None,
    }
}

#[derive(Clone, Debug)]
pub struct KeyMap {
    map: HashMap<KeyCode, Action>,
}

impl KeyMap {
    pub fn action(&self, code: KeyCode) -> Option<Action> {
        self.map.get(&code).copied()
    }

    pub fn from_bindings(bindings: &[(Action, Vec<String>)]) -> KeyMap {
        let mut map = HashMap::new();
        for (action, keys) in bindings {
            for key in keys {
                if let Some(code) = parse_key(key) {
                    map.insert(code, *action);
                } else {
                    eprintln!("rsstacker: unknown key name in config: {key:?} (ignored)");
                }
            }
        }
        KeyMap { map }
    }
}

impl Default for KeyMap {
    fn default() -> Self {
        KeyMap::from_bindings(&default_bindings())
    }
}

pub fn default_bindings() -> Vec<(Action, Vec<String>)> {
    vec![
        (Action::MoveLeft, vec!["Left".into()]),
        (Action::MoveRight, vec!["Right".into()]),
        (Action::SoftDrop, vec!["Down".into()]),
        (Action::HardDrop, vec!["Space".into()]),
        (Action::RotateCW, vec!["X".into(), "Up".into()]),
        (Action::RotateCCW, vec!["Z".into()]),
        (Action::Rotate180, vec!["A".into()]),
        (Action::Hold, vec!["C".into()]),
    ]
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::sim::input::Action;
    use ratatui::crossterm::event::KeyCode;

    #[test]
    fn parse_named_and_char_keys() {
        assert_eq!(parse_key("Left"), Some(KeyCode::Left));
        assert_eq!(parse_key("space"), Some(KeyCode::Char(' ')));
        assert_eq!(parse_key("X"), Some(KeyCode::Char('x')));
        assert_eq!(parse_key("x"), Some(KeyCode::Char('x')));
        assert_eq!(parse_key("Up"), Some(KeyCode::Up));
        assert_eq!(parse_key("nope!!"), None);
    }

    #[test]
    fn default_keymap_binds_expected_actions_including_alias() {
        let km = KeyMap::default();
        assert_eq!(km.action(KeyCode::Left), Some(Action::MoveLeft));
        assert_eq!(km.action(KeyCode::Char(' ')), Some(Action::HardDrop));
        assert_eq!(km.action(KeyCode::Char('z')), Some(Action::RotateCCW));
        assert_eq!(km.action(KeyCode::Char('x')), Some(Action::RotateCW));
        assert_eq!(km.action(KeyCode::Up), Some(Action::RotateCW));
        assert_eq!(km.action(KeyCode::Char('q')), None);
    }

    #[test]
    fn from_bindings_supports_multiple_keys_per_action() {
        let km = KeyMap::from_bindings(&[(Action::Hold, vec!["c".into(), "Tab".into()])]);
        assert_eq!(km.action(KeyCode::Char('c')), Some(Action::Hold));
        assert_eq!(km.action(KeyCode::Tab), Some(Action::Hold));
    }
}
