use pimcore::{
    Executable, CoreInstructionsBuilder,
    cpu::CPU,
    instruction_set::{InstructionsBuilder, instruction_data::InstructionDataBuilder, isa::*},
};

#[test]
fn ld_test() {
    let mut cpu = CPU::new(1);
    let mut core_instruction_builder = CoreInstructionsBuilder::new(1);
    let buff: [f32; _] = [
        1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(1).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 0).build());
    inst_builder.make_inst(sldi, idata_build.set_rdimm(2, 0).build());
    inst_builder.make_inst(
        ld,
        idata_build
            .set_rdr1(1, 2)
            .set_imm_len(10 * size_of::<f32>() as i32)
            .build(),
    );
    core_instruction_builder.set_core(1, inst_builder.build());
    let mut executable = Executable::new(cpu, core_instruction_builder.build());
    executable.execute();
    let res = executable
        .cpu_mut()
        .core(1)
        .load::<f32>(0, 10 * size_of::<f32>());

    assert_eq!(
        res.unwrap()[0],
        [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
        "LD failed to load"
    );
}

#[test]
fn st_test() {
    let mut cpu = CPU::new(1);
    let mut core_instruction_builder = CoreInstructionsBuilder::new(1);
    let buff: [f32; _] = [
        1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
    ];
    cpu.core(1).execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(1).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 0).build());
    inst_builder.make_inst(sldi, idata_build.set_rdimm(2, 0).build());
    inst_builder.make_inst(
        st,
        idata_build
            .set_rdr1(1, 2)
            .set_imm_len(10 * size_of::<f32>() as i32)
            .build(),
    );
    core_instruction_builder.set_core(1, inst_builder.build());
    let mut executable = Executable::new(cpu, core_instruction_builder.build());
    executable.execute();
    let res = executable
        .cpu_mut()
        .host()
        .load::<f32>(0, 10 * size_of::<f32>());

    assert_eq!(
        res.unwrap()[0],
        [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0],
        "ST failed to store"
    );
}

#[test]
fn lldi_test() {
    let cpu = CPU::new(1);
    let mut core_instruction_builder = CoreInstructionsBuilder::new(1);
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(1).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 0).build());
    inst_builder.make_inst(
        lldi,
        idata_build
            .set_rdimm(1, 0xff)
            .set_imm_len(10 * size_of::<i32>() as i32)
            .build(),
    );
    core_instruction_builder.set_core(1, inst_builder.build());
    let mut executable = Executable::new(cpu, core_instruction_builder.build());
    executable.execute();
    let res = executable
        .cpu_mut()
        .core(1)
        .load::<i32>(0, 10 * size_of::<i32>());

    assert_eq!(
        res.unwrap()[0],
        [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
        "Lldi failed to memset"
    );
}

#[test]
fn lmv_test() {
    let mut cpu = CPU::new(1);
    let mut core_instruction_builder = CoreInstructionsBuilder::new(1);
    let buff: [f32; _] = [
        1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
    ];
    cpu.core(1).execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(1).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(2, 0).build());
    inst_builder.make_inst(
        sldi,
        idata_build
            .set_rdimm(1, 8 * size_of::<f32>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        lmv,
        idata_build
            .set_rdr1(1, 2)
            .set_imm_len(8 * size_of::<f32>() as i32)
            .build(),
    );
    core_instruction_builder.set_core(1, inst_builder.build());
    let mut executable = Executable::new(cpu, core_instruction_builder.build());
    executable.execute();
    let res = executable
        .cpu_mut()
        .core(1)
        .load::<f32>(0, 16 * size_of::<f32>());

    assert_eq!(
        res.unwrap()[0],
        [
            1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0,
        ],
        "lmv failed to store"
    );
}

