use core::hash::{Hash, Hasher};
use std::ffi::c_char;

use binaryninjacore_sys::*;

use crate::architecture::CoreArchitecture;
use crate::basicblock::BasicBlock;
use crate::disassembly::DisassemblySettings;
use crate::flowgraph::FlowGraph;
use crate::function::{Function, Location};
use crate::rc::{Array, CoreArrayProvider, CoreArrayProviderInner, Ref, RefCountable};
use crate::string::BnStrCompatible;
use crate::types::{
    Conf, PossibleValueSet, RegisterValue, SSAVariable, Type, UserVariableValues, Variable,
};

use super::{MediumLevelILBlock, MediumLevelILInstruction, MediumLevelILLiftedInstruction};

pub struct MediumLevelILFunction {
    pub(crate) handle: *mut BNMediumLevelILFunction,
}

unsafe impl Send for MediumLevelILFunction {}
unsafe impl Sync for MediumLevelILFunction {}

impl Eq for MediumLevelILFunction {}
impl PartialEq for MediumLevelILFunction {
    fn eq(&self, rhs: &Self) -> bool {
        self.get_function().eq(&rhs.get_function())
    }
}

impl Hash for MediumLevelILFunction {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.get_function().hash(state)
    }
}

impl MediumLevelILFunction {
    pub(crate) unsafe fn ref_from_raw(handle: *mut BNMediumLevelILFunction) -> Ref<Self> {
        debug_assert!(!handle.is_null());

        Self { handle }.to_owned()
    }

    pub fn instruction_at<L: Into<Location>>(&self, loc: L) -> Option<MediumLevelILInstruction> {
        let loc: Location = loc.into();
        let arch_handle = loc.arch.unwrap();

        let expr_idx =
            unsafe { BNMediumLevelILGetInstructionStart(self.handle, arch_handle.0, loc.addr) };

        if expr_idx >= self.instruction_count() {
            None
        } else {
            Some(MediumLevelILInstruction::new(self.to_owned(), expr_idx))
        }
    }

    pub fn instruction_from_idx(&self, expr_idx: usize) -> MediumLevelILInstruction {
        MediumLevelILInstruction::new(self.to_owned(), expr_idx)
    }

    pub fn lifted_instruction_from_idx(&self, expr_idx: usize) -> MediumLevelILLiftedInstruction {
        self.instruction_from_idx(expr_idx).lift()
    }

    pub fn instruction_from_instruction_idx(&self, instr_idx: usize) -> MediumLevelILInstruction {
        MediumLevelILInstruction::new(self.to_owned(), unsafe {
            BNGetMediumLevelILIndexForInstruction(self.handle, instr_idx)
        })
    }

    pub fn lifted_instruction_from_instruction_idx(
        &self,
        instr_idx: usize,
    ) -> MediumLevelILLiftedInstruction {
        self.instruction_from_instruction_idx(instr_idx).lift()
    }

    pub fn instruction_count(&self) -> usize {
        unsafe { BNGetMediumLevelILInstructionCount(self.handle) }
    }

    pub fn ssa_form(&self) -> MediumLevelILFunction {
        let ssa = unsafe { BNGetMediumLevelILSSAForm(self.handle) };
        assert!(!ssa.is_null());
        MediumLevelILFunction { handle: ssa }
    }

    pub fn get_function(&self) -> Ref<Function> {
        unsafe {
            let func = BNGetMediumLevelILOwnerFunction(self.handle);
            Function::from_raw(func)
        }
    }

    pub fn basic_blocks(&self) -> Array<BasicBlock<MediumLevelILBlock>> {
        let mut count = 0;
        let blocks = unsafe { BNGetMediumLevelILBasicBlockList(self.handle, &mut count) };
        let context = MediumLevelILBlock {
            function: self.to_owned(),
        };

        unsafe { Array::new(blocks, count, context) }
    }

