//! Convenience types for when you expect your words to be valid UTF-8.

use super::WordList as WordListCStr;

/// A slice-like type representing the expanded words.
///
/// Elements of this "slice" are `&str`.
///
/// The validity of the UTF-8 is checked lazily, when you retrieve the element.
/// Invalid UTF-8 will cause a panic, if that it unacceptable then you should
/// just use [WordListCStr] and manually convert.
///
/// # Examples
///
/// All examples will use the following expansion:
///
/// ```
/// let words = cromulent::WordExpander::default()
///     .allow_commands()
///     .expand("$(seq 3)")?;
/// let utf8 = cromulent::utf8::WordList::from(&words);
///
/// # Ok::<(), cromulent::WordError>(())
/// ```
///
/// Length-checking:
///
/// ```
/// # let words = cromulent::WordExpander::default()
/// #     .allow_commands()
/// #     .expand("$(seq 3)")?;
/// # let utf8 = cromulent::utf8::WordList::from(&words);
///
/// assert!(!utf8.is_empty());
/// assert_eq!(utf8.len(), 3);
/// assert_eq!(&utf8[0], "1");
///
/// # Ok::<(), cromulent::WordError>(())
/// ```
///
/// Indexing:
///
/// ```
/// # let words = cromulent::WordExpander::default()
/// #     .allow_commands()
/// #     .expand("$(seq 3)")?;
/// # let utf8 = cromulent::utf8::WordList::from(&words);
///
/// assert_eq!(&utf8[0], "1");
///
/// # Ok::<(), cromulent::WordError>(())
/// ```
///
/// Iteration:
///
/// ```
/// # let words = cromulent::WordExpander::default()
/// #     .allow_commands()
/// #     .expand("$(seq 3)")?;
/// # let utf8 = cromulent::utf8::WordList::from(&words);
///
/// for (i, word) in utf8.into_iter().enumerate() {
///     assert_eq!(word.parse::<usize>()?, i + 1)
/// }
///
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct WordList<'u, 'l: 'u>(&'u WordListCStr<'l>);

impl<'u, 'l: 'u> From<&'u WordListCStr<'l>> for WordList<'u, 'l> {
    fn from(word_list: &'u WordListCStr<'l>) -> Self {
        WordList(word_list)
    }
}

impl<'u, 'l: 'u> std::ops::Index<usize> for WordList<'u, 'l> {
    type Output = str;

    fn index(&self, index: usize) -> &'u Self::Output {
        match self.0[index].to_str() {
            Ok(s) => s,
            // Explicitly panic (as opposed to `expect`) because this is an "API panic" rather than
            // a "can't happen" panic.
            Err(e) => panic!("invalid utf-8 in word: {}", e),
        }
    }
}

impl<'u, 'l: 'u> WordList<'u, 'l> {
    /// Return `true` if no words were expanded.
    ///
    /// # Examples
    ///
    /// ```
    /// let words = cromulent::WordExpander::default().expand("")?;
    /// let utf8 = cromulent::utf8::WordList::from(&words);
    /// assert!(utf8.is_empty());
    ///
    /// # Ok::<(), cromulent::WordError>(())
    /// ```
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Return the number of words that the input expanded into.
    ///
    /// # Examples
    ///
    /// ```
    /// let words = cromulent::WordExpander::default().expand("")?;
    /// let utf8 = cromulent::utf8::WordList::from(&words);
    /// assert_eq!(utf8.len(), 0);
    ///
    /// # Ok::<(), cromulent::WordError>(())
    /// ```
    ///
    /// ```
    /// let words = cromulent::WordExpander::default()
    ///     .allow_commands()
    ///     .expand("$(seq 3)")?;
    /// let utf8 = cromulent::utf8::WordList::from(&words);
    /// assert_eq!(utf8.len(), 3);
    ///
    /// # Ok::<(), cromulent::WordError>(())
    /// ```
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Return the word at a given index without panicking.
    ///
    /// # Examples
    ///
    /// ```
    /// let words = cromulent::WordExpander::default()
    ///     .allow_commands()
    ///     .expand("$(seq 3)")?;
    /// let utf8 = cromulent::utf8::WordList::from(&words);
    ///
    /// assert_eq!(utf8.get(1), Some(&utf8[1]));
    /// assert_eq!(utf8.get(3), None);
    ///
    /// # Ok::<(), cromulent::WordError>(())
    /// ```
    pub fn get(&self, index: usize) -> Option<&<Self as std::ops::Index<usize>>::Output> {
        if index < self.len() {
            Some(&self[index])
        } else {
            None
        }
    }
}

pub struct Iter<'r, 'u: 'r, 'l: 'u> {
    word_list: &'r WordList<'u, 'l>,
    index: usize,
}

impl<'r, 'u: 'r, 'l: 'u> IntoIterator for &'r WordList<'u, 'l> {
    type IntoIter = Iter<'r, 'u, 'l>;
    type Item = <Self::IntoIter as Iterator>::Item;

    fn into_iter(self) -> Self::IntoIter {
        Iter {
            word_list: self,
            index: 0,
        }
    }
}

impl<'r, 'u: 'r, 'l: 'u> Iterator for Iter<'r, 'u, 'l> {
    type Item = &'r <WordList<'u, 'l> as std::ops::Index<usize>>::Output;

    fn next(&mut self) -> Option<Self::Item> {
        if self.index >= self.word_list.len() {
            None
        } else {
            let result = &self.word_list[self.index];
            self.index += 1;
            Some(result)
        }
    }
}
