use crate::keyboard::*;
use crate::message::*;
use crate::system::{Component, ComponentLink};
use std::time::{Duration, Instant};
use std::sync::mpsc;

/// Provider accepting user input from the command line.
pub struct KeyboardComponent<T: KeyboardProvider> {
    provider: T,
    link: ComponentLink,
    locked: bool
}

impl<T: KeyboardProvider> KeyboardComponent<T> {
    pub fn new(provider: T, system_channel_sender: mpsc::Sender<Message>) -> Self {
        Self {
            provider,
            link: ComponentLink::new(system_channel_sender),
            locked: false
        }
    }
}

impl<T: KeyboardProvider> Component for KeyboardComponent<T> {
    fn get_name(&self) -> &str {
        self.provider.get_name()
    }

    fn listen_to(&self) -> Vec<MessageType> {
        vec!(MessageType::ProgramStarted,
             MessageType::ProgramStopped,
             MessageType::ProgramEnded)
    }

    fn get_sender(&self) -> mpsc::Sender<Message> {
        self.link.component_sender.clone()
    }

    fn run(&mut self) {
        const TIMEOUT: Duration = Duration::from_millis(1000/60);
    
        loop {
            let now = Instant::now();

            if !self.locked {
                if let Some(message) = self.provider.poll() {
                    self.link.system.send(message).unwrap();
                }
            }

            let left = TIMEOUT.saturating_sub(now.elapsed());

            match self.link.component_receiver.recv_timeout(left) {
                Ok(Message::Halt) => return,
                Ok(Message::ProgramStarted) => self.locked = true,
                Ok(Message::ProgramStopped) => self.locked = false,
                Ok(Message::ProgramEnded) => self.locked = false,
                Ok(_) => (),
                Err(mpsc::RecvTimeoutError::Timeout) => (),
                Err(mpsc::RecvTimeoutError::Disconnected) => return
            }
        }
    }
}

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

    #[test]
    pub fn test_keyboard_returns_configured_keys() {

        // Arrange
        
        let (system, system_recv) = mpsc::channel();
        let keys = vec!(Key::Start, Key::One, Key::Clear);
        let provider = TestKeyboard::new(keys);
        let mut component = KeyboardComponent::<TestKeyboard>::new(provider, system);
        let sender = component.link.component_sender.clone();

        // Act
        
        let handle = std::thread::spawn(move || {
            component.run();
        });

        // Assert
        
        assert_eq!(Message::KeyPressed(Key::Start), system_recv.recv().unwrap());
        assert_eq!(Message::KeyPressed(Key::One), system_recv.recv().unwrap());
        assert_eq!(Message::KeyPressed(Key::Clear), system_recv.recv().unwrap());

        // Stop provider

        sender.send(Message::Halt).unwrap();
        let _ = handle.join();
    }
}
