use anyhow::Context;

use crate::{
    CoreInstruction, cpu::CPU, instruction_set::InstructionStatus, tracing::TRACER,
    utility::add_offset_rd,
};

#[derive(Debug, Clone, Copy)]
struct SendRecvInfo {
    internal_core: usize,
    external_core: usize,
    address: usize,
    size: usize,
}

impl SendRecvInfo {
    fn new(internal_core: usize, external_core: usize, address: usize, size: usize) -> Self {
        Self {
            internal_core,
            external_core,
            address,
            size,
        }
    }
}

#[derive(Debug, Clone)]
pub struct SendRecv {
    sending: Box<[Option<SendRecvInfo>]>,
    receiving: Box<[Option<SendRecvInfo>]>,
}

impl SendRecv {
    pub fn new(num_core: usize) -> Self {
        let sending = [Option::None].repeat(num_core);
        let reciving = [Option::None].repeat(num_core);
        Self {
            sending: sending.into(),
            receiving: reciving.into(),
        }
    }
}

pub fn handle_send_recv(
    cpu: &mut CPU,
    core_instructions: &mut [CoreInstruction],
    send_recv: &mut SendRecv,
    core_result: InstructionStatus,
) -> bool {
    let transfer_memory = |cpu: &mut CPU,
                           core_instructions: &mut [CoreInstruction],
                           sender: Option<SendRecvInfo>,
                           receiver: Option<SendRecvInfo>| {
        if let Some(sender) = sender
            && let Some(receiver) = receiver
            && sender.internal_core == receiver.external_core
            && receiver.internal_core == sender.external_core
        {
            let [sender_core, reciver_core] =
                cpu.get_multiple_cores([sender.internal_core, receiver.internal_core]);
            let memory = sender_core
                .load::<u8>(sender.address, sender.size)
                .with_context(|| {
                    format!(
                        "Sender crash tranfering memroy from {} with size {}",
                        sender.address, sender.size
                    )
                })
                .unwrap();
            reciver_core.execute_store(receiver.address, memory[0]);
            {
                let sender = &mut core_instructions[sender.internal_core];
                let pc = sender.program_counter;
                let inst = sender.instructions.get(pc).unwrap();
                let data = inst.data;
                TRACER.lock().unwrap().post_send(cpu, data);
            }
            {
                let recv = &mut core_instructions[receiver.internal_core];
                let pc = recv.program_counter;
                let inst = recv.instructions.get(pc).unwrap();
                let data = inst.data;
                TRACER.lock().unwrap().post_recv(cpu, data);
            }
            core_instructions[sender.internal_core].program_counter += 1;
            core_instructions[receiver.internal_core].program_counter += 1;

            return true;
        }
        false
    };

    match core_result {
        InstructionStatus::Sending(instruction_data) => {
            let (core_idx, imm_core) = instruction_data.get_core_immcore();
            let r1 = instruction_data.r1();
            let imm_len = instruction_data
                .imm_len()
                .try_into()
                .expect("imm_len can not be negative");
            let offset_value = instruction_data.offset_value();
            let core = cpu.core(core_idx);
            let r1_val = core.register(r1);
            let address = add_offset_rd(r1_val, 1, offset_value);
            let sender: usize = core_idx.try_into().expect("core can not be negative");
            assert_ne!(sender, 0, "Host can not use send");
            let receiver: usize = imm_core.try_into().expect("imm_core can not be negative");
            assert_ne!(receiver, 0, "Host can not use receive");
            send_recv.sending[sender] = Some(SendRecvInfo::new(sender, receiver, address, imm_len));
            transfer_memory(
                cpu,
                core_instructions,
                send_recv.sending[sender],
                send_recv.receiving[receiver],
            )
        }
        InstructionStatus::Reciving(instruction_data) => {
            let (core_idx, imm_core) = instruction_data.get_core_immcore();
            let rd = instruction_data.rd();
            let imm_len = instruction_data
                .imm_len()
                .try_into()
                .expect("imm_len can not be negative");
            let offset_value = instruction_data.offset_value();
            let core = cpu.core(core_idx);
            let rd_val = core.register(rd);
            let address = add_offset_rd(rd_val, 4, offset_value);
            let receiver: usize = core_idx.try_into().expect("core can not be negative");
            assert_ne!(receiver, 0, "Host can not use receive");
            let sender: usize = imm_core.try_into().expect("imm_core can not be negative");
            assert_ne!(sender, 0, "Host can not use send");
            send_recv.receiving[receiver] =
                Some(SendRecvInfo::new(receiver, sender, address, imm_len));
            transfer_memory(
                cpu,
                core_instructions,
                send_recv.sending[sender],
                send_recv.receiving[receiver],
            )
        }
        _ => false,
    }
}
