use std::marker::PhantomData;

use fuzzcheck_traits::Mutator;

use crate::DefaultMutator;

pub type VoidMutator = UnitMutator<()>;

impl DefaultMutator for () {
    type Mutator = VoidMutator;
    fn default_mutator() -> Self::Mutator {
        Self::Mutator::default()
    }
}

pub type PhantomDataMutator<T> = UnitMutator<PhantomData<T>>;
impl<T> DefaultMutator for PhantomData<T>
where
    T: 'static,
{
    type Mutator = PhantomDataMutator<T>;
    fn default_mutator() -> Self::Mutator {
        Self::Mutator::default()
    }
}

#[derive(Clone)]
pub struct UnitMutator<T>
where
    T: Clone,
{
    value: T,
}

impl<T> UnitMutator<T>
where
    T: Clone,
{
    pub fn new(value: T) -> Self {
        Self { value }
    }
}

impl<T> Default for UnitMutator<T>
where
    T: Default + Clone,
{
    fn default() -> Self {
        Self { value: T::default() }
    }
}

impl<T> Mutator<T> for UnitMutator<T>
where
    T: Clone + 'static,
{
    type Cache = ();
    type MutationStep = ();
    type ArbitraryStep = bool;
    type UnmutateToken = ();

    fn cache_from_value(&self, _value: &T) -> Self::Cache {}

    fn initial_step_from_value(&self, _value: &T) -> Self::MutationStep {}

    fn max_complexity(&self) -> f64 {
        0.0
    }

    fn min_complexity(&self) -> f64 {
        0.0
    }

    fn complexity(&self, _value: &T, _cache: &Self::Cache) -> f64 {
        0.0
    }

    fn ordered_arbitrary(&mut self, step: &mut Self::ArbitraryStep, _max_cplx: f64) -> Option<(T, Self::Cache)> {
        if !*step {
            *step = true;
            Some((self.value.clone(), ()))
        } else {
            None
        }
    }

    fn random_arbitrary(&mut self, _max_cplx: f64) -> (T, Self::Cache) {
        (self.value.clone(), ())
    }

    fn ordered_mutate(
        &mut self,
        _value: &mut T,
        _cache: &mut Self::Cache,
        _step: &mut Self::MutationStep,
        _max_cplx: f64,
    ) -> Option<Self::UnmutateToken> {
        None
    }

    fn random_mutate(&mut self, _value: &mut T, _cache: &mut Self::Cache, _max_cplx: f64) -> Self::UnmutateToken {}

    fn unmutate(&self, _value: &mut T, _cache: &mut Self::Cache, _t: Self::UnmutateToken) {}
}
