use pimcore::{
    Executable,
    cpu::CPU,
    instruction_set::{InstructionsBuilder, instruction_data::InstructionDataBuilder, isa::*},
    memory_manager::{MemoryStorable, type_traits::UpcastDestTraits},
};

/// VVADD Test
fn vvadd_test_generic<F, T>(err: &str)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable,
{
    let mut cpu = CPU::new(0);
    let buff: [F; _] = [
        1.0.into(),
        2.0.into(),
        3.0.into(),
        4.0.into(),
        5.0.into(),
        6.0.into(),
        7.0.into(),
        8.0.into(),
        9.0.into(),
        10.0.into(),
        11.0.into(),
        12.0.into(),
        13.0.into(),
        14.0.into(),
        15.0.into(),
        16.0.into(),
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).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, 8 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 16 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        vvadd,
        idata_build
            .set_rdr1r2(3, 1, 2)
            .set_imm_len(8 * size_of::<F>() as i32)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(16 * size_of::<F>(), 8 * size_of::<T>()).unwrap()[0],
        vec![
            10.0.into(),
            12.0.into(),
            14.0.into(),
            16.0.into(),
            18.0.into(),
            20.0.into(),
            22.0.into(),
            24.0.into()
        ],
        "Wrong result for {}",
        err
    );
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 16 * size_of::<F>()).unwrap()[0],
        &buff,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            16 * size_of::<F>() + 8 * size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn vvadd_test() {
    vvadd_test_generic::<f32, f32>("vvadd<f32,f32>");
    vvadd_test_generic::<f64, f32>("vvadd<f64,f32>");
    vvadd_test_generic::<f32, f64>("vvadd<f32,f64>");
    vvadd_test_generic::<f64, f64>("vvadd<f64,f64>");
}

/// VVSUM Test
fn vvsub_test_generic<F, T>(err: &str)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable,
{
    let mut cpu = CPU::new(0);
    let buff: [F; _] = [
        1.0.into(),
        2.0.into(),
        3.0.into(),
        4.0.into(),
        5.0.into(),
        6.0.into(),
        7.0.into(),
        8.0.into(),
        9.0.into(),
        10.0.into(),
        11.0.into(),
        12.0.into(),
        13.0.into(),
        14.0.into(),
        15.0.into(),
        16.0.into(),
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).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, 8 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 16 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        vvsub,
        idata_build
            .set_rdr1r2(3, 1, 2)
            .set_imm_len(8 * size_of::<F>() as i32)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(16 * size_of::<F>(), 8 * size_of::<T>()).unwrap()[0],
        vec![
            (-8.0).into(),
            (-8.0).into(),
            (-8.0).into(),
            (-8.0).into(),
            (-8.0).into(),
            (-8.0).into(),
            (-8.0).into(),
            (-8.0).into()
        ],
        "Wrong result for {}",
        err
    );
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 16 * size_of::<F>()).unwrap()[0],
        &buff,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            16 * size_of::<F>() + 8 * size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn vvsub_test() {
    vvsub_test_generic::<f32, f32>("vvsub<f32,f32>");
    vvsub_test_generic::<f64, f32>("vvsub<f64,f32>");
    vvsub_test_generic::<f32, f64>("vvsub<f32,f64>");
    vvsub_test_generic::<f64, f64>("vvsub<f64,f64>");
}

/// VVMUL Test
fn vvmul_test_generic<F, T>(err: &str)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable,
{
    let mut cpu = CPU::new(0);
    let buff: [F; _] = [
        1.0.into(),
        2.0.into(),
        3.0.into(),
        4.0.into(),
        5.0.into(),
        6.0.into(),
        7.0.into(),
        8.0.into(),
        9.0.into(),
        10.0.into(),
        11.0.into(),
        12.0.into(),
        13.0.into(),
        14.0.into(),
        15.0.into(),
        16.0.into(),
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).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, 8 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 16 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        vvmul,
        idata_build
            .set_rdr1r2(3, 1, 2)
            .set_imm_len(8 * size_of::<F>() as i32)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(16 * size_of::<F>(), 8 * size_of::<T>()).unwrap()[0],
        vec![
            (9.0).into(),
            (20.0).into(),
            (33.0).into(),
            (48.0).into(),
            (65.0).into(),
            (84.0).into(),
            (105.0).into(),
            (128.0).into()
        ],
        "Wrong result for {}",
        err
    );
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 16 * size_of::<F>()).unwrap()[0],
        &buff,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            16 * size_of::<F>() + 8 * size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn vvmul_test() {
    vvmul_test_generic::<f32, f32>("vvmul<f32,f32>");
    vvmul_test_generic::<f64, f32>("vvmul<f64,f32>");
    vvmul_test_generic::<f32, f64>("vvmul<f32,f64>");
    vvmul_test_generic::<f64, f64>("vvmul<f64,f64>");
}

