/*!
Helper traits to work with references or owned values of types and their subsets.
*/

#[derive(Clone, Copy)]
pub enum EitherDecomp<A, B> {
    Owned(A),
    Borrowed(B),
}

/// A very basic implementation of [`Cow`] without a [`Clone`] trait dependency.
pub type MyCow<'b, X> = EitherDecomp<X, &'b X>;

impl<'b, X> std::ops::Deref for MyCow<'b, X> {
    type Target = X;

    #[inline]
    fn deref(&self) -> &Self::Target {
        match self {
            EitherDecomp::Owned(x) => &x,
            EitherDecomp::Borrowed(x) => x,
        }
    }
}

impl<'b, X> MyCow<'b, X> {
    #[inline]
    pub fn into_owned(self) -> X where X : Clone {
        match self {
            EitherDecomp::Owned(x) => x,
            EitherDecomp::Borrowed(x) => x.clone(),
        }
    }
}

/// Trait for abitrary mathematical spaces.
pub trait Space : Instance<Self, Self::Decomp> {
    /// Default decomposition for the space
    type Decomp : Decomposition<Self>;
}

#[macro_export]
macro_rules! impl_basic_space {
    ($($type:ty)*) => { $(
        impl $crate::instance::Space for $type {
            type Decomp = $crate::instance::BasicDecomposition;
        }
    )* };
    ($type:ty where $($where:tt)*) => {
        impl<$($where)*> $crate::instance::Space for $type {
            type Decomp = $crate::instance::BasicDecomposition;
        }
    };
}

impl_basic_space!(u8 u16 u32 u64 u128 usize
                  i8 i16 i32 i64 i128 isize
                  f32 f64);

/// Marker type for decompositions to be used with [`Instance`].
pub trait Decomposition<X : Space> : Sized {
    /// Possibly owned form of the decomposition
    type Decomposition<'b> : Instance<X, Self> where X : 'b;
    /// Unlikely owned form of the decomposition.
    /// Type for a lightweight intermediate conversion that does not own the original variable.
    /// Usually this is just a reference, but may also be a lightweight structure that
    /// contains references; see the implementation for [`crate::direct_product::Pair`].
    type Reference<'b> : Instance<X, Self> + Copy where X : 'b;

    /// Left the lightweight reference type into a full decomposition type.
    fn lift<'b>(r : Self::Reference<'b>) -> Self::Decomposition<'b>;
}

/// Most common [`Decomposition`] (into `Either<X, &'b X>`) that allows working with owned
/// values and all sorts of references.
#[derive(Copy, Clone, Debug)]
pub struct BasicDecomposition;

impl<X : Space + Clone> Decomposition<X> for BasicDecomposition {
    type Decomposition<'b> = MyCow<'b, X> where X : 'b;
    type Reference<'b> = &'b X where X : 'b;

    #[inline]
    fn lift<'b>(r : Self::Reference<'b>) -> Self::Decomposition<'b> {
        MyCow::Borrowed(r)
    }
}

/// Helper trait for functions to work with either owned values or references to either the
/// “principal type” `X` or types some present a subset of `X`. In the latter sense, this
/// generalises [`std::borrow::ToOwned`], [`std::borrow::Borrow`], and [`std::borrow::Cow`].
/// This type also includes iteratation facilities when `X` is a [`Collection`], to avoid
/// the possibly costly conversion of such subset types into `X`.
pub trait Instance<X : Space, D = <X as Space>::Decomp> : Sized where D : Decomposition<X> {
    /// Decomposes self according to `decomposer`.
    fn decompose<'b>(self) -> D::Decomposition<'b>
    where Self : 'b, X : 'b;
  
    /// Returns a lightweight instance of `self`.
    fn ref_instance(&self) -> D::Reference<'_>;

    /// Returns an owned instance of `X`, cloning or converting non-true instances when necessary.
    fn own(self) -> X;

    // ************** automatically implemented methods below from here **************

    /// Returns an owned instance or reference to `X`, converting non-true instances when necessary.
    ///
    /// Default implementation uses [`Self::own`]. Consumes the input.
    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b {
        MyCow::Owned(self.own())
    }
    
