use crate::message::*;
use crate::system::{Component, ComponentLink};
use std::sync::mpsc;

const COMPONENT_NAME: &str = "HaltComponent";

pub struct HaltComponent {
    link: ComponentLink
}

impl HaltComponent {
    pub fn new(system_channel_sender: mpsc::Sender<Message>) -> Self {
        Self {
            link: ComponentLink::new(system_channel_sender)
        }
    }
}

impl Component for HaltComponent {
    fn get_name(&self) -> &str {
        COMPONENT_NAME
    }

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

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

    fn run(&mut self) {
        loop {
            match self.link.component_receiver.recv() {
                Ok(message) =>  match message {
                    Message::ProgramEnded => {
                        log::info!("{} received a program ended message and it is hanging on", COMPONENT_NAME);
                        let _ = self.link.system.send(Message::Halt);
                        return;
                    },
                    Message::Halt => {
                        log::info!("{} received an halt message and it is hanging on", COMPONENT_NAME);
                        return;
                    },
                    _ => ()
                },
                Err(mpsc::RecvError) => {
                    log::error!("{} received an error reading the input channel and it is hanging on", COMPONENT_NAME);
                    return;
                }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::{thread, sync::mpsc};
    
    pub struct ComponentBuilder { }
    
    impl ComponentBuilder {
        pub fn new() -> Self {
            ComponentBuilder {}
        }
        
        pub fn build(self) -> (HaltComponent, mpsc::Receiver<Message>) {
            let (tx, rx) = mpsc::channel();

            let component = HaltComponent {
                link: ComponentLink::new(tx)
            };

            (component, rx)
        }
    }
    
    #[test]
    pub fn when_the_halt_component_receives_an_halt_message_then_it_stops() {

        // Arrange

        let (mut component, _system) = ComponentBuilder::new().build();
        let sender = component.link.component_sender.clone();
        let handle = thread::spawn(move || component.run());

        // Act

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

        // Assert

        assert!(result.is_ok());
    }
    
    #[test]
    pub fn when_the_halt_component_receives_an_program_ended_message_then_it_stops() {

        // Arrange

        let (mut component, _system) = ComponentBuilder::new().build();
        let sender = component.link.component_sender.clone();
        let handle = thread::spawn(move || component.run());

        // Act

        sender.send(Message::ProgramEnded).unwrap();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
    }
}