    pub fn get_var_definitions<'a>(&'a self, var: &Variable) -> MediumLevelILInstructionList<'a> {
        let mut count = 0;
        let raw_instrs =
            unsafe { BNGetMediumLevelILVariableDefinitions(self.handle, &var.raw(), &mut count) };
        assert!(!raw_instrs.is_null());
        let instrs = unsafe { core::slice::from_raw_parts(raw_instrs, count) };
        MediumLevelILInstructionList {
            mlil: self,
            ptr: raw_instrs,
            instr_idxs: instrs.iter(),
        }
    }

    pub fn create_user_stack_var<'a, S: BnStrCompatible, C: Into<Conf<&'a Type>>>(
        self,
        offset: i64,
        var_type: C,
        name: S,
    ) {
        let var_type = var_type.into();
        let mut raw_var_type: BNTypeWithConfidence = var_type.into();
        let name = name.into_bytes_with_nul();
        unsafe {
            BNCreateUserStackVariable(
                self.get_function().handle,
                offset,
                &mut raw_var_type,
                name.as_ref().as_ptr() as *const c_char,
            )
        }
    }

    pub fn delete_user_stack_var(self, offset: i64) {
        unsafe { BNDeleteUserStackVariable(self.get_function().handle, offset) }
    }

    pub fn create_user_var<'a, S: BnStrCompatible, C: Into<Conf<&'a Type>>>(
        &self,
        var: &Variable,
        var_type: C,
        name: S,
        ignore_disjoint_uses: bool,
    ) {
        let var_type = var_type.into();
        let raw_var_type: BNTypeWithConfidence = var_type.into();
        let name = name.into_bytes_with_nul();
        unsafe {
            BNCreateUserVariable(
                self.get_function().handle,
                &var.raw(),
                &raw_var_type as *const _ as *mut _,
                name.as_ref().as_ptr() as *const _,
                ignore_disjoint_uses,
            )
        }
    }

    pub fn delete_user_var(&self, var: &Variable) {
        unsafe { BNDeleteUserVariable(self.get_function().handle, &var.raw()) }
    }

    pub fn is_var_user_defined(&self, var: &Variable) -> bool {
        unsafe { BNIsVariableUserDefined(self.get_function().handle, &var.raw()) }
    }

    /// Allows the user to specify a PossibleValueSet value for an MLIL
    /// variable at its definition site.
    ///
    /// .. warning:: Setting the variable value, triggers a reanalysis of the
    /// function and allows the dataflow to compute and propagate values which
    /// depend on the current variable. This implies that branch conditions
    /// whose values can be determined statically will be computed, leading to
    /// potential branch elimination at the HLIL layer.
    ///
    /// * `var` - Variable for which the value is to be set
    /// * `addr` - Address of the definition site of the variable
    /// * `value` - Informed value of the variable
    ///
    /// # Example
    /// ```no_run
    /// # use binaryninja::mlil::MediumLevelILFunction;
    /// # use binaryninja::types::PossibleValueSet;
    /// # let mlil_fun: MediumLevelILFunction = todo!();
    /// let (mlil_var, arch_addr, _val) = mlil_fun.user_var_values().all().next().unwrap();
    /// let def_address = arch_addr.address;
    /// let var_value = PossibleValueSet::ConstantValue{value: 5};
    /// mlil_fun.set_user_var_value(&mlil_var, def_address, var_value).unwrap();
    /// ```
    pub fn set_user_var_value(
        &self,
        var: &Variable,
        addr: u64,
        value: PossibleValueSet,
    ) -> Result<(), ()> {
        let Some(_def_site) = self
            .get_var_definitions(var)
            .find(|def| def.address == addr)
        else {
            // Error "No definition for Variable found at given address"
            return Err(());
        };
        let function = self.get_function();
        let def_site = BNArchitectureAndAddress {
            arch: function.arch().0,
            address: addr,
        };
        let value = value.into_raw();

        unsafe { BNSetUserVariableValue(function.handle, &var.raw(), &def_site, value.as_ffi()) }
        Ok(())
    }

    /// Clears a previously defined user variable value.
    ///
    /// * `var` - Variable for which the value was informed
    /// * `def_addr` - Address of the definition site of the variable
    pub fn clear_user_var_value(&self, var: &Variable, addr: u64) -> Result<(), ()> {
        let Some(_var_def) = self
            .get_var_definitions(var)
            .find(|site| site.address == addr)
        else {
            //error "Could not get definition for Variable"
            return Err(());
        };

        let function = self.get_function();
        let def_site = BNArchitectureAndAddress {
            arch: function.arch().0,
            address: addr,
        };

        unsafe { BNClearUserVariableValue(function.handle, &var.raw(), &def_site) };
        Ok(())
    }

    /// Returns a map of current defined user variable values.
    /// Returns a Map of user current defined user variable values and their definition sites.
    pub fn user_var_values(&self) -> UserVariableValues {
        let mut count = 0;
        let function = self.get_function();
        let var_values = unsafe { BNGetAllUserVariableValues(function.handle, &mut count) };
        assert!(!var_values.is_null());
        UserVariableValues {
            vars: core::ptr::slice_from_raw_parts(var_values, count),
        }
    }

    /// Clear all user defined variable values.
    pub fn clear_user_var_values(&self) -> Result<(), ()> {
        for (var, arch_and_addr, _value) in self.user_var_values().all() {
            self.clear_user_var_value(&var, arch_and_addr.address)?;
        }
        Ok(())
    }

    pub fn create_auto_stack_var<'a, T: Into<Conf<&'a Type>>, S: BnStrCompatible>(
        &self,
        offset: i64,
        var_type: T,
        name: S,
    ) {
        let var_type: Conf<&Type> = var_type.into();
        let mut var_type = var_type.into();
        let name = name.into_bytes_with_nul();
        let name_c_str = name.as_ref();
        unsafe {
            BNCreateAutoStackVariable(
                self.get_function().handle,
                offset,
                &mut var_type,
                name_c_str.as_ptr() as *const c_char,
            )
        }
    }

    pub fn delete_auto_stack_var(&self, offset: i64) {
        unsafe { BNDeleteAutoStackVariable(self.get_function().handle, offset) }
    }

    pub fn create_auto_var<'a, S: BnStrCompatible, C: Into<Conf<&'a Type>>>(
        &self,
        var: &Variable,
        var_type: C,
        name: S,
        ignore_disjoint_uses: bool,
    ) {
        let var_type: Conf<&Type> = var_type.into();
        let mut var_type = var_type.into();
        let name = name.into_bytes_with_nul();
        let name_c_str = name.as_ref();
        unsafe {
            BNCreateAutoVariable(
                self.get_function().handle,
                &var.raw(),
                &mut var_type,
                name_c_str.as_ptr() as *const c_char,
                ignore_disjoint_uses,
            )
        }
    }

    /// Returns a list of ILReferenceSource objects (IL xrefs or cross-references)
    /// that reference the given variable. The variable is a local variable that can be either on the stack,
    /// in a register, or in a flag.
    /// This function is related to get_hlil_var_refs(), which returns variable references collected
    /// from HLIL. The two can be different in several cases, e.g., multiple variables in MLIL can be merged
    /// into a single variable in HLIL.
    ///
    /// * `var` - Variable for which to query the xref
    ///
    /// # Example
    /// ```no_run
    /// # use binaryninja::mlil::MediumLevelILFunction;
    /// # use binaryninja::types::Variable;
    /// # let mlil_fun: MediumLevelILFunction = todo!();
    /// # let mlil_var: Variable = todo!();
    /// let instr = mlil_fun.var_refs(&mlil_var).get(0).expr();
    /// ```
    pub fn var_refs(&self, var: &Variable) -> Array<ILReferenceSource> {
        let mut count = 0;
        let refs = unsafe {
            BNGetMediumLevelILVariableReferences(
                self.get_function().handle,
                &mut var.raw(),
                &mut count,
            )
        };
        assert!(!refs.is_null());
        unsafe { Array::new(refs, count, self.to_owned()) }
    }

    /// Returns a list of variables referenced by code in the function ``func``,
    /// of the architecture ``arch``, and at the address ``addr``. If no function is specified, references from
    /// all functions and containing the address will be returned. If no architecture is specified, the
    /// architecture of the function will be used.
    /// This function is related to get_hlil_var_refs_from(), which returns variable references collected
    /// from HLIL. The two can be different in several cases, e.g., multiple variables in MLIL can be merged
    /// into a single variable in HLIL.
    ///
    /// * `addr` - virtual address to query for variable references
    /// * `length` - optional length of query
    /// * `arch` - optional architecture of query
    pub fn var_refs_from(
        &self,
        addr: u64,
        length: Option<u64>,
        arch: Option<CoreArchitecture>,
    ) -> Array<VariableReferenceSource> {
        let function = self.get_function();
        let arch = arch.unwrap_or_else(|| function.arch());
        let mut count = 0;

        let refs = if let Some(length) = length {
            unsafe {
                BNGetMediumLevelILVariableReferencesInRange(
                    function.handle,
                    arch.0,
                    addr,
                    length,
                    &mut count,
                )
            }
        } else {
            unsafe {
                BNGetMediumLevelILVariableReferencesFrom(function.handle, arch.0, addr, &mut count)
            }
        };
        assert!(!refs.is_null());
        unsafe { Array::new(refs, count, self.to_owned()) }
    }

    /// Current IL Address
    pub fn current_address(&self) -> u64 {
        unsafe { BNMediumLevelILGetCurrentAddress(self.handle) }
    }

    /// Set the current IL Address
    pub fn set_current_address(&self, value: u64, arch: Option<CoreArchitecture>) {
        let arch = arch
            .map(|x| x.0)
            .unwrap_or_else(|| self.get_function().arch().0);
        unsafe { BNMediumLevelILSetCurrentAddress(self.handle, arch, value) }
    }

    /// Returns the BasicBlock at the given MLIL `instruction`.
    pub fn basic_block_containing(
        &self,
        instruction: &MediumLevelILInstruction,
    ) -> Option<BasicBlock<MediumLevelILBlock>> {
        let index = instruction.index;
        let block = unsafe { BNGetMediumLevelILBasicBlockForInstruction(self.handle, index) };
        (!block.is_null()).then(|| unsafe {
            BasicBlock::from_raw(
                block,
                MediumLevelILBlock {
                    function: self.to_owned(),
                },
            )
        })
    }
    /// ends the function and computes the list of basic blocks.
    pub fn finalize(&self) {
        unsafe { BNFinalizeMediumLevelILFunction(self.handle) }
    }

    /// Generate SSA form given the current MLIL
    ///
    /// * `analyze_conditionals` - whether or not to analyze conditionals
    /// * `handle_aliases` - whether or not to handle aliases
    /// * `known_not_aliases` - optional list of variables known to be not aliased
    /// * `known_aliases` - optional list of variables known to be aliased
    pub fn generate_ssa_form(
        &self,
        analyze_conditionals: bool,
        handle_aliases: bool,
        known_not_aliases: impl IntoIterator<Item = Variable>,
        known_aliases: impl IntoIterator<Item = Variable>,
    ) {
        let mut known_not_aliases: Box<[_]> =
            known_not_aliases.into_iter().map(|x| x.raw()).collect();
        let mut known_aliases: Box<[_]> = known_aliases.into_iter().map(|x| x.raw()).collect();
        let (known_not_aliases_ptr, known_not_aliases_len) = if known_not_aliases.is_empty() {
            (core::ptr::null_mut(), 0)
        } else {
            (known_not_aliases.as_mut_ptr(), known_not_aliases.len())
        };
        let (known_aliases_ptr, known_aliases_len) = if known_not_aliases.is_empty() {
            (core::ptr::null_mut(), 0)
        } else {
            (known_aliases.as_mut_ptr(), known_aliases.len())
        };
        unsafe {
            BNGenerateMediumLevelILSSAForm(
                self.handle,
                analyze_conditionals,
                handle_aliases,
                known_not_aliases_ptr,
                known_not_aliases_len,
                known_aliases_ptr,
                known_aliases_len,
            )
        }
    }

    /// Gets the instruction that contains the given SSA variable's definition.
    ///
    /// Since SSA variables can only be defined once, this will return the single instruction where that occurs.
    /// For SSA variable version 0s, which don't have definitions, this will return None instead.
    pub fn ssa_variable_definition(&self, var: SSAVariable) -> Option<MediumLevelILInstruction> {
        let result = unsafe {
            BNGetMediumLevelILSSAVarDefinition(self.handle, &var.variable.raw(), var.version)
        };
        (result < self.instruction_count())
            .then(|| MediumLevelILInstruction::new(self.to_owned(), result))
    }

    pub fn ssa_memory_definition(&self, version: usize) -> Option<MediumLevelILInstruction> {
        let result = unsafe { BNGetMediumLevelILSSAMemoryDefinition(self.handle, version) };
        (result < self.instruction_count())
            .then(|| MediumLevelILInstruction::new(self.to_owned(), result))
    }

    ///Gets all the instructions that use the given SSA variable.
    pub fn ssa_variable_uses(&self, ssa_var: SSAVariable) -> Array<MediumLevelILInstruction> {
        let mut count = 0;
        let uses = unsafe {
            BNGetMediumLevelILSSAVarUses(
                self.handle,
                &ssa_var.variable.raw(),
                ssa_var.version,
                &mut count,
            )
        };
        assert!(!uses.is_null());
        unsafe { Array::new(uses, count, self.to_owned()) }
    }

    pub fn ssa_memory_uses(&self, version: usize) -> Array<MediumLevelILInstruction> {
        let mut count = 0;
        let uses = unsafe { BNGetMediumLevelILSSAMemoryUses(self.handle, version, &mut count) };
        assert!(!uses.is_null());
        unsafe { Array::new(uses, count, self.to_owned()) }
    }

    /// determines if `ssa_var` is live at any point in the function
    pub fn is_ssa_variable_live(&self, ssa_var: SSAVariable) -> bool {
        unsafe {
            BNIsMediumLevelILSSAVarLive(self.handle, &ssa_var.variable.raw(), ssa_var.version)
        }
    }

    pub fn variable_definitions(&self, variable: Variable) -> Array<MediumLevelILInstruction> {
        let mut count = 0;
        let defs = unsafe {
            BNGetMediumLevelILVariableDefinitions(self.handle, &variable.raw(), &mut count)
        };
        unsafe { Array::new(defs, count, self.to_owned()) }
    }

    pub fn variable_uses(&self, variable: Variable) -> Array<MediumLevelILInstruction> {
        let mut count = 0;
        let uses =
            unsafe { BNGetMediumLevelILVariableUses(self.handle, &variable.raw(), &mut count) };
        unsafe { Array::new(uses, count, self.to_owned()) }
    }

    /// Computes the list of instructions for which `var` is live.
    /// If `include_last_use` is false, the last use of the variable will not be included in the
    /// list (this allows for easier computation of overlaps in liveness between two variables).
    /// If the variable is never used, this function will return an empty list.
    ///
    /// `var` - the variable to query
    /// `include_last_use` - whether to include the last use of the variable in the list of instructions
    pub fn live_instruction_for_variable(
        &self,
        variable: Variable,
        include_last_user: bool,
    ) -> Array<MediumLevelILInstruction> {
        let mut count = 0;
        let uses = unsafe {
            BNGetMediumLevelILLiveInstructionsForVariable(
                self.handle,
                &variable.raw(),
                include_last_user,
                &mut count,
            )
        };
        unsafe { Array::new(uses, count, self.to_owned()) }
    }

    pub fn ssa_variable_value(&self, ssa_var: SSAVariable) -> RegisterValue {
        unsafe {
            BNGetMediumLevelILSSAVarValue(self.handle, &ssa_var.variable.raw(), ssa_var.version)
        }
        .into()
    }

    pub fn create_graph(&self, settings: Option<DisassemblySettings>) -> FlowGraph {
        let settings = settings.map(|x| x.handle).unwrap_or(core::ptr::null_mut());
        let graph = unsafe { BNCreateMediumLevelILFunctionGraph(self.handle, settings) };
        unsafe { FlowGraph::from_raw(graph) }
    }

    /// This gets just the MLIL variables - you may be interested in the union
    /// of [MediumLevelILFunction::aliased_variables] and
    /// [crate::function::Function::parameter_variables] for all the
    /// variables used in the function
    pub fn variables(&self) -> Array<Variable> {
        let mut count = 0;
        let uses = unsafe { BNGetMediumLevelILVariables(self.handle, &mut count) };
        unsafe { Array::new(uses, count, ()) }
    }

    /// This returns a list of Variables that are taken reference to and used
    /// elsewhere. You may also wish to consider [MediumLevelILFunction::variables]
    /// and [crate::function::Function::parameter_variables]
    pub fn aliased_variables(&self) -> Array<Variable> {
        let mut count = 0;
        let uses = unsafe { BNGetMediumLevelILAliasedVariables(self.handle, &mut count) };
        unsafe { Array::new(uses, count, ()) }
    }

    /// This gets just the MLIL SSA variables - you may be interested in the
    /// union of [MediumLevelILFunction::aliased_variables] and
    /// [crate::function::Function::parameter_variables] for all the
    /// variables used in the function.
    pub fn ssa_variables(&self) -> Array<Array<SSAVariable>> {
        let mut count = 0;
        let vars = unsafe { BNGetMediumLevelILVariables(self.handle, &mut count) };
        unsafe { Array::new(vars, count, self.to_owned()) }
    }
}

