use std::ops::ControlFlow;

use crate::{Collector, RefCollector};

/// A [`Collector`] that can "safely" collect items even after
/// the underlying collector has stopped accumulating,
/// without triggering undesired behaviors.
///
/// This `struct` is created by [`Collector::fuse()`]. See its documentation for more.
///
/// [`Break(())`]: ControlFlow::Break
#[derive(Debug, Clone)]
pub struct Fuse<C> {
    collector: C,
    finished: bool,
}

impl<C> Fuse<C>
where
    C: Collector,
{
    #[inline]
    pub(crate) fn new(collector: C) -> Self {
        Self {
            finished: collector.break_hint(),
            collector,
        }
    }
}

impl<C> Fuse<C> {
    /// Returns whether the collector is "fisnished" and will not accept more items.
    #[inline]
    pub fn finished(&self) -> bool {
        self.finished
    }

    #[inline]
    fn collect_impl(&mut self, f: impl FnOnce(&mut C) -> ControlFlow<()>) -> ControlFlow<()> {
        if self.finished {
            ControlFlow::Break(())
        } else if f(&mut self.collector).is_break() {
            self.finished = true;
            ControlFlow::Break(())
        } else {
            ControlFlow::Continue(())
        }
    }
}

impl<C> Collector for Fuse<C>
where
    C: Collector,
{
    type Item = C::Item;
    type Output = C::Output;

    #[inline]
    fn collect(&mut self, item: Self::Item) -> ControlFlow<()> {
        self.collect_impl(|collector| collector.collect(item))
    }

    #[inline]
    fn finish(self) -> Self::Output {
        self.collector.finish()
    }

    #[inline]
    fn break_hint(&self) -> bool {
        self.finished
    }

    #[inline]
    fn collect_many(&mut self, items: impl IntoIterator<Item = Self::Item>) -> ControlFlow<()> {
        self.collect_impl(|collector| collector.collect_many(items))
    }

    #[inline]
    fn collect_then_finish(self, items: impl IntoIterator<Item = Self::Item>) -> Self::Output {
        if self.finished {
            self.finish()
        } else {
            self.collector.collect_then_finish(items)
        }
    }
}

impl<C> RefCollector for Fuse<C>
where
    C: RefCollector,
{
    #[inline]
    fn collect_ref(&mut self, item: &mut Self::Item) -> ControlFlow<()> {
        self.collect_impl(|collector| collector.collect_ref(item))
    }
}

#[cfg(test)]
mod tests {
    use crate::prelude::*;

    #[test]
    fn must_finished_for_empty_collector_on_construct() {
        struct MockCollector;

        impl Collector for MockCollector {
            type Item = ();

            type Output = ();

            fn collect(&mut self, _: Self::Item) -> core::ops::ControlFlow<()> {
                unimplemented!()
            }

            fn finish(self) -> Self::Output {
                unimplemented!()
            }

            fn break_hint(&self) -> bool {
                true
            }
        }

        assert!(MockCollector.fuse().finished());
    }
}
