use crate::keyboard::*;
use crate::message::*;
use std::sync::mpsc::*;
use std::thread;
use std::time::*;

/// Provider accepting user input from the command line.
pub struct CliKeyboard {
    buffer: String,
    mapping: Vec<KeyMap>,
    receiver: Receiver<String>,
}

impl CliKeyboard {
    /// Creates a new CLI input provider
    pub fn new() -> Self {
        Self::with_mapping(Mapping::standard())
    }

    pub fn with_mapping(mapping: Vec<KeyMap>) -> Self {
        let (sender, receiver) = channel();

        let _handle = thread::spawn(move || {
            let stdin = std::io::stdin();

            loop {
                let mut buffer = String::new();

                stdin.read_line(&mut buffer).unwrap();

                // The thread should stay alive and continue
                // reading the stdin until the channel is open.
                if sender.send(buffer).is_err() {
                    return;
                }
            }
        });

        Self {
            buffer: String::new(),
            mapping,
            receiver,
        }
    }

    /// Maps character to Key
    fn map(&self, chr: char) -> Option<Key> {
        self.mapping
            .iter()
            .find(|x| x.character == chr)
            .map(|x| x.key)
    }
}

impl Default for CliKeyboard {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
impl CliKeyboard {
    fn with_buffer(mut self, buffer: String) -> Self {
        self.buffer = buffer;
        self
    }
}

impl KeyboardProvider for CliKeyboard {
    fn get_name(&self) -> &str {
        "CliKeyboard"
    }

    fn poll(&mut self) -> Option<Message> {
        // Updates the buffer when empty
        if self.buffer.is_empty() {
            if let Ok(s) = self.receiver.recv_timeout(Duration::from_millis(16)) {
                self.buffer = s;
            }
        }

        // Map input to key, if any

        if !self.buffer.is_empty() {
            let chr = self.buffer.remove(0);

            if let Some(key) = self.map(chr) {
                return Some(Message::KeyPressed(key));
            }
        }

        None
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn consume_all_characters_in_the_buffer() {
        // Arrange

        let buffer = "2a!".to_string();
        let mut keyboard = CliKeyboard::new().with_buffer(buffer);

        // Act and assert

        assert_eq!(Some(Message::KeyPressed(Key::Two)), keyboard.poll());
        assert_eq!(Some(Message::KeyPressed(Key::CopyToA)), keyboard.poll());
        assert_eq!(Some(Message::KeyPressed(Key::Power)), keyboard.poll());

        // Consume past the last character in the buffer
        assert_eq!(None, keyboard.poll());
        assert_eq!(None, keyboard.poll());
    }

    // Setup a standard input for the thread and test
    // if the CliKeyboard properly process it.
}
