use std::any::{type_name, Any};
use std::fmt::Debug;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Weak};

use crate::lock::{Lock, WriteGuard};
use crate::system::{Param as SystemParam, System};
use crate::type_name::TypeName;
use crate::World;

/// A type which has a single instance and is shared globally.
pub trait Sole: Any + TypeName
{
    fn drop_last(&self) -> bool;

    fn as_any_mut(&mut self) -> &mut dyn Any;

    fn as_any(&self) -> &dyn Any;
}

impl dyn Sole
{
    pub fn downcast_mut<Real: 'static>(&mut self) -> Option<&mut Real>
    {
        self.as_any_mut().downcast_mut()
    }

    pub fn downcast_ref<Real: 'static>(&self) -> Option<&Real>
    {
        self.as_any().downcast_ref()
    }
}

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

impl TypeName for Box<dyn Sole>
{
    fn type_name(&self) -> &'static str
    {
        self.as_ref().type_name()
    }
}

/// Holds a reference to a globally shared singleton value.
#[derive(Debug)]
pub struct Single<'world, SoleT: Sole>
{
    sole: WriteGuard<'world, Box<dyn Sole>>,
    sole_weak: Weak<Lock<Box<dyn Sole>>>,
    _ph: PhantomData<SoleT>,
}

impl<'world, SoleT> Single<'world, SoleT>
where
    SoleT: Sole,
{
    /// Returns a struct which holds a weak reference to the [`World`] that `Single`
    /// references and that can be used to aquire a new `Single` if the referenced
    /// [`World`] is still alive.
    #[must_use]
    pub fn to_weak_ref(&self) -> SingleWeakRef<SoleT>
    {
        SingleWeakRef {
            sole: self.sole_weak.clone(),
            _ph: PhantomData,
        }
    }

    fn new(sole: &'world Arc<Lock<Box<dyn Sole>>>) -> Self
    {
        Self {
            sole: sole.write_nonblock().unwrap_or_else(|_| {
                panic!(
                    "Failed to aquire read-write lock to single component {}",
                    type_name::<SoleT>()
                )
            }),
            sole_weak: Arc::downgrade(sole),
            _ph: PhantomData,
        }
    }
}

impl<'world, SoleT> SystemParam<'world> for Single<'world, SoleT>
where
    SoleT: Sole,
{
    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
    {
        let sole = world.data.sole_storage.get::<SoleT>().unwrap_or_else(|| {
            panic!("Sole {} was not found in world", type_name::<SoleT>())
        });

        Self::new(sole)
    }
}

impl<SoleT> Deref for Single<'_, SoleT>
where
    SoleT: Sole,
{
    type Target = SoleT;

    fn deref(&self) -> &Self::Target
    {
        self.sole.downcast_ref().unwrap()
    }
}

impl<SoleT> DerefMut for Single<'_, SoleT>
where
    SoleT: Sole,
{
    fn deref_mut(&mut self) -> &mut Self::Target
    {
        self.sole.downcast_mut().unwrap()
    }
}

#[derive(Debug, Clone)]
pub struct SingleWeakRef<SoleT>
where
    SoleT: Sole,
{
    sole: Weak<Lock<Box<dyn Sole>>>,
    _ph: PhantomData<SoleT>,
}

impl<SoleT> SingleWeakRef<SoleT>
where
    SoleT: Sole,
{
    /// Returns a struct which can be used to retrieve a [`Single`].
    ///
    /// Returns [`None`] if the referenced [`World`] has been dropped.
    #[must_use]
    pub fn access(&self) -> Option<SingleRef<'_, SoleT>>
    {
        Some(SingleRef {
            sole: self.sole.upgrade()?,
            _pd: PhantomData,
        })
    }
}

/// Intermediate between [`Single`] and [`SingleWeakRef`]. Contains a strong reference to
/// a world which is not allowed direct access to.
#[derive(Debug, Clone)]
pub struct SingleRef<'weak_ref, SoleT>
where
    SoleT: Sole,
{
    sole: Arc<Lock<Box<dyn Sole>>>,
    _pd: PhantomData<&'weak_ref SoleT>,
}

impl<SoleT> SingleRef<'_, SoleT>
where
    SoleT: Sole,
{
    #[must_use]
    pub fn to_single(&self) -> Single<'_, SoleT>
    {
        Single::new(&self.sole)
    }
}