use crate::obj::field_access_to_string;
use crate::prelude::*;
use std::collections::HashMap;

fn remove_param_names_from_param_to_arg_map(
    param_to_arg_map: &HashMap<String, Obj>,
    param_names: &Vec<String>,
) -> HashMap<String, Obj> {
    let mut filtered_param_to_arg_map = HashMap::new();
    for (param_name, arg) in param_to_arg_map.iter() {
        if !param_names.contains(param_name) {
            filtered_param_to_arg_map.insert(param_name.clone(), arg.clone());
        }
    }
    filtered_param_to_arg_map
}

impl Runtime {
    pub fn inst_obj(
        &self,
        obj: &Obj,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        match obj {
            Obj::Identifier(inner) => self.inst_identifier(inner, param_to_arg_map),
            Obj::IdentifierWithMod(inner) => self.inst_identifier_with_mod(inner, param_to_arg_map),
            Obj::FieldAccess(inner) => self.inst_field_access(inner, param_to_arg_map),
            Obj::FieldAccessWithMod(inner) => {
                self.inst_field_access_with_mod(inner, param_to_arg_map)
            }
            Obj::FnObj(inner) => self.inst_fn_obj(inner, param_to_arg_map),
            Obj::Number(inner) => self.inst_number(inner, param_to_arg_map),
            Obj::Add(inner) => self.inst_add(inner, param_to_arg_map),
            Obj::Sub(inner) => self.inst_sub(inner, param_to_arg_map),
            Obj::Mul(inner) => self.inst_mul(inner, param_to_arg_map),
            Obj::Div(inner) => self.inst_div(inner, param_to_arg_map),
            Obj::Mod(inner) => self.inst_mod(inner, param_to_arg_map),
            Obj::Pow(inner) => self.inst_pow(inner, param_to_arg_map),
            Obj::Union(inner) => self.inst_union(inner, param_to_arg_map),
            Obj::Intersect(inner) => self.inst_intersect(inner, param_to_arg_map),
            Obj::SetMinus(inner) => self.inst_set_minus(inner, param_to_arg_map),
            Obj::SetDiff(inner) => self.inst_set_diff(inner, param_to_arg_map),
            Obj::Cup(inner) => self.inst_cup(inner, param_to_arg_map),
            Obj::Cap(inner) => self.inst_cap(inner, param_to_arg_map),
            Obj::ListSet(inner) => self.inst_list_set(inner, param_to_arg_map),
            Obj::SetBuilder(inner) => self.inst_set_builder(inner, param_to_arg_map),
            Obj::FnSet(inner) => self.inst_fn_set_with_params(inner, param_to_arg_map),
            Obj::StandardSet(standard_set) => self.inst_standard_set(standard_set),
            Obj::Cart(inner) => self.inst_cart(inner, param_to_arg_map),
            Obj::CartDim(inner) => self.inst_cart_dim(inner, param_to_arg_map),
            Obj::Proj(inner) => self.inst_proj(inner, param_to_arg_map),
            Obj::TupleDim(inner) => self.inst_tuple_dim(inner, param_to_arg_map),
            Obj::Tuple(inner) => self.inst_tuple(inner, param_to_arg_map),
            Obj::Count(inner) => self.inst_count(inner, param_to_arg_map),
            Obj::Range(inner) => self.inst_range(inner, param_to_arg_map),
            Obj::ClosedRange(inner) => self.inst_closed_range(inner, param_to_arg_map),
            Obj::PowerSet(inner) => self.inst_power_set(inner, param_to_arg_map),
            Obj::Choose(inner) => self.inst_choose(inner, param_to_arg_map),
            Obj::ObjAtIndex(inner) => self.inst_obj_at_index(inner, param_to_arg_map),
            Obj::FamilyObj(family) => {
                let mut params = Vec::with_capacity(family.params.len());
                for p in family.params.iter() {
                    params.push(self.inst_obj(p, param_to_arg_map)?);
                }
                Ok(Obj::FamilyObj(FamilyObj {
                    name: family.name.clone(),
                    params,
                }))
            }
            Obj::StructObj(s) => {
                let mut args = Vec::with_capacity(s.args.len());
                for a in s.args.iter() {
                    args.push(self.inst_obj(a, param_to_arg_map)?);
                }
                Ok(Obj::StructObj(StructObj {
                    name: s.name.clone(),
                    args,
                }))
            }
        }
    }