impl ToOwned for MediumLevelILFunction {
    type Owned = Ref<Self>;

    fn to_owned(&self) -> Self::Owned {
        unsafe { RefCountable::inc_ref(self) }
    }
}

unsafe impl RefCountable for MediumLevelILFunction {
    unsafe fn inc_ref(handle: &Self) -> Ref<Self> {
        Ref::new(Self {
            handle: BNNewMediumLevelILFunctionReference(handle.handle),
        })
    }

    unsafe fn dec_ref(handle: &Self) {
        BNFreeMediumLevelILFunction(handle.handle);
    }
}

impl core::fmt::Debug for MediumLevelILFunction {
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
        write!(f, "<mlil func handle {:p}>", self.handle)
    }
}

#[derive(Clone, Debug)]
pub struct MediumLevelILInstructionList<'a> {
    mlil: &'a MediumLevelILFunction,
    ptr: *mut usize,
    instr_idxs: core::slice::Iter<'a, usize>,
}

impl Drop for MediumLevelILInstructionList<'_> {
    fn drop(&mut self) {
        unsafe { BNFreeILInstructionList(self.ptr) };
    }
}

impl Iterator for MediumLevelILInstructionList<'_> {
    type Item = MediumLevelILInstruction;

    fn next(&mut self) -> Option<Self::Item> {
        self.instr_idxs
            .next()
            .map(|i| self.mlil.instruction_from_instruction_idx(*i))
    }
}