/// VVDMUL Test
fn vvdmul_test_generic<F, T>(err: &str)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable,
{
    let mut cpu = CPU::new(0);
    let buff: [F; _] = [
        1.0.into(),
        2.0.into(),
        3.0.into(),
        4.0.into(),
        5.0.into(),
        6.0.into(),
        7.0.into(),
        8.0.into(),
        9.0.into(),
        10.0.into(),
        11.0.into(),
        12.0.into(),
        13.0.into(),
        14.0.into(),
        15.0.into(),
        16.0.into(),
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).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, 8 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 16 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        vvdmul,
        idata_build
            .set_rdr1r2(3, 1, 2)
            .set_imm_len(8 * size_of::<F>() as i32)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(16 * size_of::<F>(), size_of::<T>()).unwrap()[0],
        vec![
            (492.0).into(),
        ],
        "Wrong result for {}",
        err
    );
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 16 * size_of::<F>()).unwrap()[0],
        &buff,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            16 * size_of::<F>() + size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn vvdmul_test() {
    vvdmul_test_generic::<f32, f32>("vvdmul<f32,f32>");
    vvdmul_test_generic::<f64, f32>("vvdmul<f64,f32>");
    vvdmul_test_generic::<f32, f64>("vvdmul<f32,f64>");
    vvdmul_test_generic::<f64, f64>("vvdmul<f64,f64>");
}

/// vvmax Test
fn vvmax_test_generic<F, T>(err: &str)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable,
{
    let mut cpu = CPU::new(0);
    let buff: [F; _] = [
        9.0.into(),
        2.0.into(),
        11.0.into(),
        4.0.into(),
        13.0.into(),
        6.0.into(),
        7.0.into(),
        8.0.into(),
        1.0.into(),
        10.0.into(),
        3.0.into(),
        12.0.into(),
        4.0.into(),
        14.0.into(),
        15.0.into(),
        16.0.into(),
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).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, 8 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 16 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        vvmax,
        idata_build
            .set_rdr1r2(3, 1, 2)
            .set_imm_len(8 * size_of::<F>() as i32)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(16 * size_of::<F>(), 8 * size_of::<T>()).unwrap()[0],
        vec![
        9.0.into(),
        10.0.into(),
        11.0.into(),
        12.0.into(),
        13.0.into(),
        14.0.into(),
        15.0.into(),
        16.0.into(),
        ],
        "Wrong result for {}",
        err
    );
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 16 * size_of::<F>()).unwrap()[0],
        &buff,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            16 * size_of::<F>() + 8 * size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn vvmax_test() {
    vvmax_test_generic::<f32, f32>("vvmax<f32,f32>");
    vvmax_test_generic::<f64, f32>("vvmax<f64,f32>");
    vvmax_test_generic::<f32, f64>("vvmax<f32,f64>");
    vvmax_test_generic::<f64, f64>("vvmax<f64,f64>");
}

/// vavg Test
fn vavg_test_generic<F, T>(err: &str)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable,
{
    let mut cpu = CPU::new(0);
    let buff: [F; _] = [
        9.0.into(),
        2.0.into(),
        11.0.into(),
        4.0.into(),
        13.0.into(),
        6.0.into(),
        7.0.into(),
        8.0.into(),
        1.0.into(),
        10.0.into(),
        3.0.into(),
        12.0.into(),
        4.0.into(),
        14.0.into(),
        15.0.into(),
        16.0.into(),
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 0).build());
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 16 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        vavg,
        idata_build
            .set_rdr1r2(3, 1, 1)
            .set_imm_len(8 * size_of::<F>() as i32)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(16 * size_of::<F>(), size_of::<T>()).unwrap()[0],
        vec![
        7.5.into(),
        ],
        "Wrong result for {}",
        err
    );
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 16 * size_of::<F>()).unwrap()[0],
        &buff,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            16 * size_of::<F>() +  size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn vavg_test() {
    vavg_test_generic::<f32, f32>("vavg<f32,f32>");
    vavg_test_generic::<f64, f32>("vavg<f64,f32>");
    vavg_test_generic::<f32, f64>("vavg<f32,f64>");
    vavg_test_generic::<f64, f64>("vavg<f64,f64>");
}

