use super::*;
use crate::emulator::system::memory::*;

use common::prelude::*;

fn create_test_processor() -> Processor {
    let memory = Box::new(MainStore::new());
    Processor::new(memory, Vec::new())
}

#[test]
fn test_nop_instruction() {
    let mut cpu = create_test_processor();
    let initial_state = cpu.registers;

    Instruction::Nop.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );

    assert_eq!(
        cpu.registers
            .get(Register::Rg0)
            .expect("Failed to get register Rg0"),
        initial_state
            .get(Register::Rg0)
            .expect("Failed to get register Rg0")
    );
    assert_eq!(
        cpu.registers
            .get(Register::Acc)
            .expect("Failed to get register Acc"),
        initial_state
            .get(Register::Acc)
            .expect("Failed to get register Acc")
    );
}

#[test]
fn test_mov_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1234_5678;

    let mov_instr = Instruction::Mov(RTypeArgs::new(
        Some(Register::Rg1),
        None,
        Some(Register::Rg2),
        None,
    ));

    mov_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
        0x1234_5678
    );
}

#[test]
fn test_mov_signed_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0000_00FF;

    let mov_signed_instr = Instruction::MovSigned(RTypeArgs::new(
        Some(Register::Rg1),
        None,
        Some(Register::Rg2),
        None,
    ));

    mov_signed_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
        0xFFFF_FFFF
    );
}

#[test]
fn test_load_byte_instruction() {
    let mut cpu = create_test_processor();
    let addr = 0x100;
    cpu.memory.write_byte(addr, 0xAB);
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr - 4;

    let load_byte_instr = Instruction::LoadByte(ITypeArgs::new(
        4,
        Some(Register::Rg1),
        Some(Register::Rg2),
    ));

    load_byte_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
        0x0000_00AB
    );
}

#[test]
fn test_load_byte_signed_instruction() {
    let mut cpu = create_test_processor();
    let addr = 0x100;
    cpu.memory.write_byte(addr, 0xFF);
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;

    let load_byte_signed_instr = Instruction::LoadByteSigned(ITypeArgs::new(
        0,
        Some(Register::Rg1),
        Some(Register::Rg2),
    ));

    load_byte_signed_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
        0xFFFF_FFFF
    );
}

#[test]
fn test_load_halfword_instruction() {
    let mut cpu = create_test_processor();
    let addr = 0x100;
    cpu.memory
        .write_word(addr, 0x1234_5678)
        .expect("Failed to write word to memory");
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;

    let load_halfword_instr = Instruction::LoadHalfword(ITypeArgs::new(
        0,
        Some(Register::Rg1),
        Some(Register::Rg2),
    ));

    load_halfword_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
        0x0000_1234
    );
}

#[test]
fn test_load_word_instruction() {
    let mut cpu = create_test_processor();
    let addr = 0x100;
    cpu.memory
        .write_word(addr, 0x1234_5678)
        .expect("Failed to write word to memory");
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;

    let load_word_instr = Instruction::LoadWord(ITypeArgs::new(
        0,
        Some(Register::Rg1),
        Some(Register::Rg2),
    ));

    load_word_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
        0x1234_5678
    );
}

#[test]
fn test_store_byte_instruction() {
    let mut cpu = create_test_processor();
    let addr = 0x100;
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0xAB;

    let store_byte_instr = Instruction::StoreByte(ITypeArgs::new(
        0,
        Some(Register::Rg2),
        Some(Register::Rg1),
    ));

    store_byte_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(cpu.memory.read_byte(addr), 0xAB);
}

#[test]
fn test_store_word_instruction() {
    let mut cpu = create_test_processor();
    let addr = 0x100;
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = addr;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0x1234_5678;

    let store_word_instr = Instruction::StoreWord(ITypeArgs::new(
        0,
        Some(Register::Rg2),
        Some(Register::Rg1),
    ));

    store_word_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(cpu.memory.read_word(addr).expect("Emulator was slain by losing the game while attempting to execute instruction"), 0x1234_5678);
}

#[test]
fn test_add_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 15;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 25;

    let add_instr = Instruction::Add(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        Some(Register::Rg3),
        None,
    ));

    add_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
        40
    );
}

#[test]
fn test_sub_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 20;

    let sub_instr = Instruction::Sub(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        Some(Register::Rg3),
        None,
    ));

    sub_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
        30
    );
}

#[test]
fn test_and_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;

    let and_instr = Instruction::And(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        Some(Register::Rg3),
        None,
    ));

    and_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
        0b1000
    );
}

#[test]
fn test_or_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;

    let or_instr = Instruction::Or(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        Some(Register::Rg3),
        None,
    ));

    or_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
        0b1110
    );
}

#[test]
fn test_xor_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;

    let xor_instr = Instruction::Xor(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        Some(Register::Rg3),
        None,
    ));

    xor_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
        0b0110
    );
}

#[test]
fn test_not_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0F0F_0F0F;

    let not_instr = Instruction::Not(RTypeArgs::new(
        Some(Register::Rg1),
        None,
        Some(Register::Rg2),
        None,
    ));

    not_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg2).expect("Failed to get register Rg2"),
        0xF0F0_F0F0
    );
}

#[test]
fn test_compare_equal() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 42;

    let cmp_instr = Instruction::Compare(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        None,
        None,
    ));

    cmp_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );

    assert!(cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
    assert!(
        !cpu.get_flag(Flag::GreaterThan)
            .expect("Failed to get flag GreaterThan")
    );
    assert!(
        !cpu.get_flag(Flag::LessThan)
            .expect("Failed to get flag LessThan")
    );
}

