use crate::{
    cpu::CPU,
    instruction_set::{
        instruction_data::InstructionData,
        isa::{dispatch_simd, functor_to_name, is_setbw, isa_simd},
    },
};
use anyhow::{Context, Result};
use std::mem::swap;
pub mod instruction_data;
pub mod isa;
pub mod helper;

#[derive(Clone, Copy, Debug)]
pub struct Instruction {
    pub data: InstructionData,
    functor: InstructionType,
}

#[derive(Debug, Clone, Copy, Default)]
pub enum InstructionStatus {
    Completed,
    Waiting(InstructionData),
    Sending(InstructionData),
    Reciving(InstructionData),
    Sync(InstructionData),
    #[default]
    NotExecuted,
}

impl InstructionStatus {
    #[must_use]
    pub fn is_completed(&self) -> bool {
        matches!(self, Self::Completed)
    }
}

impl Instruction {
    fn new(data: InstructionData, functor: InstructionType) -> Self {
        Self { data, functor }
    }

    pub fn execute(&self, cpu: &mut CPU) -> InstructionStatus {
        (self.functor)(cpu, self.data)
            .with_context(|| format!("Instruction: {}", functor_to_name(self.functor as usize)))
            .with_context(|| format!("Error in core: {}", self.data.core_indx() - 1))
            .unwrap()
    }

    pub(crate) fn dump(&self) {
        eprintln!("\t{}", functor_to_name(self.functor as usize));
    }
}

pub type Instructions = Vec<Instruction>;
pub type InstructionType = fn(&mut CPU, InstructionData) -> Result<InstructionStatus>;

#[derive(Debug, Clone, Copy, Default)]
pub struct VectorBitWith {
    pub vector_input_bitwith: usize,
    pub vector_output_bitwith: usize,
}

/// Support for the
/// setbw ibiw, obiw
/// Set the bit-widths of each element for input vectors and output vectors. Related vector instructions
/// use the configured bit-widths. Once setbw is caled, all subsequent related vector instructions will
/// use the configured bit-widths, until a new setbw is called. Once ibiw and obiw are set, ibyw and
/// obyw are also set accordingly by the hardware.
/// If the hardware does not support variable bit-width, this instruction is invalid and the matrix/vector
/// instructions use the fixed bit-width of the hardware.
pub struct InstructionsBuilder {
    vector_bit_with: VectorBitWith,
    instructions: Instructions,
}

impl Default for InstructionsBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl InstructionsBuilder {
    pub fn new() -> Self {
        Self {
            vector_bit_with: VectorBitWith {
                vector_input_bitwith: 32,
                vector_output_bitwith: 32,
            },
            instructions: Instructions::new(),
        }
    }

    pub fn make_inst(&mut self, functor: InstructionType, data: InstructionData) {
        if is_setbw(functor) {
            let (ibiw, obiw) = data.get_ibiw_obiw();
            self.vector_bit_with.vector_input_bitwith =
                ibiw.try_into().expect("ibiw can not be negative");
            self.vector_bit_with.vector_output_bitwith =
                obiw.try_into().expect("obiw can not be negative");
            return;
        }

        if (isa_simd(functor)) {
            self.instructions.push(Instruction::new(
                data,
                dispatch_simd(functor, self.vector_bit_with).unwrap(),
            ))
        } else {
            self.instructions.push(Instruction::new(data, functor))
        }
    }

    pub fn build(&mut self) -> Instructions {
        let mut inst = Instructions::new();
        swap(&mut self.instructions, &mut inst);
        inst
    }
}