    #[inline]
    /// Evaluates `f` on a reference to self.
    ///
    /// Default implementation uses [`Self::cow`]. Consumes the input.
    fn eval<'b, R>(self, f : impl FnOnce(&X) -> R) -> R
    where X : 'b, Self : 'b
    {
        f(&*self.cow())
    }

    #[inline]
    /// Evaluates `f` or `g` depending on whether a reference or owned value is available.
    ///
    /// Default implementation uses [`Self::cow`]. Consumes the input.
    fn either<'b, R>(
        self,
        f : impl FnOnce(X) -> R,
        g : impl FnOnce(&X) -> R
    ) -> R
    where Self : 'b
    {
        match self.cow() {
            EitherDecomp::Owned(x) => f(x),
            EitherDecomp::Borrowed(x) => g(x),
        }
    }
}


impl<X : Space + Clone> Instance<X, BasicDecomposition> for X {
    #[inline]
    fn decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b>
    where Self : 'b, X : 'b
    {
        MyCow::Owned(self)
    }

    #[inline]
    fn own(self) -> X {
        self
    }

    #[inline]
    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b {
        MyCow::Owned(self)
    }

    #[inline]
    fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> {
        self
    }
}

impl<'a, X : Space + Clone> Instance<X, BasicDecomposition> for &'a X {
    #[inline]
    fn decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b>
    where Self : 'b, X : 'b
    {
        MyCow::Borrowed(self)
    }

    #[inline]
    fn own(self) -> X {
        self.clone()
    }

    #[inline]
    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b {
        MyCow::Borrowed(self)
    }

    #[inline]
    fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> {
        *self
    }
}

impl<'a, X : Space + Clone> Instance<X, BasicDecomposition> for &'a mut X {
    #[inline]
    fn  decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b>
    where Self : 'b, X : 'b
    {
        EitherDecomp::Borrowed(self)
    }

    #[inline]
    fn own(self) -> X {
        self.clone()
    }

    #[inline]
    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b, X : Clone {
        EitherDecomp::Borrowed(self)
    }

    #[inline]
    fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> {
        *self
    }
}

impl<'a, X : Space + Clone> Instance<X, BasicDecomposition> for MyCow<'a, X> {

    #[inline]
    fn  decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b>
    where Self : 'b, X : 'b
    {
        self
    }

    #[inline]
    fn own(self) -> X {
        match self {
            MyCow::Borrowed(a) => a.own(),
            MyCow::Owned(b) => b.own()
        }
    }

    #[inline]
    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b {
        match self {
            MyCow::Borrowed(a) => a.cow(),
            MyCow::Owned(b) => b.cow()
        }
    }

    #[inline]
    fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> {
        match self {
            MyCow::Borrowed(a) => a,
            MyCow::Owned(b) => &b,
        }
    }
}

/// Marker type for mutable decompositions to be used with [`InstanceMut`].
pub trait DecompositionMut<X : Space> : Sized {
    type ReferenceMut<'b> : InstanceMut<X, Self> where X : 'b;
}


/// Helper trait for functions to work with mutable references.
pub trait InstanceMut<X : Space , D = <X as Space>::Decomp> : Sized where D : DecompositionMut<X> {
    /// Returns a mutable decomposition of self.
    fn ref_instance_mut(&mut self) -> D::ReferenceMut<'_>;
}

impl<X : Space> DecompositionMut<X> for BasicDecomposition {
    type ReferenceMut<'b> = &'b mut X where X : 'b;
}

/// This impl may seem pointless, but allows throwaway mutable scratch variables
impl<'a, X : Space> InstanceMut<X, BasicDecomposition> for X {
    #[inline]
    fn ref_instance_mut(&mut self)
        -> <BasicDecomposition as DecompositionMut<X>>::ReferenceMut<'_>
    {
        self
    }
}

impl<'a, X : Space> InstanceMut<X, BasicDecomposition> for &'a mut X {
    #[inline]
    fn ref_instance_mut(&mut self)
        -> <BasicDecomposition as DecompositionMut<X>>::ReferenceMut<'_>
    {
        self
    }
}
