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

#[test]
pub fn seven_plus_three_equals_ten() {

    //Arrange

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

    let memory = MemoryBuilder::new()
        .with_m(5u8.into())
        .with_a(7u8.into())
        .with_r(9u8.into())
        .with_b(Decimal::new(3, 0).into())
        .with_precision(0)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // Act

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

    // Assert
    
    assert!(result.is_ok());

    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(10, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(10, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
}

#[test]
pub fn seven_dot_five_minus_three_dot_six_equals_three_dot_nine() {

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

    let memory = MemoryBuilder::new()
        .with_m(5u8.into())
        .with_a(Decimal::new(75, 1))
        .with_r(9u8.into())
        .with_b(Decimal::new(36, 1).into())
        .with_precision(0)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // Act

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

    // Assert
    
    assert!(result.is_ok());

    assert_eq!(Decimal::new(36, 1), alu.read_register(Register::M));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(39, 1), alu.read_register(Register::R));
    assert_eq!(Decimal::new(36, 1), alu.read_register(Register::B));
}

#[test]
pub fn seven_by_six_dot_four_equals_33_dot_28() {

    //Arrange

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

    let memory = MemoryBuilder::new()
        .with_m(5u8.into())
        .with_a(Decimal::new(52, 1))
        .with_r(9u8.into())
        .with_b(Decimal::new(64, 1).into())
        .with_precision(0)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // Act

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

    // Assert
    
    assert!(result.is_ok());

    assert_eq!(Decimal::new(64, 1), alu.read_register(Register::M));
    assert_eq!(Decimal::new(33, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(3328, 2), alu.read_register(Register::R));
    assert_eq!(Decimal::new(64, 1), alu.read_register(Register::B));
}

#[test]
pub fn seven_divided_three_equals_two_remainder_one() {

    //Arrange

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

    let memory = MemoryBuilder::new()
        .with_m(5u8.into())
        .with_a(7u8.into())
        .with_r(9u8.into())
        .with_b(Decimal::new(3, 0).into())
        .with_precision(0)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // Act

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

    // Assert
    
    assert!(result.is_ok());

    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
}

#[test]
pub fn dividing_by_zero_turns_on_the_error_light() {

    //Arrange

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

    let memory = MemoryBuilder::new()
        .with_m(5u8.into())
        .with_a(7u8.into())
        .with_r(9u8.into())
        .with_b(Decimal::new(0, 0).into())
        .with_precision(0)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // Act

    let _ = alu.divide(Register::B);

    let mut messages = Vec::new();

    messages.push(system_receiver.try_recv());
    messages.push(system_receiver.try_recv());

    // Assert
    
    assert!(messages.len() >= 1);
    assert!(messages.contains(&Ok(Message::Error)));
}

#[test]
pub fn sqr_of_four_is_two() {

    //Arrange

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

    let memory = MemoryBuilder::new()
        .with_m(4u8.into())
        .with_a(98u8.into())
        .with_r(97u8.into())
        .with_precision(1)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // Act

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

    // Assert
    
    assert!(result.is_ok());

    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::A));

    alu.set_precision(2);
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::A));

    alu.set_precision(3);
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::A));

    alu.set_precision(4);
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::A));

    alu.set_precision(5);
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::A));

    alu.set_precision(15);
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::A));
}

#[test]
pub fn sqr_of_five_is_2_236067977499789() {

    //Arrange

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

    let memory = MemoryBuilder::new()
        .with_m(5u8.into())
        .with_precision(15)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // Act

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

    // Assert
    
    assert!(result.is_ok());

    assert_eq!(Decimal::new(2236067977499789, 15), alu.read_register(Register::A));
}

#[test]
pub fn check_intermediate_registers_values_of_multiple_addiction() {
    let (sender, _receiver) = mpsc::channel();

    let memory = MemoryBuilder::new()
        .with_b(Decimal::new(1, 0).into())
        .with_c(Decimal::new(3, 0).into())
        .with_d(Decimal::new(7, 0).into())
        .with_precision(2)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // A + B

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

    assert!(result.is_ok());
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A + C

    let result = alu.add(Register::C);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A + D

    let result = alu.add(Register::D);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(11, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(11, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));
}

#[test]
pub fn check_intermediate_registers_values_of_multiple_subtractions() {
    let (sender, _receiver) = mpsc::channel();

    let memory = MemoryBuilder::new()
        .with_a(Decimal::new(100, 0).into())
        .with_b(Decimal::new(1, 0).into())
        .with_c(Decimal::new(3, 0).into())
        .with_d(Decimal::new(7, 0).into())
        .with_precision(2)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // A - B

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

    assert!(result.is_ok());
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(99, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(99, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A - C

    let result = alu.subtract(Register::C);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(96, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(96, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A - D

    let result = alu.subtract(Register::D);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(89, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(89, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));
}

#[test]
pub fn check_intermediate_registers_values_of_multiple_multiplications() {
    let (sender, _receiver) = mpsc::channel();

    let memory = MemoryBuilder::new()
        .with_a(Decimal::new(1, 0).into())
        .with_b(Decimal::new(3, 0).into())
        .with_c(Decimal::new(5, 0).into())
        .with_d(Decimal::new(7, 0).into())
        .with_precision(2)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // A * B

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

    assert!(result.is_ok());
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A * C

    let result = alu.multiply(Register::C);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(15, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(15, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A * D

    let result = alu.multiply(Register::D);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(105, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(105, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));
}

#[test]
pub fn check_intermediate_registers_values_of_multiple_divisions() {
    let (sender, _receiver) = mpsc::channel();

    let memory = MemoryBuilder::new()
        .with_a(Decimal::new(105, 0).into())
        .with_b(Decimal::new(3, 0).into())
        .with_c(Decimal::new(5, 0).into())
        .with_d(Decimal::new(7, 0).into())
        .with_precision(2)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // A / B

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

    assert!(result.is_ok());
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(35, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A / C

    let result = alu.divide(Register::C);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A / D

    let result = alu.divide(Register::D);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(1, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(7, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));
}

#[test]
pub fn check_intermediate_registers_values_of_multiple_calculations() {
    let (sender, _receiver) = mpsc::channel();

    let memory = MemoryBuilder::new()
        .with_a(Decimal::new(105, 0).into())
        .with_b(Decimal::new(3, 0).into())
        .with_c(Decimal::new(5, 0).into())
        .with_d(Decimal::new(4, 0).into())
        .with_e(Decimal::new(2, 0).into())
        .with_precision(2)
        .build();
        
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // A + B

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

    assert!(result.is_ok());
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(108, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(108, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A - C

    let result = alu.subtract(Register::C);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(103, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(103, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A * D

    let result = alu.multiply(Register::D);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(412, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(412, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));

    // A / E

    let result = alu.divide(Register::E);

    assert!(result.is_ok());
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::M));
    assert_eq!(Decimal::new(206, 0), alu.read_register(Register::A));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::R));
    assert_eq!(Decimal::new(3, 0), alu.read_register(Register::B));
    assert_eq!(Decimal::new(5, 0), alu.read_register(Register::C));
    assert_eq!(Decimal::new(4, 0), alu.read_register(Register::D));
    assert_eq!(Decimal::new(2, 0), alu.read_register(Register::E));
    assert_eq!(Decimal::new(0, 0), alu.read_register(Register::F));
}

