use std::any::{Any, TypeId};
use std::panic::{RefUnwindSafe, UnwindSafe};

use hashbrown::HashMap;
use seq_macro::seq;

use crate::component::Component;
use crate::lock::Lock;
use crate::system::{
    ComponentRefMut,
    Into as IntoSystem,
    Param,
    ParamWithInputFilter,
    System,
    TypeErased,
};
use crate::tuple::{
    Reduce as TupleReduce,
    Tuple,
    WithAllElemLtStatic as TupleWithAllElemLtStatic,
};
use crate::uid::Uid;
use crate::World;

/// A stateful system.
pub struct Stateful<Func>
{
    func: Func,
    local_components: HashMap<Uid, Lock<Box<dyn Component>>>,
}

macro_rules! impl_system {
    ($c: tt) => {
        seq!(I in 0..$c {
            impl<'world, Func, #(TParam~I,)*> System<'world, fn(&'world (), #(TParam~I,)*)>
                for Stateful<Func>
            where
                Func: Fn(#(TParam~I,)*) + Copy + RefUnwindSafe + UnwindSafe + 'static,
                #(TParam~I: Param<'world>,)*
                #(TParam~I::Input: 'static,)*
                (#(TParam~I::Input,)*): TupleReduce<
                    ParamWithInputFilter,
                    Out: Tuple<InOptions: TupleWithAllElemLtStatic>
                >,
            {
                type Input =
                    <(#(TParam~I::Input,)*) as TupleReduce<ParamWithInputFilter>>::Out;

                fn initialize(mut self, input: Self::Input) -> Self
                {
                    let mut option_input = input.into_in_options();

                    let mut index = 0;

                    #(
                        if TypeId::of::<TParam~I::Input>() !=
                            TypeId::of::<()>()
                        {
                            let input = option_input
                                .get_mut::<Option<TParam~I::Input>>(index)
                                .expect("Input element index out of range")
                                .take()
                                .expect("Input element is already taken");

                            TParam~I::initialize(
                                &mut self,
                                input
                            );

                            #[allow(unused_assignments)]
                            {
                                index += 1;
                            }
                        }
                    )*

                    self
                }

                fn run<'this>(&'this self, world: &'world World)
                where
                    'this: 'world
                {
                    let func = self.func;

                    func(#({
                        TParam~I::new(self, &world)
                    },)*);
                }

                fn into_type_erased(self) -> TypeErased
                {
                    TypeErased {
                        data: Box::new(self),
                        run: Box::new(|data, world| {
                            // SAFETY: The caller of TypeErased::run ensures the lifetime
                            // is correct
                            let data = unsafe { &*std::ptr::from_ref::<dyn Any>(data) };

                            let me = data.downcast_ref::<Self>().unwrap();

                            // SAFETY: The caller of TypeErased::run ensures the lifetime
                            // is correct
                            let world = unsafe { &*std::ptr::from_ref(world) };

                            me.run(world);
                        }),
                    }
                }

                fn get_local_component_mut<LocalComponent: Component>(
                    &self,
                ) -> Option<ComponentRefMut<LocalComponent>>
                {
                    let local_component = self.local_components
                        .get(&LocalComponent::id())?
                        .write_nonblock()
                        .expect("Failed to aquire read-write local component lock");

                    Some(ComponentRefMut::new(local_component))
                }

                fn set_local_component<LocalComponent: Component>(
                    &mut self,
                    local_component: LocalComponent,
                )
                {
                    self.local_components
                        .insert(
                            LocalComponent::id(),
                            Lock::new(Box::new(local_component))
                        );
                }
            }

            impl<Func, #(TParam~I,)*> IntoSystem<fn(#(TParam~I,)*)>
                for Func
            where
                Func: Fn(#(TParam~I,)*) + Copy + 'static,
            {
                type System = Stateful<Func>;

                fn into_system(self) -> Self::System
                {
                    Self::System {
                        func: self,
                        local_components: HashMap::new(),
                    }
                }
            }
        });
    };
}

seq!(C in 1..16 {
    impl_system!(C);
});