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

const COMPONENT_NAME: &str = "StatusComponent";

/// The status component maps raw input into logical status transitions to coordinate other
/// components. It mantains the global status.
/// A list of managed events follows.
///
/// Consumes event => Emits event
/// Message::KeyPressed(Key::Power) => Message::SystemStatusChanged(SystemStatus)
/// Message::KeyPressed(Key::GeneralReset) => Message::SystemStatusChanged(SystemStatus)
/// Message::KeyPressed(Key::PrintProgram) => Message::PrintProgramSwitch(bool)
/// Message::KeyPressed(Key::RecordProgram) => Message::RecordProgramSwitch(bool)
/// Message::KeyPressed(Key::DecimalUp) => Message::SetPrecision(u8)
/// Message::KeyPressed(Key::DecimalDown) => Message::SetPrecision(u8)
pub struct StatusComponent {
    status: SystemStatus,
    print_program: bool,
    record_program: bool,
    precision: u8,
    link: ComponentLink
}

impl StatusComponent {
    pub fn new(system_channel_sender: mpsc::Sender<Message>) -> Self {
        Self {
            status: SystemStatus::Off,
            print_program: false,
            record_program: false,
            precision: 2,
            link: ComponentLink::new(system_channel_sender)
        }
    }

    fn process_message(&mut self, message: Message) {
        log::debug!("Received message {:?}", message);

        match message {
            Message::KeyPressed(Key::Power) => {
                self.status = match self.status {
                    SystemStatus::Off => SystemStatus::On,
                    _ => SystemStatus::Off
                };
                self.link.system.send(Message::SystemStatusChanged(self.status)).unwrap();
            },
            Message::KeyPressed(Key::GeneralReset) => {
                if self.status == SystemStatus::On {
                    self.status = SystemStatus::Ready;
                    self.link.system.send(Message::SystemStatusChanged(self.status)).unwrap();
                }
            },
            Message::KeyPressed(Key::PrintProgram) => {
                self.print_program = !self.print_program;
                self.link.system.send(Message::PrintProgramSwitch(self.print_program)).unwrap();
            },
            Message::KeyPressed(Key::RecordProgram) => {
                self.record_program = !self.record_program;
                self.link.system.send(Message::RecordProgramSwitch(self.record_program)).unwrap();
            },
            Message::KeyPressed(Key::DecimalUp) => {
                self.precision = if self.precision == 15 { 0 } else { self.precision + 1};
                self.link.system.send(Message::SetPrecision(self.precision)).unwrap();
            },
            Message::KeyPressed(Key::DecimalDown) => {
                self.precision = if self.precision == 0 { 15 } else { self.precision - 1};
                self.link.system.send(Message::SetPrecision(self.precision)).unwrap();
            },
            _ => ()
        }
    }
}

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

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

    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::Halt => {
                        log::info!("{} received an halt message and it is hanging on", COMPONENT_NAME);
                        return;
                    },
                    _ => self.process_message(message)
                },
                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::{sync::mpsc, thread};

    pub struct ComponentBuilder {
        status: SystemStatus,
        print_program: bool,
        record_program: bool,
        precision: u8
    }

    impl ComponentBuilder {
        pub fn new() -> Self {
            Self {
                status: SystemStatus::Off,
                print_program: false,
                record_program: false,
                precision: 2
            }
        }

        pub fn with_status(mut self, value: SystemStatus) -> Self {
            self.status = value;
            self
        }

        pub fn with_print_program(mut self, value: bool) -> Self {
            self.print_program = value;
            self
        }

        pub fn with_record_program(mut self, value: bool) -> Self {
            self.record_program = value;
            self
        }

        pub fn with_precision(mut self, value: u8) -> Self {
            self.precision = value;
            self
        }

        pub fn build(self) -> (StatusComponent, mpsc::Receiver<Message>) {
            let (tx, rx) = mpsc::channel();

            let component = StatusComponent {
                status: self.status,
                print_program: self.print_program,
                record_program: self.record_program,
                precision: self.precision,
                link: ComponentLink::new(tx)
            };

            (component, rx)
        }
    }

    #[test]
    pub fn when_the_status_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_status_component_receives_a_power_key_pressed_message_while_off_then_change_system_status_to_on() {

        // Arrange

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

        // Act

        sender.send(Message::KeyPressed(Key::Power)).unwrap();
        sender.send(Message::Halt).unwrap();
        let message = system.recv();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
        assert_eq!(SystemStatus::On, result.unwrap());

        assert!(message.is_ok());
        assert_eq!(Message::SystemStatusChanged(SystemStatus::On), message.unwrap());
    }

    #[test]
    pub fn when_the_status_component_receives_a_power_key_pressed_message_while_on_then_change_system_status_to_off() {

        // Arrange

        let (mut component, system) = ComponentBuilder::new()
            .with_status(SystemStatus::On)
            .build();
        let sender = component.link.component_sender.clone();
        let handle = thread::spawn(move || {
            component.run();
            component.status
        });

        // Act

        sender.send(Message::KeyPressed(Key::Power)).unwrap();
        sender.send(Message::Halt).unwrap();
        let message = system.recv();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
        assert_eq!(SystemStatus::Off, result.unwrap());

        assert!(message.is_ok());
        assert_eq!(Message::SystemStatusChanged(SystemStatus::Off), message.unwrap());
    }

    #[test]
    pub fn when_the_status_component_receives_a_reset_key_pressed_message_while_on_then_change_system_status_to_ready() {

        // Arrange

        let (mut component, system) = ComponentBuilder::new()
            .with_status(SystemStatus::On)
            .build();
        let sender = component.link.component_sender.clone();
        let handle = thread::spawn(move || {
            component.run();
            component.status
        });

        // Act

        sender.send(Message::KeyPressed(Key::GeneralReset)).unwrap();
        sender.send(Message::Halt).unwrap();
        let message = system.recv();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
        assert_eq!(SystemStatus::Ready, result.unwrap());

        assert!(message.is_ok());
        assert_eq!(Message::SystemStatusChanged(SystemStatus::Ready), message.unwrap());
    }

    #[test]
    pub fn when_the_status_component_receives_a_print_program_pressed_message_while_the_switch_is_off_then_set_the_print_program_field_and_emit_a_print_program_true_event() {

        // Arrange

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

        // Act

        sender.send(Message::KeyPressed(Key::PrintProgram)).unwrap();
        sender.send(Message::Halt).unwrap();
        let message = system.recv();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
        assert_eq!(true, result.unwrap());

        assert!(message.is_ok());
        assert_eq!(Message::PrintProgramSwitch(true), message.unwrap());
    }

    #[test]
    pub fn when_the_status_component_receives_a_print_program_pressed_message_while_the_switch_is_on_then_set_the_print_program_field_and_emit_a_print_program_false_event() {

        // Arrange

        let (mut component, system) = ComponentBuilder::new()
            .with_print_program(true)
            .build();
        let sender = component.link.component_sender.clone();
        let handle = thread::spawn(move || {
            component.run();
            component.print_program
        });

        // Act

        sender.send(Message::KeyPressed(Key::PrintProgram)).unwrap();
        sender.send(Message::Halt).unwrap();
        let message = system.recv();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
        assert_eq!(false, result.unwrap());

        assert!(message.is_ok());
        assert_eq!(Message::PrintProgramSwitch(false), message.unwrap());
    }

    #[test]
    pub fn when_the_status_component_receives_a_record_program_pressed_message_while_the_switch_is_off_then_set_the_record_program_field_and_emit_a_record_program_true_event() {

        // Arrange

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

        // Act

        sender.send(Message::KeyPressed(Key::RecordProgram)).unwrap();
        sender.send(Message::Halt).unwrap();
        let message = system.recv();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
        assert_eq!(true, result.unwrap());

        assert!(message.is_ok());
        assert_eq!(Message::RecordProgramSwitch(true), message.unwrap());
    }

    #[test]
    pub fn when_the_status_component_receives_a_record_program_pressed_message_while_the_switch_is_on_then_reset_the_record_program_field_and_emit_a_record_program_false_event() {

        // Arrange

        let (mut component, system) = ComponentBuilder::new()
            .with_record_program(true)
            .build();
        let sender = component.link.component_sender.clone();
        let handle = thread::spawn(move || {
            component.run();
            component.record_program
        });

        // Act

        sender.send(Message::KeyPressed(Key::RecordProgram)).unwrap();
        sender.send(Message::Halt).unwrap();
        let message = system.recv();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
        assert_eq!(false, result.unwrap());

        assert!(message.is_ok());
        assert_eq!(Message::RecordProgramSwitch(false), message.unwrap());
    }

    #[test]
    pub fn when_the_status_component_receives_a_decimal_up_pressed_message_then_update_the_precision_field_and_emit_a_set_precision_event() {

        // Arrange

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

        // Act

        sender.send(Message::KeyPressed(Key::DecimalUp)).unwrap();
        sender.send(Message::Halt).unwrap();
        let message = system.recv();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
        assert_eq!(3, result.unwrap());

        assert!(message.is_ok());
        assert_eq!(Message::SetPrecision(3), message.unwrap());
    }

    #[test]
    pub fn when_the_status_component_receives_a_decimal_down_pressed_message_then_update_the_precision_field_and_emit_a_set_precision_event() {

        // Arrange

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

        // Act

        sender.send(Message::KeyPressed(Key::DecimalDown)).unwrap();
        sender.send(Message::Halt).unwrap();
        let message = system.recv();
        let result = handle.join();

        // Assert

        assert!(result.is_ok());
        assert_eq!(1, result.unwrap());

        assert!(message.is_ok());
        assert_eq!(Message::SetPrecision(1), message.unwrap());
    }
}
