use super::*;
use crate::alu::{TestAlu, TestAluBuilder};
use std::thread;

fn run_component(messages: &[Message]) -> TestAlu {

    // Create a AluComponent<TestAlu>

    let (sender, _receiver) = mpsc::channel();

    let alu = TestAluBuilder::new(sender.clone())
        .build();

    let mut component = AluComponent::new(alu, sender.clone());
    let component_sender = component.link.component_sender.clone();

    // Start the component and return the ALU when done

    let handle = thread::spawn(move || {
        component.run();
        return component.alu;
    });

    // Send messages to the component

    component_sender.send(Message::SystemStatusChanged(SystemStatus::Ready)).unwrap();

    for message in messages.iter() {
        component_sender.send(message.clone()).unwrap();
    }

    component_sender.send(Message::Halt).unwrap();

    // Return the ALU

    handle.join().expect("The component should halt")
}

#[test]
pub fn record_when_the_m_register_should_be_cleared() {

    // Arrange
 
    let (sender, _receiver) = mpsc::channel();

    let alu = TestAluBuilder::new(sender.clone())
        .build();

    let mut component = AluComponent::new(alu, sender.clone());
    let component_sender = component.link.component_sender.clone();

    // Start the component and return the component when done

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

    // Send messages to the component

    component_sender.send(Message::SystemStatusChanged(SystemStatus::Ready)).unwrap();
    component_sender.send(Message::KeyPressed(Key::One)).unwrap();
    component_sender.send(Message::KeyPressed(Key::Add)).unwrap();
    component_sender.send(Message::Halt).unwrap();

    // Return the ALU

    let component = handle.join().expect("The component should halt");

    // Assert

    assert!(component.clear_m_on_next_input);
}

#[test]
pub fn clear_m_register_after_an_addition() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::Add),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("add(M)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn clear_m_register_after_a_subtraction() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::Subtract),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("sub(M)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn clear_m_register_after_an_multiplication() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::Multiply),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("mul(M)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn clear_m_register_after_an_division() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::Divide),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("div(M)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn clear_m_register_after_a_square_root() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::SquareRoot),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("sqr(M)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn when_m_is_copied_to_a_then_clear_m() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::CopyToA),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("copy_to_a(M)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn when_m_is_copied_to_b_then_clear_m() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::B),
        Message::KeyPressed(Key::CopyM),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("copy_m(B)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn when_m_is_copied_to_c_then_clear_m() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::C),
        Message::KeyPressed(Key::CopyM),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("copy_m(C)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn when_m_is_copied_to_d_then_clear_m() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::D),
        Message::KeyPressed(Key::CopyM),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("copy_m(D)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn when_m_is_copied_to_e_then_clear_m() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::E),
        Message::KeyPressed(Key::CopyM),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("copy_m(E)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

#[test]
pub fn when_m_is_copied_to_f_then_clear_m() {

    // Arrange
 
    let messages = vec!(Message::KeyPressed(Key::One),
        Message::KeyPressed(Key::F),
        Message::KeyPressed(Key::CopyM),
        Message::KeyPressed(Key::Nine)
    );

    // Act

    let alu = run_component(&messages);

    // Assert

    assert_eq!(4, alu.log.len());
    assert_eq!("write_to_m(1)", alu.log[0]);
    assert_eq!("copy_m(F)", alu.log[1]);
    assert_eq!("clear_m", alu.log[2]);
    assert_eq!("write_to_m(9)", alu.log[3]);
}

