use std::rc::Rc;
use debris_error::{LangError, LangErrorKind, LangResult, Result};
use crate::{
    class::ClassRef,
    objects::{
        obj_bool_static::ObjStaticBool, obj_function::FunctionContext,
        obj_int_static::ObjStaticInt, obj_null::ObjNull, obj_string::ObjString,
    },
    type_context::TypeContext,
    ObjectPayload, ObjectRef, ValidPayload,
};
pub struct DebrisFunctionInterface(NormalizedFunction);
impl DebrisFunctionInterface {
    pub(crate) fn call(&self, function_ctx: &mut FunctionContext) -> Result<ObjectRef> {
        let raw_value = self.call_raw(function_ctx);
        self.handle_raw_result(function_ctx, raw_value)
            .map_err(|kind| LangError::new(kind, function_ctx.span).into())
    }
    pub(crate) fn call_raw(
        &self,
        function_ctx: &mut FunctionContext,
    ) -> Option<LangResult<ObjectRef>> {
        (self.0.inner_fn)(function_ctx)
    }
    pub fn handle_raw_result(
        &self,
        function_ctx: &FunctionContext,
        value: Option<LangResult<ObjectRef>>,
    ) -> LangResult<ObjectRef> {
        match value {
            Some(val) => val,
            None => Err(LangErrorKind::UnexpectedOverload {
                expected: (self.0.required_parameter_fn)(function_ctx.type_ctx()).map_or_else(
                    || vec![vec!["<Any>".to_string()]],
                    |overloads| {
                        overloads
                            .into_iter()
                            .map(|params| {
                                params.into_iter().map(|param| param.to_string()).collect()
                            })
                            .collect()
                    },
                ),
                parameters: function_ctx
                    .parameters
                    .iter()
                    .map(|param| param.class.to_string())
                    .collect(),
                function_definition_span: None,
            }),
        }
    }
}
impl From<NormalizedFunction> for DebrisFunctionInterface {
    fn from(value: NormalizedFunction) -> Self {
        DebrisFunctionInterface(value)
    }
}
type NormalizedFnSig = dyn Fn(&mut FunctionContext) -> Option<LangResult<ObjectRef>>;
pub struct NormalizedFunction {
    inner_fn: Box<NormalizedFnSig>,
    required_parameter_fn: Box<dyn Fn(&TypeContext) -> Option<Vec<Vec<ClassRef>>>>,
}
pub fn make_overload(functions: Vec<NormalizedFunction>) -> NormalizedFunction {
    let functions = Rc::new(functions);
    let functions_2 = Rc::clone(&functions);
    NormalizedFunction {
        inner_fn: Box::new(move |ctx| {
            for function in functions.as_ref() {
                if let Some(result) = (function.inner_fn)(ctx) {
                    return Some(result);
                }
            }
            None
        }),
        required_parameter_fn: Box::new(move |type_ctx| {
            let mut values = Vec::with_capacity(functions_2.len());
            for function in functions_2.as_ref() {
                values.extend(
                    (function.required_parameter_fn)(type_ctx).unwrap_or_else(|| vec![vec![]]),
                );
            }
            Some(values)
        }),
    }
}
pub trait ValidReturnType {
    fn into_result(self, ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>>;
}
impl<T> ValidReturnType for T
where
    T: ObjectPayload,
{
    fn into_result(self, ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
        Some(Ok(self.into_object(ctx.type_ctx())))
    }
}
impl<T> ValidReturnType for LangResult<T>
where
    T: ObjectPayload,
{
    fn into_result(self, ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
        Some(self.map(|value| value.into_object(ctx.type_ctx())))
    }
}
impl<T> ValidReturnType for Option<LangResult<T>>
where
    T: ObjectPayload,
{
    fn into_result(self, ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
        self.map(|res| res.map(|val| val.into_object(ctx.type_ctx())))
    }
}
impl ValidReturnType for Option<LangResult<ObjectRef>> {
    fn into_result(self, _ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
        self
    }
}
impl ValidReturnType for LangResult<ObjectRef> {
    fn into_result(self, _ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
        Some(self)
    }
}
impl ValidReturnType for ObjectRef {
    fn into_result(self, _ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
        Some(Ok(self))
    }
}
macro_rules! impl_map_valid_return_type {
    ($from:ty, $to:ty) => {
        impl ValidReturnType for $from {
            fn into_result(self, ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
                Some(Ok(
                    <$to as From<$from>>::from(self).into_object(&ctx.type_ctx())
                ))
            }
        }
        impl ValidReturnType for LangResult<$from> {
            fn into_result(self, ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
                Some(match self {
                    Ok(value) => Ok(<$to as From<$from>>::from(value).into_object(&ctx.type_ctx())),
                    Err(err) => Err(err),
                })
            }
        }
        impl ValidReturnType for Option<$from> {
            fn into_result(self, ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
                self.map(|value| Ok(<$to as From<$from>>::from(value).into_object(&ctx.type_ctx())))
            }
        }
        impl ValidReturnType for Option<LangResult<$from>> {
            fn into_result(self, ctx: &mut FunctionContext) -> Option<LangResult<ObjectRef>> {
                self.map(|res| match res {
                    Ok(value) => Ok(<$to as From<$from>>::from(value).into_object(&ctx.type_ctx())),
                    Err(err) => Err(err),
                })
            }
        }
    };
}
impl_map_valid_return_type!((), ObjNull);
impl_map_valid_return_type!(i32, ObjStaticInt);
impl_map_valid_return_type!(bool, ObjStaticBool);
impl_map_valid_return_type!(Rc<str>, ObjString);
pub trait ToFunctionInterface<Params, Return>: 'static {
    fn to_normalized_function(self) -> NormalizedFunction;
}
impl ToFunctionInterface<(), ()> for NormalizedFunction {
    fn to_normalized_function(self) -> NormalizedFunction {
        self
    }
}
impl<Function, Return> ToFunctionInterface<(&mut FunctionContext<'_, '_, '_>, &[ObjectRef]), Return>
    for Function
where
    Function: Fn(&mut FunctionContext, &[ObjectRef]) -> Return + 'static,
    Return: ValidReturnType,
{
    fn to_normalized_function(self) -> NormalizedFunction {
        NormalizedFunction {
            inner_fn: Box::new(move |ctx: &mut FunctionContext| {
                (self)(ctx, ctx.parameters).into_result(ctx)
            }),
            required_parameter_fn: Box::new(|_ctx| None),
        }
    }
}
macro_rules! count {
    () => (0_usize);
    ( $x:tt $($xs:tt)* ) => (1_usize + count!($($xs)*));
}
macro_rules! impl_to_function_interface {
    ($($xs:ident),*) => {
        impl<Function, Return, $($xs),*> ToFunctionInterface<(&mut FunctionContext<'_, '_, '_>, $(&$xs),*), Return> for Function
        where
            Function: Fn(&mut FunctionContext, $(&$xs),*) -> Return + 'static,
            Return: ValidReturnType,
            $($xs: ObjectPayload),*
        {
            impl_to_function_interface!(impl_inner, [$($xs),*], ,);
        }
        impl<Function, Return, $($xs),*> ToFunctionInterface<(&FunctionContext<'_, '_, '_>, $(&$xs),*), Return> for Function
        where
            Function: Fn(&FunctionContext, $(&$xs),*) -> Return + 'static,
            Return: ValidReturnType,
            $($xs: ObjectPayload),*
        {
            impl_to_function_interface!(impl_inner, [$($xs),*], ,);
        }
        #[allow(unused_parens)]
        impl<Function, Return, $($xs),*> ToFunctionInterface<($(&$xs),*), Return> for Function
        where
            Function: Fn($(&$xs),*) -> Return + 'static,
            Return: ValidReturnType,
            $($xs: ObjectPayload),*
        {
            impl_to_function_interface!(impl_inner, [$($xs),*],);
        }
    };
    (impl_inner, [$($xs:ident),*], $($use_ctx:tt)?) => {
        fn to_normalized_function(self) -> NormalizedFunction {
            let inner_fn = Box::new(move |ctx: &mut FunctionContext| {
                let iter = ctx.parameters.iter();
                let self_val = if ctx.parameters.len() + 1 == count!($($xs)*) {
                    ctx.self_val.clone()
                } else {
                    None
                };
                #[allow(unused_variables, unused_mut)]
                let mut iter = self_val.iter().chain(iter);
                let temp_slice: [ObjectRef; count!($($xs)*)] = [$(impl_to_function_interface!(maybe_promote, ctx, iter.next(), $xs)),*];
                if iter.next().is_some() {
                    return None;
                }
                #[allow(unused_variables, unused_mut)]
                let mut temp_values = temp_slice.iter();
                (self)($(ctx $use_ctx)? $(
                    impl_to_function_interface!(verify_type, temp_values.next(), $xs)?
                ),*).into_result(ctx)
            });
            NormalizedFunction {
                inner_fn,
                required_parameter_fn: Box::new(#[allow(unused_variables)] |type_ctx| Some(vec![vec![$($xs::static_class(type_ctx)),*]]))
            }
        }
    };
    (maybe_promote, $ctx:expr, $val:expr, $typ:ident) => {{
        let value = $val?;
        if value.downcast_payload::<$typ>().is_some() {
            value.clone()
        } else {
            match $ctx.promote_obj(value.clone(), $ctx.type_ctx().static_class_obj::<$typ>()) {
                Some(Ok(value)) => value,
                other => return other,
            }
        }
    }};
    (verify_type, $val:expr, $typ:ident) => {
        $val.and_then(|value| value.downcast_payload::<$typ>())
    };
}
impl_to_function_interface!();
impl_to_function_interface!(A);
impl_to_function_interface!(A, B);
impl_to_function_interface!(A, B, C);
impl_to_function_interface!(A, B, C, D);
impl_to_function_interface!(A, B, C, D, E);
impl_to_function_interface!(A, B, C, D, E, F);
impl_to_function_interface!(A, B, C, D, E, F, G);
impl_to_function_interface!(A, B, C, D, E, F, G, H);
pub trait DowncastArray<'a, T> {
    fn downcast_array(&'a self) -> Option<T>;
}
macro_rules! impl_downcast_array {
    ($($xs:ident),*) => {
        impl<'a, $($xs),*> DowncastArray<'a, ($(&'a $xs),*,)> for [ObjectRef]
        where
            $(
                $xs: ObjectPayload
            ),*
        {
            #[allow(non_snake_case)]
            fn downcast_array(&'a self) -> Option<($(&'a $xs),*,)> {
                match &self {
                    [$($xs),*] => {
                        $(
                            let $xs = $xs.downcast_payload()?;
                        )*
                        Some(($($xs),*,))
                    }
                    _ => None,
                }
            }
        }
    };
}
impl_downcast_array!(A);
impl_downcast_array!(A, B);
impl_downcast_array!(A, B, C);
impl_downcast_array!(A, B, C, D);
impl_downcast_array!(A, B, C, D, E);
impl_downcast_array!(A, B, C, D, E, F);
impl_downcast_array!(A, B, C, D, E, F, G);
impl_downcast_array!(A, B, C, D, E, F, G, H);
impl_downcast_array!(A, B, C, D, E, F, G, H, I);
impl_downcast_array!(A, B, C, D, E, F, G, H, I, J);