use anyhow::{Context, Result};
use paste::paste;
use std::{collections::HashMap, mem::offset_of, sync::LazyLock};

use crate::{
    instruction_set::{
        Instruction, InstructionsBuilder, instruction_data::InstructionDataBuilder, isa::*,
    },
    utility::pack_float_in_i32,
};
use serde_json::Value;
type FunctorType = fn(&mut InstructionsBuilder, &mut InstructionDataBuilder, &Value) -> Result<()>;

macro_rules! add_to_json_map {
    ($storage:ident, $id:ident) => {
        paste! {
        $storage.insert(stringify!($id).to_string(), [<json_to_ $id>] as FunctorType );
        }
    };
}

static SIMD: LazyLock<HashMap<String, FunctorType>> = LazyLock::new(|| {
    let mut storage = HashMap::new();
    add_to_json_map!(storage, sldi);
    add_to_json_map!(storage, sld);
    add_to_json_map!(storage, sadd);
    add_to_json_map!(storage, ssub);
    add_to_json_map!(storage, smul);
    add_to_json_map!(storage, saddi);
    add_to_json_map!(storage, setbw);
    add_to_json_map!(storage, mvmul);
    add_to_json_map!(storage, vvadd);
    add_to_json_map!(storage, vvsub);
    add_to_json_map!(storage, vvmul);
    add_to_json_map!(storage, vvdmul);
    add_to_json_map!(storage, vvmax);
    add_to_json_map!(storage, vvsll);
    add_to_json_map!(storage, vvsra);
    add_to_json_map!(storage, vrelu);
    add_to_json_map!(storage, vtanh);
    add_to_json_map!(storage, vsigm);
    add_to_json_map!(storage, vmv);
    add_to_json_map!(storage, vrsu);
    add_to_json_map!(storage, vrsl);
    add_to_json_map!(storage, ld);
    add_to_json_map!(storage, st);
    add_to_json_map!(storage, lldi);
    add_to_json_map!(storage, lmv);
    add_to_json_map!(storage, send);
    add_to_json_map!(storage, recv);
    add_to_json_map!(storage, wait);
    add_to_json_map!(storage, sync);
    storage
});

fn json_to_offset(json: &Value) -> (i32, i32) {
    let offset_value = json.get("offset_value").unwrap().as_i64().unwrap() as i32;
    let offset_select = json.get("offset_select").unwrap().as_i64().unwrap() as i32;
    (offset_select, offset_value)
}

pub fn json_to_instruction(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) {
    let json_struct = json.as_object().expect("Not an object");
    let op = json_struct.get("op").unwrap().as_str().unwrap();
    SIMD.get(op)
        .unwrap_or_else(|| panic!("Operation not found {}", op))(
        inst_builder,
        inst_data_builder,
        json,
    )
    .unwrap();
}

macro_rules! json_str {
    ($json:ident , $value:literal) => {
        $json
            .get($value)
            .context(concat![$value, " field not present"])?
            .as_str()
            .context(concat![$value, " field not str"])?
    };
}

macro_rules! json_i64 {
    ($json:ident , $value:literal) => {
        $json
            .get($value)
            .context(concat![$value, " field not present"])?
            .as_i64()
            .context(concat![$value, " field not i64"])?
    };
}

fn json_to_sldi(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("sldi", json_str!(json, "op"));
    let rd = json_i64!(json, "rd");
    let imm = json_i64!(json, "imm");
    inst_data_builder.set_rd(rd as i32);
    inst_data_builder.set_imm(imm as i32);
    inst_builder.make_inst(sldi, inst_data_builder.build());
    Ok(())
}