    pub fn inst_atom(
        &self,
        atom: &Atom,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Atom, RuntimeError> {
        match atom {
            Atom::Identifier(identifier) => {
                match self.inst_identifier(identifier, param_to_arg_map)? {
                    Obj::Identifier(new_identifier) => Ok(Atom::Identifier(new_identifier)),
                    Obj::IdentifierWithMod(new_identifier_with_mod) => {
                        Ok(Atom::IdentifierWithMod(new_identifier_with_mod))
                    }
                    Obj::FieldAccess(new_field_access) => Ok(Atom::FieldAccess(new_field_access)),
                    Obj::FieldAccessWithMod(new_field_access_with_mod) => {
                        Ok(Atom::FieldAccessWithMod(new_field_access_with_mod))
                    }
                    _ => Ok(Atom::Identifier(identifier.clone())),
                }
            }
            Atom::IdentifierWithMod(identifier_with_mod) => {
                Ok(Atom::IdentifierWithMod(identifier_with_mod.clone()))
            }
            Atom::FieldAccess(field_access) => {
                match self.inst_field_access(field_access, param_to_arg_map)? {
                    Obj::Identifier(new_identifier) => Ok(Atom::Identifier(new_identifier)),
                    Obj::IdentifierWithMod(new_identifier_with_mod) => {
                        Ok(Atom::IdentifierWithMod(new_identifier_with_mod))
                    }
                    Obj::FieldAccess(new_field_access) => Ok(Atom::FieldAccess(new_field_access)),
                    Obj::FieldAccessWithMod(new_field_access_with_mod) => {
                        Ok(Atom::FieldAccessWithMod(new_field_access_with_mod))
                    }
                    _ => Ok(Atom::FieldAccess(field_access.clone())),
                }
            }
            Atom::FieldAccessWithMod(field_access_with_mod) => {
                Ok(Atom::FieldAccessWithMod(field_access_with_mod.clone()))
            }
        }
    }

    pub fn inst_identifier(
        &self,
        identifier: &Identifier,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(match param_to_arg_map.get(&identifier.name) {
            Some(obj) => obj.clone(),
            None => Obj::Identifier(identifier.clone()),
        })
    }

    pub fn inst_identifier_with_mod(
        &self,
        identifier_with_mod: &IdentifierWithMod,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        _ = param_to_arg_map;
        Ok(Obj::IdentifierWithMod(identifier_with_mod.clone()))
    }

    pub fn inst_field_access(
        &self,
        field_access: &FieldAccess,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let dotted = field_access_to_string(&field_access.name, &field_access.field);
        if let Some(obj) = param_to_arg_map.get(&dotted) {
            return Ok(obj.clone());
        }
        match param_to_arg_map.get(&field_access.name) {
            Some(Obj::Identifier(identifier)) => Ok(Obj::FieldAccess(FieldAccess {
                name: identifier.name.clone(),
                field: field_access.field.clone(),
            })),
            Some(Obj::IdentifierWithMod(identifier_with_mod)) => {
                Ok(Obj::FieldAccessWithMod(FieldAccessWithMod {
                    mod_name: identifier_with_mod.mod_name.clone(),
                    name: identifier_with_mod.name.clone(),
                    field: field_access.field.clone(),
                }))
            }
            Some(obj) => {
                let tuple_opt = match obj {
                    Obj::Tuple(t) => Some(t.clone()),
                    _ => self.get_obj_equal_to_tuple(&obj.to_string()),
                };
                match tuple_opt {
                    Some(t) => self.inst_field_access_on_struct_tuple(field_access, &t),
                    None => {
                        return Err(
                            InstantiateRuntimeError(RuntimeErrorStruct::new(
                                None,
                                format!(
                                    "field `{}` of struct `{}` is not a tuple",
                                    field_access.field, field_access.name
                                ),
                                default_line_file(),
                                None,
                            ))
                            .into(),
                        )
                    }
                }
            }
            None => Ok(Obj::FieldAccess(field_access.clone())),
        }
    }

    fn inst_field_access_on_struct_tuple(
        &self,
        field_access: &FieldAccess,
        tuple: &Tuple,
    ) -> Result<Obj, RuntimeError> {
        let Some(def) = self.get_definition_of_struct_where_object_satisfies(
            &IdentifierOrIdentifierWithMod::Identifier(Identifier::new(field_access.name.clone())),
        ) else {
            return Err(
                InstantiateRuntimeError(RuntimeErrorStruct::new(
                    None,
                    format!("struct `{}` is not defined", field_access.name),
                    default_line_file(),
                    None,
                ))
                .into(),
            );
        };

        let Some(field_index) = def
            .fields
            .iter()
            .position(|(fname, _)| fname == &field_access.field)
        else {
            return Err(
                InstantiateRuntimeError(RuntimeErrorStruct::new(
                    None,
                    format!(
                        "field `{}` of struct `{}` is not defined",
                        field_access.field, field_access.name
                    ),
                    default_line_file(),
                    None,
                ))
                .into(),
            );
        };

        let tuple_index = field_index + def.number_of_params();
        let Some(component) = tuple.args.get(tuple_index) else {
            return Err(
                InstantiateRuntimeError(RuntimeErrorStruct::new(
                    None,
                    format!("field `{}` of struct `{}` is at index {}, but tuple for `{}` has only {} component(s)", field_access.field, field_access.name, tuple_index, field_access.name, tuple.args.len()),
                    default_line_file(),
                    None,
                ))
                .into(),
            );
        };

        Ok((**component).clone())
    }

    pub fn inst_field_access_with_mod(
        &self,
        field_access_with_mod: &FieldAccessWithMod,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        _ = param_to_arg_map;
        Ok(Obj::FieldAccessWithMod(field_access_with_mod.clone()))
    }

    pub fn inst_fn_obj(
        &self,
        fn_obj: &FnObj,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let mut body = Vec::with_capacity(fn_obj.body.len());
        for obj_vec in fn_obj.body.iter() {
            let mut new_obj_vec = Vec::with_capacity(obj_vec.len());
            for obj in obj_vec.iter() {
                new_obj_vec.push(Box::new(self.inst_obj(obj, param_to_arg_map)?));
            }
            body.push(new_obj_vec);
        }
        Ok(Obj::FnObj(FnObj {
            head: Box::new(self.inst_atom(&fn_obj.head, param_to_arg_map)?),
            body,
        }))
    }

    pub fn inst_number(
        &self,
        number: &Number,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        _ = param_to_arg_map;
        Ok(Obj::Number(number.clone()))
    }

    pub fn inst_add(
        &self,
        add: &Add,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let instantiated_left_obj = self.inst_obj(&add.left, param_to_arg_map)?;
        let instantiated_right_obj = self.inst_obj(&add.right, param_to_arg_map)?;
        Ok(Obj::Add(Add::new(
            instantiated_left_obj,
            instantiated_right_obj,
        )))
    }

    pub fn inst_sub(
        &self,
        sub: &Sub,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let instantiated_left_obj = self.inst_obj(&sub.left, param_to_arg_map)?;
        let instantiated_right_obj = self.inst_obj(&sub.right, param_to_arg_map)?;
        Ok(Obj::Sub(Sub::new(
            instantiated_left_obj,
            instantiated_right_obj,
        )))
    }

    pub fn inst_mul(
        &self,
        mul: &Mul,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let instantiated_left_obj = self.inst_obj(&mul.left, param_to_arg_map)?;
        let instantiated_right_obj = self.inst_obj(&mul.right, param_to_arg_map)?;
        Ok(Obj::Mul(Mul::new(
            instantiated_left_obj,
            instantiated_right_obj,
        )))
    }

    pub fn inst_div(
        &self,
        div: &Div,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::Div(Div {
            left: Box::new(self.inst_obj(&div.left, param_to_arg_map)?),
            right: Box::new(self.inst_obj(&div.right, param_to_arg_map)?),
        }))
    }