impl DoubleEndedIterator for MediumLevelILInstructionList<'_> {
    fn next_back(&mut self) -> Option<Self::Item> {
        self.instr_idxs
            .next_back()
            .map(|i| self.mlil.instruction_from_instruction_idx(*i))
    }
}

impl ExactSizeIterator for MediumLevelILInstructionList<'_> {}
impl core::iter::FusedIterator for MediumLevelILInstructionList<'_> {}

/////////////////////////
// FunctionGraphType

pub type FunctionGraphType = binaryninjacore_sys::BNFunctionGraphType;

/////////////////////////
// ILReferenceSource

pub struct ILReferenceSource {
    mlil: Ref<MediumLevelILFunction>,
    _func: Ref<Function>,
    _arch: CoreArchitecture,
    addr: u64,
    type_: FunctionGraphType,
    expr_id: usize,
}

impl ILReferenceSource {
    unsafe fn from_raw(value: BNILReferenceSource, mlil: Ref<MediumLevelILFunction>) -> Self {
        Self {
            mlil,
            _func: Function::from_raw(value.func),
            _arch: CoreArchitecture::from_raw(value.arch),
            addr: value.addr,
            type_: value.type_,
            expr_id: value.exprId,
        }
    }
    pub fn addr(&self) -> u64 {
        self.addr
    }
    pub fn graph_type(&self) -> FunctionGraphType {
        self.type_
    }
    pub fn expr(&self) -> MediumLevelILInstruction {
        self.mlil.instruction_from_idx(self.expr_id)
    }
}

