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

const LABELS: [Option<Instruction>; 24] = [
    Some(Instruction::Abs),
    Some(Instruction::Label(JumpDestination::aV)),
    Some(Instruction::Label(JumpDestination::aW)),
    Some(Instruction::Label(JumpDestination::aY)),
    Some(Instruction::Label(JumpDestination::aZ)),
    Some(Instruction::Label(JumpDestination::bV)),
    Some(Instruction::Label(JumpDestination::bW)),
    Some(Instruction::Label(JumpDestination::bY)),
    Some(Instruction::Label(JumpDestination::bZ)),
    Some(Instruction::Label(JumpDestination::eV)),
    Some(Instruction::Label(JumpDestination::eW)),
    Some(Instruction::Label(JumpDestination::eY)),
    Some(Instruction::Label(JumpDestination::eZ)),
    Some(Instruction::Label(JumpDestination::fV)),
    Some(Instruction::Label(JumpDestination::fW)),
    Some(Instruction::Label(JumpDestination::fY)),
    Some(Instruction::Label(JumpDestination::fZ)),
    None,
    None,
    None,
    None,
    None,
    None,
    None,
];

fn alu_with_labels(a: i64) -> (DefaultAlu, mpsc::Receiver<Message>) {
    let memory = MemoryBuilder::new()
        .with_p1(LABELS.into())
        .with_ip(24)
        .with_a(Decimal::new(a, 0))
        .build();

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

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

    (alu, receiver)
}

#[test]
pub fn setup_fn_can_set_a_to_zero() {
    let (alu, _) = alu_with_labels(0);
    assert_eq!(Decimal::new(0, 0), alu.memory.a.read());
}

#[test]
pub fn setup_fn_can_set_a_to_one() {
    let (alu, _) = alu_with_labels(1);
    assert_eq!(Decimal::new(1, 0), alu.memory.a.read());
}

#[test]
#[allow(non_snake_case)]
pub fn when_a_is_zero_jumping_to__v_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::_V);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
#[allow(non_snake_case)]
pub fn when_a_is_one_jumping_to__v_set_ip_to_1() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::_V);

    assert_eq!(Ok(()), result);
    assert_eq!(1, alu.memory.ip);
}

#[test]
#[allow(non_snake_case)]
pub fn when_a_is_zero_jumping_to__y_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::_Y);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
#[allow(non_snake_case)]
pub fn when_a_is_one_jumping_to__y_should_set_ip_to_3() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::_Y);

    assert_eq!(Ok(()), result);
    assert_eq!(3, alu.memory.ip);
}

#[test]
#[allow(non_snake_case)]
pub fn when_a_is_zero_jumping_to__z_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::_Z);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
#[allow(non_snake_case)]
pub fn when_a_is_one_jumping_to__z_should_set_ip_to_4() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::_Z);

    assert_eq!(Ok(()), result);
    assert_eq!(4, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumpidoes_not_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::cV);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_cv_should_set_ip_to_5() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::cV);

    assert_eq!(Ok(()), result);
    assert_eq!(5, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_cw_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::cW);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_cw_should_set_ip_to_6() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::cW);

    assert_eq!(Ok(()), result);
    assert_eq!(6, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_cy_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::cY);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_cy_should_set_ip_to_7() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::cY);

    assert_eq!(Ok(()), result);
    assert_eq!(7, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_cz_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::cZ);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_cz_should_set_ip_to_8() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::cZ);

    assert_eq!(Ok(()), result);
    assert_eq!(8, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_dv_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::dV);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_dv_should_set_ip_to_9() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::dV);

    assert_eq!(Ok(()), result);
    assert_eq!(9, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_dw_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::dW);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_dw_should_set_ip_to_10() {
    let (mut alu, _) = alu_with_labels(1);
    
    let result = alu.conditional_jump(ConditionalOrigin::dW);

    assert_eq!(Ok(()), result);
    assert_eq!(10, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_dy_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::dY);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_dy_should_set_ip_to_11() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::dY);

    assert_eq!(Ok(()), result);
    assert_eq!(11, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_dz_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::dZ);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_dz_should_set_ip_to_12() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::dZ);

    assert_eq!(Ok(()), result);
    assert_eq!(12, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_rv_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::rV);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_rv_should_set_ip_to_13() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::rV);

    assert_eq!(Ok(()), result);
    assert_eq!(13, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_rw_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::rW);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_rw_should_set_ip_to_14() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::rW);

    assert_eq!(Ok(()), result);
    assert_eq!(14, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_ry_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::rY);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_ry_should_set_ip_to_15() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::rY);

    assert_eq!(Ok(()), result);
    assert_eq!(15, alu.memory.ip);
}

#[test]
pub fn when_a_is_zero_jumping_to_rz_update_the_ip() {
    let (mut alu, _) = alu_with_labels(0);

    let result = alu.conditional_jump(ConditionalOrigin::rZ);

    assert_eq!(Ok(()), result);
    assert_eq!(25, alu.memory.ip);
}

#[test]
pub fn when_a_is_one_jumping_to_rz_should_set_ip_to_16() {
    let (mut alu, _) = alu_with_labels(1);

    let result = alu.conditional_jump(ConditionalOrigin::rZ);

    assert_eq!(Ok(()), result);
    assert_eq!(16, alu.memory.ip);
}