fn json_to_sld(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

fn json_to_sadd(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

fn json_to_ssub(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

fn json_to_smul(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

fn json_to_saddi(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

///////////////////////////////////////////////////////////////
/////////////////Matrix/vector Instructions////////////////////
///////////////////////////////////////////////////////////////

fn json_to_setbw(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("setbw", json_str!(json, "op"));
    let ibiw = json_i64!(json, "ibiw");
    let obiw = json_i64!(json, "obiw");
    inst_data_builder.set_ibiw(ibiw as i32);
    inst_data_builder.set_obiw(obiw as i32);
    inst_builder.make_inst(setbw, inst_data_builder.build());
    Ok(())
}

fn json_to_mvmul(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("mvmul", json_str!(json, "op"));
    let group = json_i64!(json, "group") as i32;
    let relu = json_i64!(json, "relu") as i32;
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let mbiw = json_i64!(json, "mbiw") as i32;
    inst_data_builder
        .set_imm_group(group)
        .set_imm_relu(relu)
        .set_rd(rd)
        .set_r1(rs1)
        .set_mbiw(mbiw);
    inst_builder.make_inst(mvmul, inst_data_builder.build());
    Ok(())
}

fn json_to_vvadd(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("vvadd", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let rs2 = json_i64!(json, "rs2") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_r2(rs2)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(vvadd, inst_data_builder.build());
    Ok(())
}

fn json_to_vvsub(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("vvsub", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let rs2 = json_i64!(json, "rs2") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_r2(rs2)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(vvsub, inst_data_builder.build());
    Ok(())
}

fn json_to_vvmul(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("vvmul", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let rs2 = json_i64!(json, "rs2") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_r2(rs2)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(vvmul, inst_data_builder.build());
    Ok(())
}

fn json_to_vvdmul(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("vvdmul", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let rs2 = json_i64!(json, "rs2") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_r2(rs2)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(vvdmul, inst_data_builder.build());
    Ok(())
}

fn json_to_vvmax(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("vvmax", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let rs2 = json_i64!(json, "rs2") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_r2(rs2)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(vvmax, inst_data_builder.build());
    Ok(())
}

fn json_to_vvsll(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

fn json_to_vvsra(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

fn json_to_vavg(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("vavg", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let rs2 = json_i64!(json, "rs2") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_r2(rs2)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(vavg, inst_data_builder.build());
    Ok(())
}

fn json_to_vrelu(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("vrelu", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(vrelu, inst_data_builder.build());
    Ok(())
}

fn json_to_vtanh(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("vtanh", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(vtanh, inst_data_builder.build());
    Ok(())
}

fn json_to_vsigm(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("vsigm", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(vsigm, inst_data_builder.build());
    Ok(())
}

fn json_to_vmv(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

fn json_to_vrsu(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

fn json_to_vrsl(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

///////////////////////////////////////////////////////////////
///Communication/synchronization Instructions/////////////////
///////////////////////////////////////////////////////////////
fn json_to_ld(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("ld", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let len = json_i64!(json, "size") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(ld, inst_data_builder.build());
    Ok(())
}

fn json_to_st(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("st", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let len = json_i64!(json, "size") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(st, inst_data_builder.build());
    Ok(())
}

fn json_to_lldi(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("lldi", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let imm = json_i64!(json, "imm") as f32;
    let imm = pack_float_in_i32(imm);
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_imm(imm)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(lldi, inst_data_builder.build());
    Ok(())
}

fn json_to_lmv(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("lmv", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let rs1 = json_i64!(json, "rs1") as i32;
    let len = json_i64!(json, "len") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_r1(rs1)
        .set_imm_len(len)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(lmv, inst_data_builder.build());
    Ok(())
}

fn json_to_send(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("send", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let core = json_i64!(json, "core") as i32;
    let size = json_i64!(json, "size") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_imm_core(core)
        .set_imm_len(size)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(send, inst_data_builder.build());
    Ok(())
}

fn json_to_recv(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    let json = json.as_object().expect("Not an object");
    assert_eq!("recv", json_str!(json, "op"));
    let rd = json_i64!(json, "rd") as i32;
    let core = json_i64!(json, "core") as i32;
    let size = json_i64!(json, "size") as i32;
    let (offset_select, offset_value) = json_to_offset(json.get("offset").unwrap());
    inst_data_builder
        .set_rd(rd)
        .set_imm_core(core)
        .set_imm_len(size)
        .set_offset_select(offset_select)
        .set_offset_value(offset_value);
    inst_builder.make_inst(recv, inst_data_builder.build());
    Ok(())
}

fn json_to_wait(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

fn json_to_sync(
    inst_builder: &mut InstructionsBuilder,
    inst_data_builder: &mut InstructionDataBuilder,
    json: &Value,
) -> Result<()> {
    todo!("Not present in the compiler");
    Ok(())
}

#[cfg(test)]
mod test {
    use serde_json::Value;

    use crate::{
        instruction_set::{InstructionsBuilder, instruction_data::InstructionDataBuilder},
        json_to_instruction::json_isa::json_to_instruction,
    };

    #[test]
    #[should_panic(expected = "Operation not found bazza")]
    fn test_op_not_present() {
        let json = r#"{"imm":15168,"op":"bazza","rd":1}"#;
        let json = serde_json::from_str(json).unwrap();
        let mut instruction_builder = InstructionsBuilder::new();
        let mut inst_data_builder = InstructionDataBuilder::new();
        json_to_instruction(&mut instruction_builder, &mut inst_data_builder, &json);
    }

    #[test]
    fn test_json_sldi() {
        let json = r#"{"imm":15168,"op":"sldi","rd":1}"#;
        let json = serde_json::from_str(json).unwrap();
        let mut instruction_builder = InstructionsBuilder::new();
        let mut inst_data_builder = InstructionDataBuilder::new();
        json_to_instruction(&mut instruction_builder, &mut inst_data_builder, &json);
    }
}