    pub fn inst_mod(
        &self,
        mod_obj: &Mod,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let instantiated_left_obj = self.inst_obj(&mod_obj.left, param_to_arg_map)?;
        let instantiated_right_obj = self.inst_obj(&mod_obj.right, param_to_arg_map)?;
        Ok(Obj::Mod(Mod::new(
            instantiated_left_obj,
            instantiated_right_obj,
        )))
    }

    pub fn inst_pow(
        &self,
        pow: &Pow,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let instantiated_base_obj = self.inst_obj(&pow.base, param_to_arg_map)?;
        let instantiated_exponent_obj = self.inst_obj(&pow.exponent, param_to_arg_map)?;
        Ok(Obj::Pow(Pow::new(
            instantiated_base_obj,
            instantiated_exponent_obj,
        )))
    }

    pub fn inst_union(
        &self,
        union: &Union,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::Union(Union {
            left: Box::new(self.inst_obj(&union.left, param_to_arg_map)?),
            right: Box::new(self.inst_obj(&union.right, param_to_arg_map)?),
        }))
    }

    pub fn inst_intersect(
        &self,
        intersect: &Intersect,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::Intersect(Intersect {
            left: Box::new(self.inst_obj(&intersect.left, param_to_arg_map)?),
            right: Box::new(self.inst_obj(&intersect.right, param_to_arg_map)?),
        }))
    }

    pub fn inst_set_minus(
        &self,
        set_minus: &SetMinus,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::SetMinus(SetMinus {
            left: Box::new(self.inst_obj(&set_minus.left, param_to_arg_map)?),
            right: Box::new(self.inst_obj(&set_minus.right, param_to_arg_map)?),
        }))
    }

    pub fn inst_set_diff(
        &self,
        set_diff: &SetDiff,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::SetDiff(SetDiff {
            left: Box::new(self.inst_obj(&set_diff.left, param_to_arg_map)?),
            right: Box::new(self.inst_obj(&set_diff.right, param_to_arg_map)?),
        }))
    }

    pub fn inst_cup(
        &self,
        cup: &Cup,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::Cup(Cup {
            left: Box::new(self.inst_obj(&cup.left, param_to_arg_map)?),
        }))
    }

    pub fn inst_cap(
        &self,
        cap: &Cap,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::Cap(Cap {
            left: Box::new(self.inst_obj(&cap.left, param_to_arg_map)?),
        }))
    }

    pub fn inst_power_set(
        &self,
        power_set: &PowerSet,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::PowerSet(PowerSet {
            set: Box::new(self.inst_obj(&power_set.set, param_to_arg_map)?),
        }))
    }

    pub fn inst_list_set(
        &self,
        list_set: &ListSet,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let mut list = Vec::with_capacity(list_set.list.len());
        for obj in list_set.list.iter() {
            list.push(Box::new(self.inst_obj(obj, param_to_arg_map)?));
        }
        Ok(Obj::ListSet(ListSet { list }))
    }

    pub fn inst_set_builder(
        &self,
        set_builder: &SetBuilder,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let param_names = vec![set_builder.param.clone()];
        let filtered_param_to_arg_map =
            remove_param_names_from_param_to_arg_map(param_to_arg_map, &param_names);
        let mut facts = Vec::with_capacity(set_builder.facts.len());
        for fact in set_builder.facts.iter() {
            facts.push(self.inst_or_and_chain_atomic_fact(fact, &filtered_param_to_arg_map)?);
        }
        Ok(Obj::SetBuilder(SetBuilder {
            param: set_builder.param.clone(),
            param_set: Box::new(self.inst_obj(&set_builder.param_set, &filtered_param_to_arg_map)?),
            facts,
        }))
    }

    pub fn inst_fn_set_with_params(
        &self,
        fn_set_with_params: &FnSet,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let param_names =
            ParamGroupWithSet::collect_param_names(&fn_set_with_params.params_def_with_set);
        let filtered_param_to_arg_map =
            remove_param_names_from_param_to_arg_map(param_to_arg_map, &param_names);
        let mut params_def_with_set =
            Vec::with_capacity(fn_set_with_params.params_def_with_set.len());
        for param_def_with_set in fn_set_with_params.params_def_with_set.iter() {
            params_def_with_set.push(ParamGroupWithSet::new(
                param_def_with_set.params.clone(),
                self.inst_obj(&param_def_with_set.set, &filtered_param_to_arg_map)?,
            ));
        }
        let mut dom_facts = Vec::with_capacity(fn_set_with_params.dom_facts.len());
        for dom_fact in fn_set_with_params.dom_facts.iter() {
            dom_facts
                .push(self.inst_or_and_chain_atomic_fact(dom_fact, &filtered_param_to_arg_map)?);
        }
        Ok(Obj::FnSet(FnSet {
            params_def_with_set,
            dom_facts,
            ret_set: Box::new(
                self.inst_obj(&fn_set_with_params.ret_set, &filtered_param_to_arg_map)?,
            ),
        }))
    }

    pub fn inst_cart(
        &self,
        cart: &Cart,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let mut args = Vec::with_capacity(cart.args.len());
        for arg in cart.args.iter() {
            args.push(Box::new(self.inst_obj(arg, param_to_arg_map)?));
        }
        Ok(Obj::Cart(Cart { args }))
    }

    pub fn inst_cart_dim(
        &self,
        cart_dim: &CartDim,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::CartDim(CartDim {
            set: Box::new(self.inst_obj(&cart_dim.set, param_to_arg_map)?),
        }))
    }

    pub fn inst_proj(
        &self,
        proj: &Proj,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::Proj(Proj {
            set: Box::new(self.inst_obj(&proj.set, param_to_arg_map)?),
            dim: Box::new(self.inst_obj(&proj.dim, param_to_arg_map)?),
        }))
    }

    pub fn inst_tuple_dim(
        &self,
        tuple_dim: &TupleDim,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::TupleDim(TupleDim {
            arg: Box::new(self.inst_obj(&tuple_dim.arg, param_to_arg_map)?),
        }))
    }

    pub fn inst_tuple(
        &self,
        tuple: &Tuple,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        let mut elements = Vec::with_capacity(tuple.args.len());
        for element in tuple.args.iter() {
            elements.push(Box::new(self.inst_obj(element, param_to_arg_map)?));
        }
        Ok(Obj::Tuple(Tuple { args: elements }))
    }

    pub fn inst_count(
        &self,
        count: &Count,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::Count(Count {
            set: Box::new(self.inst_obj(&count.set, param_to_arg_map)?),
        }))
    }

    pub fn inst_range(
        &self,
        range: &Range,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::Range(Range {
            start: Box::new(self.inst_obj(&range.start, param_to_arg_map)?),
            end: Box::new(self.inst_obj(&range.end, param_to_arg_map)?),
        }))
    }

    pub fn inst_closed_range(
        &self,
        closed_range: &ClosedRange,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::ClosedRange(ClosedRange {
            start: Box::new(self.inst_obj(&closed_range.start, param_to_arg_map)?),
            end: Box::new(self.inst_obj(&closed_range.end, param_to_arg_map)?),
        }))
    }

    pub fn inst_choose(
        &self,
        choose: &Choose,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::Choose(Choose {
            set: Box::new(self.inst_obj(&choose.set, param_to_arg_map)?),
        }))
    }

    pub fn inst_obj_at_index(
        &self,
        obj_at_index: &ObjAtIndex,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<Obj, RuntimeError> {
        Ok(Obj::ObjAtIndex(ObjAtIndex {
            obj: Box::new(self.inst_obj(&obj_at_index.obj, param_to_arg_map)?),
            index: Box::new(self.inst_obj(&obj_at_index.index, param_to_arg_map)?),
        }))
    }

    pub fn inst_standard_set(&self, standard_set: &StandardSet) -> Result<Obj, RuntimeError> {
        Ok(Obj::StandardSet(standard_set.clone()))
    }

    pub fn inst_param_type(
        &self,
        param_type: &ParamType,
        param_to_arg_map: &HashMap<String, Obj>,
    ) -> Result<ParamType, RuntimeError> {
        match param_type {
            ParamType::Set(_) => Ok(param_type.clone()),
            ParamType::FiniteSet(_) => Ok(param_type.clone()),
            ParamType::NonemptySet(_) => Ok(param_type.clone()),
            ParamType::Obj(obj) => Ok(ParamType::Obj(self.inst_obj(obj, param_to_arg_map)?)),
            ParamType::Struct(struct_ty) => {
                let mut params = Vec::with_capacity(struct_ty.args.len());
                for param in struct_ty.args.iter() {
                    params.push(self.inst_obj(param, param_to_arg_map)?);
                }
                Ok(ParamType::Struct(StructObj {
                    name: struct_ty.name.clone(),
                    args: params,
                }))
            }
        }
    }

    pub fn inst_param_def_with_set_one_by_one(
        &self,
        param_defs: &Vec<ParamGroupWithSet>,
        args: &Vec<Obj>,
    ) -> Result<Vec<Obj>, RuntimeError> {
        let total_param_count = ParamGroupWithSet::number_of_params(param_defs);
        if total_param_count != args.len() {
            return Err(
                InstantiateRuntimeError(RuntimeErrorStruct::new(
                    None,
                    format!(
                        "argument count mismatch: expected {} parameter(s), got {} argument(s)",
                        total_param_count,
                        args.len()
                    ),
                    default_line_file(),
                    None,
                ))
                .into(),
            );
        }

        let mut param_to_arg_map: HashMap<String, Obj> = HashMap::with_capacity(total_param_count);
        let mut arg_index: usize = 0;
        let mut instantiated_param_sets: Vec<Obj> = Vec::with_capacity(param_defs.len());
        for param_def in param_defs.iter() {
            let instantiated_param_set = if arg_index != 0 {
                self.inst_obj(&param_def.set, &param_to_arg_map)?
            } else {
                param_def.set.clone()
            };
            instantiated_param_sets.push(instantiated_param_set);

            for param_name in param_def.params.iter() {
                param_to_arg_map.insert(param_name.clone(), args[arg_index].clone());
                arg_index += 1;
            }
        }

        Ok(instantiated_param_sets)
    }

    pub fn inst_param_def_with_type_one_by_one(
        &self,
        param_defs: &Vec<ParamGroupWithParamType>,
        args: &Vec<Obj>,
    ) -> Result<Vec<ParamType>, RuntimeError> {
        let total_param_count = ParamGroupWithParamType::number_of_params(param_defs);
        if total_param_count != args.len() {
            return Err(
                InstantiateRuntimeError(RuntimeErrorStruct::new(
                    None,
                    format!(
                        "argument count mismatch: expected {} parameter(s), got {} argument(s)",
                        total_param_count,
                        args.len()
                    ),
                    default_line_file(),
                    None,
                ))
                .into(),
            );
        }

        let mut param_arg_map: HashMap<String, Obj> = HashMap::with_capacity(total_param_count);
        let mut arg_index: usize = 0;
        let mut new_types: Vec<ParamType> = Vec::with_capacity(param_defs.len());
        for param_def in param_defs.iter() {
            let new_type = if arg_index != 0 {
                self.inst_param_type(&param_def.param_type, &param_arg_map)?
            } else {
                param_def.param_type.clone()
            };
            new_types.push(new_type);

            for param_name in param_def.params.iter() {
                param_arg_map.insert(param_name.clone(), args[arg_index].clone());
                arg_index += 1;
            }
        }

        Ok(new_types)
    }
}
