use std::any::Any;
use std::convert::Infallible;
use std::fmt::Debug;

use ecs_macros::Component;
use seq_macro::seq;

use crate::component::{Component, HandleMut as ComponentHandleMut};
use crate::tuple::{ReduceElement as TupleReduceElement, Tuple};
use crate::World;

pub mod stateful;

pub trait System<'world, Impl>: 'static
{
    type Input;

    #[must_use]
    fn initialize(self, input: Self::Input) -> Self;

    fn run<'this>(&'this self, world: &'world World)
    where
        'this: 'world;

    fn into_type_erased(self) -> TypeErased;

    fn get_local_component_mut<LocalComponent: Component>(
        &self,
    ) -> Option<ComponentHandleMut<LocalComponent>>;

    fn set_local_component<LocalComponent: Component>(
        &mut self,
        local_component: LocalComponent,
    );
}

macro_rules! impl_system {
    ($c: tt) => {
        seq!(I in 0..$c {
            impl<'world, Func, #(TParam~I,)*> System<'world, fn(#(TParam~I,)*)>
                for Func
            where
                Func: Fn(#(TParam~I,)*) + Copy + 'static,
                #(TParam~I: Param<'world, Input = ()>,)*
            {
                type Input = Infallible;

                fn initialize(self, _input: Self::Input) -> Self
                {
                    self
                }

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

                    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(data) };

                            let me = data
                                .downcast_ref::<Func>()
                                .expect("Function downcast failed");

                            // 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<ComponentHandleMut<LocalComponent>>
                {
                    panic!("System does not have any local components");
                }

                fn set_local_component<LocalComponent: Component>(
                    &mut self,
                    _local_component: LocalComponent,
                ) {
                    panic!("System does not have any local components");
                }
            }
        });
    };
}

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

pub trait Into<Impl>
{
    type System;

    fn into_system(self) -> Self::System;
}

pub struct TypeErased
{
    data: Box<dyn Any>,
    run: Box<TypeErasedRunFn>,
}

impl TypeErased
{
    /// Runs the system.
    ///
    /// # Safety
    /// `world_data` must live at least as long as the [`World`] the system belongs to.
    pub unsafe fn run(&self, world: &World)
    {
        // You have to dereference for downcasting to work for some reason
        let data = &*self.data;

        (self.run)(data, world);
    }
}

impl Debug for TypeErased
{
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
    {
        formatter.debug_struct("TypeErased").finish_non_exhaustive()
    }
}

/// Function in [`TypeErased`] used to run the system.
type TypeErasedRunFn = dyn Fn(&dyn Any, &World);

/// A parameter to a [`System`].
pub trait Param<'world>
{
    type Input;

    fn initialize<SystemImpl>(
        system: &mut impl System<'world, SystemImpl>,
        input: Self::Input,
    );

    fn new<SystemImpl>(
        system: &'world impl System<'world, SystemImpl>,
        world: &'world World,
    ) -> Self;
}

/// A type which can be used as input to a [`System`].
pub trait Input: 'static {}

/// Component tuple reducing operation to get the parameters that takes input.
pub struct ParamWithInputFilter;

impl<InputT: Input, Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter>
    for InputT
where
    Accumulator: Tuple,
{
    type Return = Accumulator::WithElementAtEnd<Self>;
}

impl<Accumulator> TupleReduceElement<Accumulator, ParamWithInputFilter> for ()
{
    type Return = Accumulator;
}

#[derive(Debug, Component)]
pub(crate) struct SystemComponent
{
    pub(crate) system: TypeErased,
}