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

// TODO: Add tests for swapping operation overflowing a split register

#[test]
pub fn swapping_the_accumulator_is_idempotent() {

    // Arrange

    let (sender, _system) = mpsc::channel();
    let memory = MemoryBuilder::new().with_a(1u8.into()).build();
    let mut alu = DefaultAlu::new_with_memory(sender, memory);

    // Act

    let result = alu.swap_a(Register::A);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(1, 0), alu.memory.a.read());
}

#[test]
pub fn swapping_m_register_updates_m_and_a() {

    // Arrange

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_m(2u8.into())
        .build();

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

    // Act

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

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.m.read());
}

#[test]
pub fn swapping_b_register_updates_b_and_a() {

    // Arrange

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_b(Decimal::new(2, 0).into())
        .build();

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

    // Act

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

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.b.read());
}

#[test]
pub fn swapping_b_split_register_updates_splits_and_a() {

    // Arrange

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_b((Decimal::new(2, 0), Decimal::new(3, 0)).into())
        .build();

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

    // Act

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

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(3, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.b.read());

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

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(3, 0), alu.memory.b.read_left());
}

#[test]
pub fn swapping_c_register_updates_c_and_a() {

    // Arrange

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_c(Decimal::new(2, 0).into())
        .build();

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

    // Act

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

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.c.read());
}

#[test]
pub fn swapping_c_split_register_updates_splits_and_a() {

    // Arrange

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_c((Decimal::new(2, 0), Decimal::new(3, 0)).into())
        .build();

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

    // Act & assert

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

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(3, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.c.read());

    let result = alu.swap_a(Register::c);

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(3, 0), alu.memory.c.read_left());
}

#[test]
pub fn swapping_d_register_updates_d_and_a() {

    // Arrange

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_d(Decimal::new(2, 0).into())
        .build();

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

    // Act

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

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.d.read());
}

#[test]
pub fn swapping_d_split_register_updates_splits_and_a() {

    // Assert

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_d((Decimal::new(2, 0), Decimal::new(3, 0)).into())
        .build();

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

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

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(3, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.d.read());

    let result = alu.swap_a(Register::d);

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(3, 0), alu.memory.d.read_left());
}

#[test]
pub fn swapping_e_register_updates_e_and_a() {

    // Assert

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_e(Decimal::new(2, 0).into())
        .build();

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

    // Act

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

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.e.read());
}

#[test]
pub fn swapping_e_split_register_updates_splits_and_a() {

    // Assert

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_e((Decimal::new(2, 0), Decimal::new(3, 0)).into())
        .build();

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

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

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(3, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.e.read());

    let result = alu.swap_a(Register::e);

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(3, 0), alu.memory.e.read_left());
}

#[test]
pub fn swapping_f_register_updates_f_and_a() {

    // Assert

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_f(Decimal::new(2, 0).into())
        .build();

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

    // Act

    let result = alu.swap_a(Register::F);

    // Assert

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.f.read());
}

#[test]
pub fn swapping_f_split_register_updates_splits_and_a() {

    // Assert

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

    let memory = MemoryBuilder::new()
        .with_a(1u8.into())
        .with_f((Decimal::new(2, 0), Decimal::new(3, 0)).into())
        .build();

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

    // Act & assert

    let result = alu.swap_a(Register::F);

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(3, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(1, 0), alu.memory.f.read());

    let result = alu.swap_a(Register::f);

    assert_eq!(Ok(()), result);
    assert_eq!(Decimal::new(2, 0), alu.memory.a.read());
    assert_eq!(Decimal::new(3, 0), alu.memory.f.read_left());
}