/// vrelu Test
fn vrelu_test_generic<F, T>(err: &str)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable,
{
    let mut cpu = CPU::new(0);
    let buff: [F; _] = [
        (-9.0).into(),
        2.0.into(),
        11.0.into(),
        (-4.0).into(),
        13.0.into(),
        (-6.0).into(),
        7.0.into(),
        (-6.0).into(),
        1.0.into(),
        (-6.0).into(),
        3.0.into(),
        12.0.into(),
        (-6.0).into(),
        14.0.into(),
        (-6.0).into(),
        16.0.into(),
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 0).build());
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 16 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        vrelu,
        idata_build
            .set_rdr1r2(3, 1, 1)
            .set_imm_len(8 * size_of::<F>() as i32)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(16 * size_of::<F>(), 8*size_of::<T>()).unwrap()[0],
        vec![
        0.0.into(),
        2.0.into(),
        11.0.into(),
        0.0.into(),
        13.0.into(),
        0.0.into(),
        7.0.into(),
        0.0.into(),
        ],
        "Wrong result for {}",
        err
    );
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 16 * size_of::<F>()).unwrap()[0],
        &buff,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            16 * size_of::<F>() +  8*size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn vrelu_test() {
    vrelu_test_generic::<f32, f32>("vrelu<f32,f32>");
    vrelu_test_generic::<f64, f32>("vrelu<f64,f32>");
    vrelu_test_generic::<f32, f64>("vrelu<f32,f64>");
    vrelu_test_generic::<f64, f64>("vrelu<f64,f64>");
}

/// vtanh Test
fn vtanh_test_generic<F, T>(err: &str)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable + UpcastDestTraits<T>,
{
    let mut cpu = CPU::new(0);
    let buff: [F; _] = [
        0.1.into(),
        0.2.into(),
        0.3.into(),
        0.4.into(),
        0.5.into(),
        0.6.into(),
        0.7.into(),
        0.8.into(),
        0.9.into(),
        0.10.into(),
        0.11.into(),
        0.12.into(),
        0.13.into(),
        0.14.into(),
        0.15.into(),
        0.16.into(),
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 0).build());
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 16 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        vtanh,
        idata_build
            .set_rdr1r2(3, 1, 1)
            .set_imm_len(8 * size_of::<F>() as i32)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
    
    assert!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(16 * size_of::<F>(), 8*size_of::<T>()).unwrap()[0].iter().zip(
        vec![
        T::from(0.1).tanh(),
        T::from(0.2).tanh(),
        T::from(0.3).tanh(),
        T::from(0.4).tanh(),
        T::from(0.5).tanh(),
        T::from(0.6).tanh(),
        T::from(0.7).tanh(),
        T::from(0.8).tanh(),
        ]).all(|(&a,b) : (&T, T)| {a-b < 0.001.into()}),
        "Wrong result for {}",
        err
    );
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 16 * size_of::<F>()).unwrap()[0],
        &buff,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            16 * size_of::<F>() +  8*size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn vtanh_test() {
    vtanh_test_generic::<f32, f32>("vtanh<f32,f32>");
    vtanh_test_generic::<f64, f32>("vtanh<f64,f32>");
    vtanh_test_generic::<f32, f64>("vtanh<f32,f64>");
    vtanh_test_generic::<f64, f64>("vtanh<f64,f64>");
}


/// vsigm Test
fn vsigm_test_generic<F, T>(err: &str)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable + UpcastDestTraits<T>,
{
    let mut cpu = CPU::new(0);
    let buff: [F; _] = [
        0.1.into(),
        0.2.into(),
        0.3.into(),
        0.4.into(),
        0.5.into(),
        0.6.into(),
        0.7.into(),
        0.8.into(),
        0.9.into(),
        0.10.into(),
        0.11.into(),
        0.12.into(),
        0.13.into(),
        0.14.into(),
        0.15.into(),
        0.16.into(),
    ];
    cpu.host().execute_store(0, &buff).unwrap();
    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 0).build());
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 16 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        vsigm,
        idata_build
            .set_rdr1r2(3, 1, 1)
            .set_imm_len(8 * size_of::<F>() as i32)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
    
    assert!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(16 * size_of::<F>(), 8*size_of::<T>()).unwrap()[0].iter().zip(
        vec![
        T::from(0.1).sigm(),
        T::from(0.2).sigm(),
        T::from(0.3).sigm(),
        T::from(0.4).sigm(),
        T::from(0.5).sigm(),
        T::from(0.6).sigm(),
        T::from(0.7).sigm(),
        T::from(0.8).sigm(),
        ]).all(|(&a,b) : (&T, T)| {a-b < 0.001.into()}),
        "Wrong result for {}",
        err
    );
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 16 * size_of::<F>()).unwrap()[0],
        &buff,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            16 * size_of::<F>() +  8*size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn vsigm_test() {
    vsigm_test_generic::<f32, f32>("vsigm<f32,f32>");
    vsigm_test_generic::<f64, f32>("vsigm<f64,f32>");
    vsigm_test_generic::<f32, f64>("vsigm<f32,f64>");
    vsigm_test_generic::<f64, f64>("vsigm<f64,f64>");
}