#[test]
fn test_compare_greater_than() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 50;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30;

    let cmp_instr = Instruction::Compare(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        None,
        None,
    ));

    cmp_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );

    assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
    assert!(
        cpu.get_flag(Flag::GreaterThan)
            .expect("Failed to get flag GreaterThan")
    );
    assert!(
        !cpu.get_flag(Flag::LessThan)
            .expect("Failed to get flag LessThan")
    );
}

#[test]
fn test_compare_less_than() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 20;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 30;

    let cmp_instr = Instruction::Compare(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        None,
        None,
    ));

    cmp_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );

    assert!(!cpu.get_flag(Flag::Equal).expect("Failed to get flag Equal"));
    assert!(
        !cpu.get_flag(Flag::GreaterThan)
            .expect("Failed to get flag GreaterThan")
    );
    assert!(
        cpu.get_flag(Flag::LessThan)
            .expect("Failed to get flag LessThan")
    );
}

#[test]
fn test_increment_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;

    let inc_instr =
        Instruction::Increment(RTypeArgs::new(Some(Register::Rg1), None, None, None));

    inc_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
        43
    );
}

#[test]
fn test_decrement_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 42;

    let dec_instr =
        Instruction::Decrement(RTypeArgs::new(Some(Register::Rg1), None, None, None));

    dec_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
        41
    );
}

#[test]
fn test_shift_left_with_shamt() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010;

    let shl_instr = Instruction::ShiftLeft(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Zero),
        Some(Register::Rg1),
        Some(2),
    ));

    shl_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
        0b10_1000
    );
}

#[test]
fn test_shift_right_with_shamt() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b10_1000;

    let shr_instr = Instruction::ShiftRight(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Zero),
        Some(Register::Rg1),
        Some(2),
    ));

    shr_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
        0b1010
    );
}

// #[test]
// fn test_shift_left_with_register() {
//     let mut cpu = create_test_processor();
//     *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1010;

//     let shl_instr =
//         Instruction::ShiftLeft(RTypeArgs::new(Some(Register::Rg1), None, None,
// Some(3)));

//     shl_instr.execute(&mut cpu).expect(
//         "Emulator was slain by losing the game while attempting to execute
// instruction",     );
//     assert_eq!(
//         cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
//         0b101_0000
//     );
// }

#[test]
fn test_load_lower_immediate() {
    let mut cpu = create_test_processor();

    let lli_instr = Instruction::LoadLowerImmediate(ITypeArgs::new(
        0x1234,
        Some(Register::Rg1),
        None,
    ));

    lli_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
        0x0000_1234
    );
}

#[test]
fn test_load_upper_immediate() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x0000_5678;

    let lui_instr = Instruction::LoadUpperImmediate(ITypeArgs::new(
        0x1234,
        Some(Register::Rg1),
        None,
    ));

    lui_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg1).expect("Failed to get register Rg1"),
        0x1234_5678
    );
}

#[test]
fn test_jump_unconditional() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
    let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx");

    let jump_instr = Instruction::Jump(ITypeArgs::new(0x100, Some(Register::Rg1), None));

    jump_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
        0x1100
    );
    assert_ne!(
        cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
        initial_pc
    );
}

#[test]
fn test_jump_equal_when_flag_set() {
    let mut cpu = create_test_processor();
    cpu.set_flag(Flag::Equal, true);
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;

    let jump_eq_instr =
        Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));

    jump_eq_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
        0x1100
    );
}

#[test]
fn test_jump_equal_when_flag_not_set() {
    let mut cpu = create_test_processor();
    cpu.set_flag(Flag::Equal, false);
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0x1000;
    let initial_pc = cpu.get(Register::Pcx).expect("Failed to get register Pcx");

    let jump_eq_instr =
        Instruction::JumpEq(ITypeArgs::new(0x100, Some(Register::Rg1), None));

    jump_eq_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Pcx).expect("Failed to get register Pcx"),
        initial_pc
    );
}

#[test]
fn test_halt_instruction() {
    let mut cpu = create_test_processor();
    assert!(!cpu.halted);

    Instruction::Halt.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert!(cpu.halted);
}

#[test]
fn test_nand_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;

    let nand_instr = Instruction::Nand(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        Some(Register::Rg3),
        None,
    ));

    nand_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
        !0b1000
    );
}

#[test]
fn test_nor_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;

    let nor_instr = Instruction::Nor(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        Some(Register::Rg3),
        None,
    ));

    nor_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
        !0b1110
    );
}

#[test]
fn test_xnor_instruction() {
    let mut cpu = create_test_processor();
    *cpu.reg(Register::Rg1).expect("Failed to get register Rg1") = 0b1100;
    *cpu.reg(Register::Rg2).expect("Failed to get register Rg2") = 0b1010;

    let xnor_instr = Instruction::Xnor(RTypeArgs::new(
        Some(Register::Rg1),
        Some(Register::Rg2),
        Some(Register::Rg3),
        None,
    ));

    xnor_instr.execute(&mut cpu).expect(
        "Emulator was slain by losing the game while attempting to execute instruction",
    );
    assert_eq!(
        cpu.get(Register::Rg3).expect("Failed to get register Rg3"),
        !0b0110
    );
}
