//! Test helpers.
use crate::{Range, Selection};
use smallvec::SmallVec;
use std::cmp::Reverse;

/// Convert annotated test string to test string and selection.
///
/// `#[|` for primary selection with head before anchor followed by `]#`.
/// `#(|` for secondary selection with head before anchor followed by `)#`.
/// `#[` for primary selection with head after anchor followed by `|]#`.
/// `#(` for secondary selection with head after anchor followed by `|)#`.
///
/// # Examples
///
/// ```
/// use helix_core::{Range, Selection, test::print};
/// use smallvec::smallvec;
///
/// assert_eq!(
///     print("#[a|]#b#(|c)#"),
///     ("abc".to_owned(), Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0))
/// );
/// ```
///
/// # Panics
///
/// Panics when missing primary or appeared more than once.
/// Panics when missing head or anchor.
/// Panics when head come after head or anchor come after anchor.
pub fn print(s: &str) -> (String, Selection) {
    let mut primary_idx = None;
    let mut ranges = SmallVec::new();
    let mut iter = s.chars().peekable();
    let mut left = String::with_capacity(s.len());

    'outer: while let Some(c) = iter.next() {
        let start = left.chars().count();

        if c != '#' {
            left.push(c);
            continue;
        }

        let (is_primary, close_pair) = match iter.next() {
            Some('[') => (true, ']'),
            Some('(') => (false, ')'),
            Some(ch) => {
                left.push('#');
                left.push(ch);
                continue;
            }
            None => break,
        };

        if is_primary && primary_idx.is_some() {
            panic!("primary `#[` already appeared {:?} {:?}", left, s);
        }

        let head_at_beg = iter.next_if_eq(&'|').is_some();

        while let Some(c) = iter.next() {
            if !(c == close_pair && iter.peek() == Some(&'#')) {
                left.push(c);
                continue;
            }

            if !head_at_beg {
                let prev = left.pop().unwrap();
                if prev != '|' {
                    left.push(prev);
                    left.push(c);
                    continue;
                }
            }

            iter.next(); // skip "#"

            if is_primary {
                primary_idx = Some(ranges.len());
            }

            let (anchor, head) = match head_at_beg {
                true => (left.chars().count(), start),
                false => (start, left.chars().count()),
            };

            ranges.push(Range::new(anchor, head));
            continue 'outer;
        }

        if head_at_beg {
            panic!("missing end `{}#` {:?} {:?}", close_pair, left, s);
        } else {
            panic!("missing end `|{}#` {:?} {:?}", close_pair, left, s);
        }
    }

    let primary = match primary_idx {
        Some(i) => i,
        None => panic!("missing primary `#[|]#` {:?}", s),
    };

    let selection = Selection::new(ranges, primary);
    (left, selection)
}

/// Convert test string and selection to annotated test string.
///
/// `#[|` for primary selection with head before anchor followed by `]#`.
/// `#(|` for secondary selection with head before anchor followed by `)#`.
/// `#[` for primary selection with head after anchor followed by `|]#`.
/// `#(` for secondary selection with head after anchor followed by `|)#`.
///
/// # Examples
///
/// ```
/// use helix_core::{Range, Selection, test::plain};
/// use smallvec::smallvec;
///
/// assert_eq!(
///     plain("abc", Selection::new(smallvec![Range::new(0, 1), Range::new(3, 2)], 0)),
///     "#[a|]#b#(|c)#".to_owned()
/// );
/// ```
pub fn plain(s: &str, selection: Selection) -> String {
    let primary = selection.primary_index();
    let mut out = String::with_capacity(s.len() + 5 * selection.len());
    out.push_str(s);
    let mut insertion: Vec<_> = selection
        .iter()
        .enumerate()
        .flat_map(|(i, range)| {
            // sort like this before reversed so anchor < head later
            match (range.anchor < range.head, i == primary) {
                (true, true) => [(range.anchor, "#["), (range.head, "|]#")],
                (true, false) => [(range.anchor, "#("), (range.head, "|)#")],
                (false, true) => [(range.anchor, "]#"), (range.head, "#[|")],
                (false, false) => [(range.anchor, ")#"), (range.head, "#(|")],
            }
        })
        .collect();
    // insert in reverse order
    insertion.sort_unstable_by_key(|k| Reverse(k.0));
    for (i, s) in insertion {
        out.insert_str(i, s);
    }
    out
}

#[cfg(test)]
#[allow(clippy::module_inception)]
mod test {
    use super::*;

    #[test]
    fn print_single() {
        assert_eq!(
            (String::from("hello"), Selection::single(1, 0)),
            print("#[|h]#ello")
        );
        assert_eq!(
            (String::from("hello"), Selection::single(0, 1)),
            print("#[h|]#ello")
        );
        assert_eq!(
            (String::from("hello"), Selection::single(4, 0)),
            print("#[|hell]#o")
        );
        assert_eq!(
            (String::from("hello"), Selection::single(0, 4)),
            print("#[hell|]#o")
        );
        assert_eq!(
            (String::from("hello"), Selection::single(5, 0)),
            print("#[|hello]#")
        );
        assert_eq!(
            (String::from("hello"), Selection::single(0, 5)),
            print("#[hello|]#")
        );
    }

    #[test]
    fn print_multi() {
        assert_eq!(
            (
                String::from("hello"),
                Selection::new(
                    SmallVec::from_slice(&[Range::new(1, 0), Range::new(5, 4)]),
                    0
                )
            ),
            print("#[|h]#ell#(|o)#")
        );
        assert_eq!(
            (
                String::from("hello"),
                Selection::new(
                    SmallVec::from_slice(&[Range::new(0, 1), Range::new(4, 5)]),
                    0
                )
            ),
            print("#[h|]#ell#(o|)#")
        );
        assert_eq!(
            (
                String::from("hello"),
                Selection::new(
                    SmallVec::from_slice(&[Range::new(2, 0), Range::new(5, 3)]),
                    0
                )
            ),
            print("#[|he]#l#(|lo)#")
        );
        assert_eq!(
            (
                String::from("hello\r\nhello\r\nhello\r\n"),
                Selection::new(
                    SmallVec::from_slice(&[
                        Range::new(7, 5),
                        Range::new(21, 19),
                        Range::new(14, 12)
                    ]),
                    0
                )
            ),
            print("hello#[|\r\n]#hello#(|\r\n)#hello#(|\r\n)#")
        );
    }

    #[test]
    fn print_multi_byte_code_point() {
        assert_eq!(
            (String::from("„“"), Selection::single(1, 0)),
            print("#[|„]#“")
        );
        assert_eq!(
            (String::from("„“"), Selection::single(2, 1)),
            print("„#[|“]#")
        );
        assert_eq!(
            (String::from("„“"), Selection::single(0, 1)),
            print("#[„|]#“")
        );
        assert_eq!(
            (String::from("„“"), Selection::single(1, 2)),
            print("„#[“|]#")
        );
        assert_eq!(
            (String::from("they said „hello“"), Selection::single(11, 10)),
            print("they said #[|„]#hello“")
        );
    }

    #[test]
    fn print_multi_code_point_grapheme() {
        assert_eq!(
            (
                String::from("hello 👨‍👩‍👧‍👦 goodbye"),
                Selection::single(13, 6)
            ),
            print("hello #[|👨‍👩‍👧‍👦]# goodbye")
        );
    }
}