/// mvmul Test
fn mvmul_test_generic<F,M, T>(err: &str, relu:i32)
where
    F: From<f32> + std::fmt::Debug + PartialEq<F> + MemoryStorable,
    M: From<f32> + std::fmt::Debug + PartialEq<M> + MemoryStorable,
    T: From<f32> + std::fmt::Debug + PartialEq<T> + MemoryStorable + UpcastDestTraits<T>,
{
    let mut cpu = CPU::new(0);
    cpu.reserve_crossbar(1, 4 * size_of::<M>(),  4);
    let (memory, crossbars) = cpu.host().get_memory_crossbar();
    let matrix: [M; _] = [
        1.0.into(),
        2.0.into(),
        3.0.into(),
        4.0.into(),
        5.0.into(),
        (-56.0).into(),
        7.0.into(),
        8.0.into(),
        9.0.into(),
        10.0.into(),
        11.0.into(),
        12.0.into(),
        13.0.into(),
        14.0.into(),
        15.0.into(),
        16.0.into(),
    ];
    crossbars.get_mut(0).unwrap().execute_store( &matrix).unwrap();
    let vector: [F; _] = [
        1.0.into(),
        2.0.into(),
        3.0.into(),
        4.0.into(),
    ];
    memory.execute_store(0, &vector).unwrap();

    let mut inst_builder = InstructionsBuilder::new();
    let mut idata_build = InstructionDataBuilder::new();
    idata_build.set_core_indx(0).fix_core_indx();
    inst_builder.make_inst(sldi, idata_build.set_rdimm(1, 0).build());
    inst_builder.make_inst(
        sldi,
        idata_build.set_rdimm(3, 4 * size_of::<F>() as i32).build(),
    );
    inst_builder.make_inst(
        setbw,
        idata_build
            .set_ibiw_obiw(8 * size_of::<F>() as i32, 8 * size_of::<T>() as i32)
            .build(),
    );
    inst_builder.make_inst(
        mvmul,
        idata_build
            .set_rdr1(3, 1)
            .set_mbiw_immrelu_immgroup(8*size_of::<M>() as i32, relu, 0)
            .build(),
    );
    let core_instruction = vec![inst_builder.build().into()];
    let mut executable = Executable::new(cpu, core_instruction);
    executable.execute();

    // Check result correct
   if relu == 0 {
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(4 * size_of::<F>(), 4*size_of::<T>()).unwrap()[0],
        vec![
        90.0.into(),
        (-24.0).into(),
        110.0.into(),
        120.0.into(),
        ],
        "Wrong result for {}",
        err
    );
   }
    else {
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<T>(4 * size_of::<F>(), 4*size_of::<T>()).unwrap()[0],
        vec![
        90.0.into(),
        0.0.into(),
        110.0.into(),
        120.0.into(),
        ],
        "Wrong result for {}",
        err
    );
   }
    // Check first part equal
    assert_eq!(
        executable
            .cpu_mut()
            .host()
            .load::<F>(0, 4 * size_of::<F>()).unwrap()[0],
        &vector,
        "Altered first part for {}",
        err
    );
    //Check that later is 0
    assert_eq!(
        executable.cpu_mut().host().load::<i32>(
            4 * size_of::<F>() +  4*size_of::<T>(),
            4 * size_of::<i32>()
        ).unwrap()[0],
        [0, 0, 0, 0],
        "Altered first part for {}",
        err
    );
}

#[test]
fn mvmul_test() {
    mvmul_test_generic::<f32,f32,f32>("mvmul<f32,f32,f32>",0);
    mvmul_test_generic::<f32,f32,f64>("mvmul<f32,f32,f64>",0);
    mvmul_test_generic::<f32,f64,f32>("mvmul<f32,f64,f32>",0);
    mvmul_test_generic::<f32,f64,f64>("mvmul<f32,f64,f64>",0);
    mvmul_test_generic::<f64,f32,f32>("mvmul<f64,f32,f32>",0);
    mvmul_test_generic::<f64,f32,f64>("mvmul<f64,f32,f64>",0);
    mvmul_test_generic::<f64,f64,f32>("mvmul<f64,f64,f32>",0);
    mvmul_test_generic::<f64,f64,f64>("mvmul<f64,f64,f64>",0);

    mvmul_test_generic::<f32,f32,f32>("mvmul<f32,f32,f32>",1);
    mvmul_test_generic::<f32,f32,f64>("mvmul<f32,f32,f64>",1);
    mvmul_test_generic::<f32,f64,f32>("mvmul<f32,f64,f32>",1);
    mvmul_test_generic::<f32,f64,f64>("mvmul<f32,f64,f64>",1);
    mvmul_test_generic::<f64,f32,f32>("mvmul<f64,f32,f32>",1);
    mvmul_test_generic::<f64,f32,f64>("mvmul<f64,f32,f64>",1);
    mvmul_test_generic::<f64,f64,f32>("mvmul<f64,f64,f32>",1);
    mvmul_test_generic::<f64,f64,f64>("mvmul<f64,f64,f64>",1);

}


