/// `MinMaxResult` is an enum returned by `minmax`.
///
/// See [`.minmax()`](crate::Itertools::minmax) for more detail.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum MinMaxResult<T> {
    /// Empty iterator
    NoElements,

    /// Iterator with one element, so the minimum and maximum are the same
    OneElement(T),

    /// More than one element in the iterator, the first element is not larger
    /// than the second
    MinMax(T, T),
}

impl<T: Clone> MinMaxResult<T> {
    /// `into_option` creates an `Option` of type `(T, T)`. The returned `Option`
    /// has variant `None` if and only if the `MinMaxResult` has variant
    /// `NoElements`. Otherwise `Some((x, y))` is returned where `x <= y`.
    /// If the `MinMaxResult` has variant `OneElement(x)`, performing this
    /// operation will make one clone of `x`.
    ///
    /// # Examples
    ///
    /// ```
    /// use itertools::MinMaxResult::{self, MinMax, NoElements, OneElement};
    ///
    /// let r: MinMaxResult<i32> = NoElements;
    /// assert_eq!(r.into_option(), None);
    ///
    /// let r = OneElement(1);
    /// assert_eq!(r.into_option(), Some((1, 1)));
    ///
    /// let r = MinMax(1, 2);
    /// assert_eq!(r.into_option(), Some((1, 2)));
    /// ```
    pub fn into_option(self) -> Option<(T, T)> {
        match self {
            Self::NoElements => None,
            Self::OneElement(x) => Some((x.clone(), x)),
            Self::MinMax(x, y) => Some((x, y)),
        }
    }
}

/// Implementation guts for `minmax` and `minmax_by_key`.
pub fn minmax_impl<I, K, F, L>(mut it: I, mut key_for: F, mut lt: L) -> MinMaxResult<I::Item>
where
    I: Iterator,
    F: FnMut(&I::Item) -> K,
    L: FnMut(&I::Item, &I::Item, &K, &K) -> bool,
{
    let (mut min, mut max, mut min_key, mut max_key) = match it.next() {
        None => return MinMaxResult::NoElements,
        Some(x) => match it.next() {
            None => return MinMaxResult::OneElement(x),
            Some(y) => {
                let xk = key_for(&x);
                let yk = key_for(&y);
                if !lt(&y, &x, &yk, &xk) {
                    (x, y, xk, yk)
                } else {
                    (y, x, yk, xk)
                }
            }
        },
    };

    loop {
        // `first` and `second` are the two next elements we want to look
        // at.  We first compare `first` and `second` (#1). The smaller one
        // is then compared to current minimum (#2). The larger one is
        // compared to current maximum (#3). This way we do 3 comparisons
        // for 2 elements.
        let first = match it.next() {
            None => break,
            Some(x) => x,
        };
        let second = match it.next() {
            None => {
                let first_key = key_for(&first);
                if lt(&first, &min, &first_key, &min_key) {
                    min = first;
                } else if !lt(&first, &max, &first_key, &max_key) {
                    max = first;
                }
                break;
            }
            Some(x) => x,
        };
        let first_key = key_for(&first);
        let second_key = key_for(&second);
        if !lt(&second, &first, &second_key, &first_key) {
            if lt(&first, &min, &first_key, &min_key) {
                min = first;
                min_key = first_key;
            }
            if !lt(&second, &max, &second_key, &max_key) {
                max = second;
                max_key = second_key;
            }
        } else {
            if lt(&second, &min, &second_key, &min_key) {
                min = second;
                min_key = second_key;
            }
            if !lt(&first, &max, &first_key, &max_key) {
                max = first;
                max_key = first_key;
            }
        }
    }

    MinMaxResult::MinMax(min, max)
}
