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

pub struct PrinterComponent<T: PrinterProvider> {
    provider: T,
    ready: bool,
    link: ComponentLink
}

impl<T: PrinterProvider> PrinterComponent<T> {
    pub fn new(provider: T, system_channel_sender: mpsc::Sender<Message>) -> Self {
        Self {
            provider,
            ready: false,
            link: ComponentLink::new(system_channel_sender)
        }
    }

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

        match message {
            Message::SystemStatusChanged(SystemStatus::Ready) => self.ready = true,
            Message::SystemStatusChanged(_) => self.ready = false,
            Message::Debug(DebugEvent::RegisterUpdated(register, value)) => {
                if self.ready && p101_is::Register::M == register {
                    self.provider.print_value(value);
                }
            },
            Message::Debug(DebugEvent::RegisterSelected(register)) => if self.ready {
                self.provider.print_register(register);
            },
            Message::Print(payload) => if self.ready {
                self.provider.print(payload);
            },
            Message::NewLine => if self.ready {
                self.provider.newline();
            },
            _ => ()
        }
    }
}

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

    fn listen_to(&self) -> Vec<MessageType> {
        vec!(MessageType::Print, 
            MessageType::NewLine,
            MessageType::Debug,
            MessageType::SystemStatusChanged)
    }

    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) => if message == Message::Halt {
                    log::info!("{} received an halt message and it is hanging on", self.provider.get_name());
                    break;
                } else {
                    self.process_message(message);
                },
                Err(mpsc::RecvError) => log::error!("{} received an error reading the input channel and it is hanging on", self.provider.get_name())
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use p101_is::Register;
    use crate::printer::TestPrinter;
    use std::thread;
    use std::sync::{Arc, RwLock};
    use rust_decimal::Decimal;

    fn create_printer() -> (PrinterComponent<TestPrinter>, Arc<RwLock<Vec<String>>>, mpsc::Receiver<Message>) {
        let (system_sender, system_receiver) = mpsc::channel();
        let (provider, logs) = TestPrinter::new();
        let component = PrinterComponent::new(provider, system_sender);
        (component, logs, system_receiver)
    }

    #[test]
    pub fn when_the_printer_component_receives_an_halt_message_then_it_stops() {

        // Arrange
        let (mut printer, _logs, _system) = create_printer();
        let sender = printer.link.component_sender.clone();

        // Act

        let handle = thread::spawn(move || {
            printer.run();
        });
        sender.send(Message::Halt).unwrap();

        // Assert

        let _ = handle.join();
        assert!(true, "the component should halt");
    }

    #[test]
    pub fn when_multiple_print_messages_are_received_then_print_them_in_order() {

        // Arrange
        
        let (mut printer, logs, _system) = create_printer();
        let sender = printer.link.component_sender.clone();
        let handle = thread::spawn(move || {
            printer.run();
        });

        // Act

        sender.send(Message::SystemStatusChanged(SystemStatus::Ready)).unwrap();
        
        // Line 0
        sender.send(Message::Debug(DebugEvent::RegisterUpdated(Register::M, Decimal::new(1, 0).into())))
            .unwrap();

        sender.send(Message::Debug(DebugEvent::RegisterUpdated(Register::M, Decimal::new(12, 0).into())))
            .unwrap();

        sender.send(Message::Debug(DebugEvent::RegisterUpdated(Register::M, Decimal::new(123, 0).into())))
            .unwrap();

        sender.send(Message::Debug(DebugEvent::RegisterSelected(p101_is::Register::F.into())))
            .unwrap();

        let payload = PrintPayload::Value {
            value: Decimal::new(123, 0).into(),
            register: p101_is::Register::F.into(),
            operator: Operator::Add.into()
        };

        sender.send(Message::Print(payload)).unwrap();

        sender.send(Message::NewLine).unwrap();
        
        // Line 1
        sender.send(Message::Debug(DebugEvent::RegisterUpdated(Register::M, Decimal::new(222, 0).into())))
            .unwrap();

        sender.send(Message::NewLine).unwrap();

        let payload = PrintPayload::Value {
            value: Decimal::new(222, 0).into(),
            register: p101_is::Register::F.into(),
            operator: Operator::Add.into()
        };

        sender.send(Message::Print(payload)).unwrap();

        sender.send(Message::Debug(DebugEvent::RegisterSelected(p101_is::Register::F.into()))).unwrap();

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

        // Assert

        let a = logs.read().unwrap();
        assert_eq!(10, a.len(), "unexpected number of printed lines");
        assert_eq!("1".to_string(), a[0], "unexpected text at line 0");
        assert_eq!("12".to_string(), a[1], "unexpected text at line 1");
        assert_eq!("123".to_string(), a[2], "unexpected text at line 2");
        assert_eq!("F".to_string(), a[3], "unexpected text at line 3");
        assert_eq!("123 F +".to_string(), a[4], "unexpected text at line 4");
        assert_eq!("".to_string(), a[5], "unexpected text at line 5");
    }
}

