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

pub struct StorageComponent<T: storage::StorageProvider> {
    card: T,
    link: system::ComponentLink,
    ready: bool,
    record_switch: bool,
    print_switch: bool
}

impl<T: storage::StorageProvider> StorageComponent<T> {
    pub fn new(card: T, system: mpsc::Sender<message::Message>) -> Self {
        Self {
            card,
            link: system::ComponentLink::new(system),
            ready: false,
            record_switch: false,
            print_switch: false
        }
    }

    fn send(&self, msg: message::Message) {
        self.link.system.send(msg).unwrap();
    }

    fn write_card(&mut self, new_text: &str) {
        self.card.write(new_text).unwrap();
        self.card.eject();
    }

    fn card_inserted(&mut self, card: message::Card) {
        if self.ready && !self.print_switch && !self.record_switch {
            self.card.using(card).unwrap();
            let content = self.card.read().unwrap();
            self.send(message::Message::TextRead(content));
            self.card.eject();
        } else if self.ready && !self.print_switch && self.record_switch {
            self.card.using(card).unwrap();
        }
    }
}

impl<T: storage::StorageProvider> Component for StorageComponent<T> {
    fn get_name(&self) -> &str {
        "StorageComponent"
    }

    fn listen_to(&self) -> Vec<message::MessageType> {
        vec!(message::MessageType::CardInserted,
             message::MessageType::StoreTextOnCard,
             message::MessageType::SystemStatusChanged,
             message::MessageType::PrintProgramSwitch,
             message::MessageType::RecordProgramSwitch
        )
    }

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

    fn run(&mut self) {
        loop {
            match self.link.component_receiver.recv() {
                Ok(message::Message::CardInserted(card)) => self.card_inserted(card),
                Ok(message::Message::StoreTextOnCard(ref text)) => self.write_card(text),
                Ok(message::Message::Halt) => return,
                Ok(message::Message::SystemStatusChanged(status)) => {
                    self.ready = status == system::SystemStatus::Ready
                },
                Ok(message::Message::PrintProgramSwitch(status)) => self.print_switch = status,
                Ok(message::Message::RecordProgramSwitch(status)) => self.record_switch = status,
                _ => (),
            }
        }
    }
}


#[cfg(test)]
mod tests {
    use crate::{message, storage, storage::StorageProvider, system, system::Component};
    use std::sync::mpsc;
    use std::thread;
    use std::time::Duration;

    fn create_storage_for_test(ready: bool, record: bool, print: bool) -> (storage::StorageComponent<storage::MemoryCard>, mpsc::Receiver<message::Message>) {
        let (system_sender, system_receiver) = mpsc::channel();

        let storage = storage::StorageComponent {
            card: storage::MemoryCard::new_with_content(""),
            link: system::ComponentLink::new(system_sender),
            ready: ready,
            record_switch: record,
            print_switch: print
        };

        (storage, system_receiver)
    }

    fn new_memory_card_message(content: &str) -> message::Message {
        message::Message::CardInserted(message::Card::new_memory_card(content))
    }

    #[test]
    pub fn when_the_storage_component_receives_an_halt_message_then_it_stops() {
        // Arrange
        let (mut storage, _system_receiver) = create_storage_for_test(false, false, false);
        let sender = storage.link.component_sender.clone();

        // Act
        let handle = thread::spawn(move || storage.run());
        sender.send(message::Message::Halt).unwrap();

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

    #[test]
    pub fn when_the_storage_component_is_not_ready_then_ignore_any_messages() {

        // Arrange

        const TEXT: &str = "this is a test";
        let (mut storage, system_receiver) = create_storage_for_test(false, false, false);
        let sender = storage.link.component_sender.clone();

        let handle = thread::spawn(move || {
            storage.run();
        });

        // Act

        sender.send(new_memory_card_message(TEXT)).unwrap();

        // Assert

        let result = system_receiver.recv_timeout(Duration::from_secs(1));
        assert_eq!(Err(mpsc::RecvTimeoutError::Timeout), result, "The component should not send any message");

        // Clean up

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

    #[test]
    pub fn when_both_record_and_print_switches_are_off_and_a_card_is_inserted_then_read_its_contents() {

        // Arrange

        const TEXT: &str = "this is a test";
        let (mut storage, system_receiver) = create_storage_for_test(true, false, false);
        let sender = storage.link.component_sender.clone();

        // Act

        let handle = thread::spawn(move || {
            storage.run();
        });

        sender.send(new_memory_card_message(TEXT)).unwrap();

        // Assert

        let result = system_receiver.recv_timeout(Duration::from_secs(1));
        assert_eq!(Ok(message::Message::TextRead(TEXT.to_string())), result, "The component did not send the text read message");

        // Clean up

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

    /* TODO
    #[test]
    pub fn when_record_switch_is_off_and_print_switch_is_on_and_a_card_is_inserted_then_print_its_contents() {
        todo!();
    }
    */

    /// When a card is inserted and the record switch is on, memory content is wrote to the card.
    /// In this library implementation, saving memory contents starts from the ALU.
    /// - 1a: The storage component processes the CardInserted message and store card info
    /// - 1b: The ALU component receices the CardInserted message, reads memory and send
    ///       a StoreText message
    /// - 2:  The Storage component processes the StoreText message and writes memory on the card.
    /// This test check point 1a only.
    #[test]
    pub fn when_record_switch_is_on_and_print_switch_is_off_and_a_card_is_inserted_then_save_card_for_later_use() {

        // Arrange

        const TEXT: &str = "this is a test";
        let (mut storage, _system_receiver) = create_storage_for_test(true, true, false);
        let sender = storage.link.component_sender.clone();

        // Act

        let handle = thread::spawn(move || {
            storage.run();
            storage.card
        });

        sender.send(new_memory_card_message(TEXT)).unwrap();
        sender.send(message::Message::Halt).unwrap();

        let result = handle.join();

        // Assert

        assert!(result.is_ok(), "The component should have terminated clearly");
        let card = result.unwrap();
        assert_eq!(Ok(TEXT.to_string()), card.read(), "The component did not send the text read message");
    }

    #[test]
    /// When a card is inserted and the record switch is on, memory content is wrote to the card.
    /// In this library implementation, saving memory contents starts from the ALU.
    /// - 1a: The storage component processes the CardInserted message and store card info
    /// - 1b: The ALU component receices the CardInserted message, reads memory and send
    ///       a StoreText message
    /// - 2:  The Storage component processes the StoreText message and writes memory on the card.
    /// This test emulates points 1a and 1b and checks point 2.
    pub fn when_a_store_text_message_is_received_and_a_card_is_in_the_reader_then_update_card_contents() {

        // Arrange

        const TEXT: &str = "this is a test";
        let (mut storage, _system_receiver) = create_storage_for_test(true, true, false);
        let sender = storage.link.component_sender.clone();

        // Act

        let handle = thread::spawn(move || {
            storage.run();
            storage.card
        });

        sender.send(new_memory_card_message("")).unwrap();
        sender.send(message::Message::StoreTextOnCard(TEXT.to_string())).unwrap();
        sender.send(message::Message::Halt).unwrap();

        let result = handle.join();

        // Assert

        assert!(result.is_ok(), "The component should have terminated clearly");
        let card = result.unwrap();
        assert_eq!(Ok(TEXT.to_string()), card.read(), "The component did update card content correctly");
    }

    /* TODO
    #[test]
    pub fn when_both_record_and_print_switches_are_on_and_a_card_is_inserted_then_wtf() {
        todo!("What is the expected behavior????");
    }
    */
}