impl CoreArrayProvider for ILReferenceSource {
    type Raw = BNILReferenceSource;
    type Context = Ref<MediumLevelILFunction>;
    type Wrapped<'a> = Self;
}
unsafe impl CoreArrayProviderInner for ILReferenceSource {
    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
        BNFreeILReferences(raw, count)
    }
    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
        Self::from_raw(*raw, context.to_owned())
    }
}

/////////////////////////
// VariableReferenceSource

pub struct VariableReferenceSource {
    var: Variable,
    source: ILReferenceSource,
}

impl VariableReferenceSource {
    pub fn variable(&self) -> &Variable {
        &self.var
    }
    pub fn source(&self) -> &ILReferenceSource {
        &self.source
    }
}

impl CoreArrayProvider for VariableReferenceSource {
    type Raw = BNVariableReferenceSource;
    type Context = Ref<MediumLevelILFunction>;
    type Wrapped<'a> = Self;
}

unsafe impl CoreArrayProviderInner for VariableReferenceSource {
    unsafe fn free(raw: *mut Self::Raw, count: usize, _context: &Self::Context) {
        BNFreeVariableReferenceSourceList(raw, count)
    }
    unsafe fn wrap_raw<'a>(raw: &'a Self::Raw, context: &'a Self::Context) -> Self::Wrapped<'a> {
        Self {
            var: Variable::from_raw(raw.var),
            source: ILReferenceSource::from_raw(raw.source, context.to_owned()),
        }
    }
}
