use p101_is::Register;
use crate::alu::*;
use crate::message::*;
use std::sync::mpsc;
use rust_decimal::Decimal;

fn build_alu(memory: Memory) -> (DefaultAlu, mpsc::Receiver<Message>) {
    let (sender, receiver) = mpsc::channel();

    let alu = DefaultAlu::new_with_memory(sender, memory);

    (alu, receiver)
}

fn read_events_until_timeout(receiver: mpsc::Receiver<Message>) -> Vec<Message> {
    const TIMEOUT: std::time::Duration = std::time::Duration::from_millis(1);
    let mut messages = Vec::new();

    loop {
        if let Ok(message) = receiver.recv_timeout(TIMEOUT) {
            messages.push(message);
        } else {
            break;
        }
    }

    return messages;
}

#[test]
pub fn printing_register_a_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::A,
        operator: Operator::Print
    });

    // Arrange

    let memory = MemoryBuilder::new()
            .with_a(value)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::A);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_register_m_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::M,
        operator: Operator::Print
    });

    // Arrange

    let memory = MemoryBuilder::new()
            .with_m(value)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::M);

    // Assert

    let messages = read_events_until_timeout(receiver);

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_register_r_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::R,
        operator: Operator::Print
    });

    // Arrange

    let memory = MemoryBuilder::new()
            .with_r(value)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::R);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_register_b_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::B,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = SplitRegister::new();
    reg.write(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_b(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::B);

    // Assert

    let messages = read_events_until_timeout(receiver);

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_split_register_b_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::b,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = SplitRegister::new();
    reg.write_left(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_b(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::b);

    // Assert

    let messages = read_events_until_timeout(receiver);

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_register_c_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::C,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = SplitRegister::new();
    reg.write(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_c(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::C);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_split_register_c_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::c,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = SplitRegister::new();
    reg.write_left(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_c(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::c);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_register_d_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::D,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = MemoryRegister::new();
    reg.write(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_d(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::D);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_split_register_d_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::d,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = MemoryRegister::new();
    reg.write_left(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_d(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::d);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_register_e_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::E,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = MemoryRegister::new();
    reg.write(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_e(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::E);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_split_register_e_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::e,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = MemoryRegister::new();
    reg.write_left(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_e(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::e);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_register_f_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::F,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = MemoryRegister::new();
    reg.write(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_f(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::F);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}

#[test]
pub fn printing_split_register_f_posts_its_value_on_the_system_bus() {
    let value = Decimal::new(12345, 2);
    let expected = Message::Print(PrintPayload::Value {
        value: value,
        register: Register::f,
        operator: Operator::Print
    });

    // Arrange

    let mut reg = MemoryRegister::new();
    reg.write_left(value).unwrap();

    let memory = MemoryBuilder::new()
            .with_f(reg)
            .with_precision(2)
            .with_ip(1)
            .build();

    let (mut alu, receiver) = build_alu(memory);

    // Act

    let result = alu.print(Register::f);
    let messages = read_events_until_timeout(receiver);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
    assert!(messages.contains(&expected));
}
