use crate::interpreter::{
    consts::HandledConst,
    data::Data,
    supported_type::{ArrayLen, SliceLen, SupportedType, TypedStruct},
    Interpreter,
};
use num_traits::ToPrimitive;

impl Interpreter<'_> {
    pub fn refine_struct_type(
        &self,
        ty: &mut TypedStruct,
        may_return_err: bool,
    ) -> Result<(), &'static str> {
        for b in ty.types_mut().iter_mut() {
            self.refine_type(b, may_return_err)?;
        }
        Ok(())
    }
    pub fn refine_arr_len(
        &self,
        arr_len: &mut ArrayLen,
        may_return_err: bool,
    ) -> Result<(), &'static str> {
        match arr_len {
            ArrayLen::Computed(_) => {}
            ArrayLen::ConstVar(a) => match self.generic_context.get_const_var(*a) {
                None => {
                    if may_return_err {
                        return Err("Array len not set");
                    }
                }
                Some(HandledConst::Computed(a)) => {
                    let Data::Val(val) = self.memory[a] else {
                        return Err("Array len not an integer.");
                    };
                    let (len, len_bis) = val.val().bounds().to_signed_number_pair();
                    if len != len_bis {
                        return Err("Array len not a constant.");
                    }
                    let Some(len) = len.to_usize() else {
                        return Err("Array len too small or too big.");
                    };
                    *arr_len = ArrayLen::Computed(len);
                }
                Some(HandledConst::Generic(idx)) => {
                    *arr_len = ArrayLen::ConstVar(idx);
                    self.refine_arr_len(arr_len, may_return_err)?;
                }
            },
        }
        Ok(())
    }
    fn refine_slice_len(
        &self,
        slice_len: &mut SliceLen,
        may_return_err: bool,
    ) -> Result<(), &'static str> {
        match slice_len {
            SliceLen::None => {}
            SliceLen::NArcisValues(ty) => {
                self.refine_type(ty, may_return_err)?;
            }
            SliceLen::NPackedArcisValues(ty) => {
                self.refine_type(ty, may_return_err)?;
            }
        }
        Ok(())
    }

    pub fn refine_type(
        &self,
        ty: &mut SupportedType,
        may_return_err: bool,
    ) -> Result<(), &'static str> {
        match ty {
            SupportedType::Unit => {}
            SupportedType::FieldValue(_) => {}
            SupportedType::Bool => {}
            SupportedType::Curve(_) => {}
            SupportedType::Struct(s) => self.refine_struct_type(s, may_return_err)?,
            SupportedType::Ref(a) => self.refine_type(a, may_return_err)?,
            SupportedType::Array(a, arr_len) => {
                self.refine_type(a, may_return_err)?;
                self.refine_arr_len(arr_len, may_return_err)?;
            }
            SupportedType::Tuple(a) => {
                for b in a.iter_mut() {
                    self.refine_type(b, may_return_err)?;
                }
            }
            SupportedType::Slice(a, s) => {
                self.refine_type(a, may_return_err)?;
                self.refine_slice_len(s, may_return_err)?;
            }
            SupportedType::Var(i) => {
                let level = self.generic_context.get_type_var(*i);
                match level {
                    None => {
                        if may_return_err {
                            return Err("Unknown generic type");
                        }
                    }
                    Some(s) => {
                        *ty = s;
                        self.refine_type(ty, may_return_err)?;
                    }
                }
            }
            SupportedType::ImplAny => {
                if may_return_err {
                    return Err("Undefined impl type");
                }
            }
            SupportedType::Fn => {}
        };
        Ok(())
    }
}