#[test]
fn simple_send_recv_test() {
    let mut cpu = CPU::new(2);
    let mut core_instruction_builder = CoreInstructionsBuilder::new(2);
    let buff: [f32; _] = [
        1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
    ];
    cpu.core(1).execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(1).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 3*size_of::<f32>() as i32).build());
    inst_builder.make_inst(
        send,
        idata_build
            .set_r1(1)
            .set_imm_core(2)
            .set_imm_len(8 * size_of::<f32>() as i32)
            .build(),
    );
    core_instruction_builder.set_core(1, inst_builder.build());
    idata_build.set_core_indx(2).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 0).build());
    inst_builder.make_inst(
        recv,
        idata_build
            .set_rd(1)
            .set_imm_core(1)
            .set_imm_len(8 * size_of::<f32>() as i32)
            .build(),
    );
    core_instruction_builder.set_core(2, inst_builder.build());
    let mut executable = Executable::new(cpu, core_instruction_builder.build());
    executable.execute();
    let res = executable
        .cpu_mut()
        .core(2)
        .load::<f32>(0, 8 * size_of::<f32>());

    assert_eq!(
        res.unwrap()[0],
        [
             4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0
        ],
        "send_recv failed to store"
    );
}



// 1 -> 3
// 2 -> 3
// 3 <- 2
// 4 -> 2
// 3 <- 4
// 2 <- 4
// 4 -> 3
// 3 <- 1

#[test]
fn multiple_send_recv_test() {
    let mut cpu = CPU::new(4);
    let mut core_instruction_builder = CoreInstructionsBuilder::new(4);
    let buff: [f32; _] = [
       1.0, 1.0, 1.0, 1.0, 1.0 
    ];
    cpu.core(1).execute_store(0, &buff).unwrap();
    let buff: [f32; _] = [
       2.0, 2.0, 2.0, 2.0, 2.0
    ];
    cpu.core(2).execute_store(0, &buff).unwrap();
    let buff: [f32; _] = [
       3.0, 3.0, 3.0, 3.0, 3.0
    ];
    cpu.core(3).execute_store(0, &buff).unwrap();
    let buff: [f32; _] = [
       4.0, 4.0, 4.0, 4.0, 4.0
    ];
    cpu.core(4).execute_store(0, &buff).unwrap();

    let send_inst = |cpu :&mut CPU, core_instruction_builder: &mut CoreInstructionsBuilder, inst_builder: &mut InstructionsBuilder, from : i32,  to : i32| {
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(from).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, from*size_of::<f32>() as i32).build());
    inst_builder.make_inst(
        send,
        idata_build
            .set_r1(1)
            .set_imm_core(to)
            .set_imm_len(size_of::<f32>() as i32)
            .build(),
    );
    };

    let recv_inst = |cpu :&mut CPU, core_instruction_builder: &mut CoreInstructionsBuilder, mut inst_builder: &mut InstructionsBuilder, to : i32,  from : i32| {
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(to).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, from*size_of::<f32>() as i32).build());
    inst_builder.make_inst(
        recv,
        idata_build
            .set_rd(1)
            .set_imm_core(from)
            .set_imm_len(size_of::<f32>() as i32)
            .build(),
    );
    };
    let mut inst_builder = InstructionsBuilder::new();


    // 1 -> 3
    send_inst(&mut cpu,&mut core_instruction_builder,&mut inst_builder,1, 3);
    core_instruction_builder.set_core(1, inst_builder.build());

    // 2 -> 3
    // 2 <- 4
    send_inst(&mut cpu,&mut core_instruction_builder,&mut inst_builder,2, 3);
    recv_inst(&mut cpu,&mut core_instruction_builder,&mut inst_builder,2, 4);
    core_instruction_builder.set_core(2, inst_builder.build());

    // 3 <- 2
    // 3 <- 4
    // 3 <- 1
    recv_inst(&mut cpu,&mut core_instruction_builder,&mut inst_builder,3, 2);
    recv_inst(&mut cpu,&mut core_instruction_builder,&mut inst_builder,3, 4);
    recv_inst(&mut cpu,&mut core_instruction_builder,&mut inst_builder,3, 1);
    core_instruction_builder.set_core(3, inst_builder.build());
    // 4 -> 2
    // 4 -> 3
    send_inst(&mut cpu,&mut core_instruction_builder,&mut inst_builder,4, 2);
    send_inst(&mut cpu,&mut core_instruction_builder,&mut inst_builder,4, 3);
    core_instruction_builder.set_core(4, inst_builder.build());

    let mut executable = Executable::new(cpu, core_instruction_builder.build());
    executable.execute();
    let res = executable
        .cpu_mut()
        .core(3)
        .load::<f32>(4, 4 * size_of::<f32>());

    assert_eq!(
        res.unwrap()[0],
        [ 1.0, 2.0, 3.0, 4.0    ],
        "send_recv failed to store"
    );
}
