//! Provides some widgets for displaying text objects in the [`crate::text`] module.
mod hbox;
mod repeat;
mod text_widget;
mod truncatable;
pub use hbox::*;
pub use repeat::*;
pub use text_widget::*;
pub use truncatable::*;

#[cfg(test)]
mod test {
    use super::*;
    use crate::text::{Pushable, Span, Spans, WidthSliceable};
    use crate::widget::truncatable::TruncationStyle;
    use ansi_term::{ANSIStrings, Color, Style};
    use std::borrow::Cow;
    fn make_spans(style: &Style, text: &str) -> Spans<Style> {
        let ansistring = style.paint(text);
        let span: Span<Style> = ansistring.into();
        let mut spans: Spans<Style> = Default::default();
        spans.push(&span);
        spans
    }

    #[test]
    fn truncate_trivial_left() {
        let style = Color::Red.normal();
        let content = "0123456";
        let text = make_spans(&style, content);
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Left(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(4));
        let expected = format!(
            "{}",
            ANSIStrings(&[style.paint("012"), ellipsis_style.paint(ellipsis)])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_compound_span_left() {
        let style0 = Color::Red.normal();
        let style1 = Color::Green.normal();
        let content0 = "0123456";
        let content1 = "789";
        let text = {
            let mut text = make_spans(&style0, content0);
            text.push(&make_spans(&style1, content1));
            text
        };
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Left(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(4));
        let expected = format!(
            "{}",
            ANSIStrings(&[style0.paint("012"), ellipsis_style.paint(ellipsis)])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_compound_span_2_left() {
        let style0 = Color::Red.normal();
        let style1 = Color::Green.normal();
        let content0 = "0123456";
        let content1 = "789";
        let text = {
            let mut text = make_spans(&style0, content0);
            text.push(&make_spans(&style1, content1));
            text
        };
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Left(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(9));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                style0.paint(content0),
                style1.paint("7"),
                ellipsis_style.paint(ellipsis),
            ]),
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_two_widgets_first_left() {
        let style0 = Color::Red.normal();
        let style1 = Color::Green.normal();
        let content0 = "01234";
        let content1 = "56789";
        let first_span = make_spans(&style0, content0);
        let second_span = make_spans(&style1, content1);
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Left(Some(ellipsis_span));
        let hbox = vec![&first_span, &second_span]
            .iter()
            .map(|s| {
                let foo: Box<dyn Fitable<_>> =
                    Box::new(TextWidget::<Spans<_>, TruncationStyle<_>>::new(
                        Cow::Borrowed(s),
                        Cow::Borrowed(&truncation),
                    ));
                foo
            })
            .collect::<HBox<Spans<_>>>();
        let actual = format!("{}", hbox.truncate(8));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                style0.paint("012"),
                ellipsis_style.paint(ellipsis),
                style1.paint("567"),
                ellipsis_style.paint(ellipsis),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_trivial_left_noop() {
        let style = Color::Red.normal();
        let content = "0123456";
        let string = style.paint(content);
        let text = make_spans(&style, content);
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Left(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(7));
        let expected = format!("{}", ANSIStrings(&[string]));
        assert_eq!(expected, actual);
    }

    #[test]
    fn trunctate_infinite_left() {
        let span = Span::<Style>::new(
            Cow::Owned(Color::Blue.normal()),
            Cow::Owned("=".to_string()),
        );
        use std::ops::Bound;
        span.slice_width((Bound::Unbounded, Bound::Included(5)));
        let repeat_widget = Repeat::new(span);
        let truncator_style = Color::Black.normal();
        let truncator_text = ".";
        let truncator_span = make_spans(&truncator_style, truncator_text);
        let truncation = TruncationStyle::Left(Some(truncator_span));
        let repeat_text_widget =
            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(repeat_text_widget));
        let actual = format!("{}", hbox.truncate(5));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                Color::Blue.normal().paint("===="),
                truncator_style.paint(truncator_text),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_trivial_right() {
        let style = Color::Red.normal();
        let content = "0123456";
        let text = make_spans(&style, content);
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Right(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(4));
        let expected = format!(
            "{}",
            ANSIStrings(&[ellipsis_style.paint(ellipsis), style.paint("456")])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_compound_span_right() {
        let style0 = Color::Red.normal();
        let style1 = Color::Green.normal();
        let content0 = "0123456";
        let content1 = "789";
        let text = {
            let mut text = make_spans(&style0, content0);
            text.push(&make_spans(&style1, content1));
            text
        };
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Right(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(6));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                ellipsis_style.paint(ellipsis),
                style0.paint("56"),
                style1.paint("789"),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_compound_span_2_right() {
        let style0 = Color::Red.normal();
        let style1 = Color::Green.normal();
        let content0 = "0123456";
        let content1 = "789";
        let text = {
            let mut text = make_spans(&style0, content0);
            text.push(&make_spans(&style1, content1));
            text
        };
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Right(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(9));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                ellipsis_style.paint(ellipsis),
                style0.paint("23456"),
                style1.paint(content1),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_two_widgets_first_right() {
        let style0 = Color::Red.normal();
        let style1 = Color::Green.normal();
        let content0 = "01234";
        let content1 = "56789";
        let first_span = make_spans(&style0, content0);
        let second_span = make_spans(&style1, content1);
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Right(Some(ellipsis_span));
        let hbox = vec![&first_span, &second_span]
            .iter()
            .map(|s| {
                let b: Box<dyn Fitable<_>> =
                    Box::new(TextWidget::<Spans<_>, TruncationStyle<_>>::new(
                        Cow::Borrowed(s),
                        Cow::Borrowed(&truncation),
                    ));
                b
            })
            .collect::<HBox<_>>();
        let actual = format!("{}", hbox.truncate(8));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                ellipsis_style.paint(ellipsis),
                style0.paint("234"),
                ellipsis_style.paint(ellipsis),
                style1.paint("789"),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_trivial_right_noop() {
        let style = Color::Red.normal();
        let content = "0123456";
        let string = style.paint(content);
        let text = make_spans(&style, content);
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Right(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(7));
        let expected = format!("{}", ANSIStrings(&[string]));
        assert_eq!(expected, actual);
    }

    #[test]
    fn trunctate_infinite_right() {
        let repeat_widget = Repeat::new(Span::<Style>::new(
            Cow::Owned(Color::Blue.normal()),
            Cow::Owned("=".to_string()),
        ));
        let truncator_style = Color::Black.normal();
        let truncator_str = ".";
        let truncator_span = make_spans(&truncator_style, truncator_str);
        let truncation = TruncationStyle::Right(Some(truncator_span));
        let repeat_text_widget =
            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(repeat_text_widget));
        let actual = format!("{}", hbox.truncate(5));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                truncator_style.paint(truncator_str),
                Color::Blue.normal().paint("===="),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_trivial_inner() {
        let style = Color::Red.normal();
        let content = "0123456";
        let text = make_spans(&style, content);
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(4));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                style.paint("01"),
                ellipsis_style.paint(ellipsis),
                style.paint("6"),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_compound_span_inner() {
        let style0 = Color::Red.normal();
        let style1 = Color::Green.normal();
        let content0 = "01234";
        let content1 = "56789";
        let text = {
            let mut text = make_spans(&style0, content0);
            text.push(&make_spans(&style1, content1));
            text
        };
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(6));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                style0.paint("012"),
                ellipsis_style.paint(ellipsis),
                style1.paint("89"),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_compound_span_2_inner() {
        let style0 = Color::Red.normal();
        let style1 = Color::Green.normal();
        let content0 = "0123456";
        let content1 = "789";
        let text = {
            let mut text = make_spans(&style0, content0);
            text.push(&make_spans(&style1, content1));
            text
        };
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(9));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                style0.paint("0123"),
                ellipsis_style.paint(ellipsis),
                style0.paint("6"),
                style1.paint(content1),
            ])
        );
        eprintln!("expected: {}", expected);
        eprintln!("actual:   {}", actual);
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_two_widgets_first_inner() {
        let style0 = Color::Red.normal();
        let style1 = Color::Green.normal();
        let content0 = "01234";
        let content1 = "56789";
        let first_span = make_spans(&style0, content0);
        let second_span = make_spans(&style1, content1);
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
        let hbox = vec![&first_span, &second_span]
            .iter()
            .map(|s| {
                let b: Box<dyn Fitable<_>> =
                    Box::new(TextWidget::<Spans<_>, TruncationStyle<_>>::new(
                        Cow::Borrowed(s),
                        Cow::Borrowed(&truncation),
                    ));
                b
            })
            .collect::<HBox<_>>();

        let actual = format!("{}", hbox.truncate(8));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                style0.paint("01"),
                ellipsis_style.paint(ellipsis),
                style0.paint("4"),
                style1.paint("56"),
                ellipsis_style.paint(ellipsis),
                style1.paint("9"),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn truncate_trivial_inner_noop() {
        let style = Color::Red.normal();
        let content = "0123456";
        let text = make_spans(&style, content);
        let ellipsis_style = Color::Blue.normal();
        let ellipsis = "…";
        let ellipsis_span = make_spans(&ellipsis_style, ellipsis);
        let truncation = TruncationStyle::Inner(Some(ellipsis_span));
        let widget = TextWidget::new(Cow::Borrowed(&text), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(widget));
        let actual = format!("{}", hbox.truncate(7));
        let expected = format!("{}", ANSIStrings(&[style.paint(content)]));
        assert_eq!(expected, actual);
    }

    #[test]
    fn trunctate_infinite_inner() {
        let repeat_widget = Repeat::new(Span::<Style>::new(
            Cow::Owned(Color::Blue.normal()),
            Cow::Owned("=".to_string()),
        ));
        let truncator_style = Color::Black.normal();
        let truncator_text = ".";
        let truncator = truncator_style.paint(truncator_text);
        let truncator_span = make_spans(&truncator_style, truncator_text);
        let truncation = TruncationStyle::Inner(Some(truncator_span));
        let repeat_text_widget =
            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(repeat_text_widget));
        let actual = format!("{}", hbox.truncate(5));
        let expected = format!(
            "{}",
            ANSIStrings(&[
                Color::Blue.normal().paint("=="),
                truncator.clone(),
                Color::Blue.normal().paint("=="),
            ])
        );
        assert_eq!(expected, actual);
    }

    #[test]
    fn trunctate_infinite_none_left() {
        let span = Span::<Style>::new(
            Cow::Owned(Color::Blue.normal()),
            Cow::Owned("=".to_string()),
        );
        use std::ops::Bound;
        span.slice_width((Bound::Unbounded, Bound::Included(5)));
        let repeat_widget = Repeat::new(span);
        let truncator_style = Color::Black.normal();
        let truncator_text = ".";
        let truncator_span = make_spans(&truncator_style, truncator_text);
        let truncation = TruncationStyle::Left(Some(truncator_span));
        let repeat_text_widget =
            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(repeat_text_widget));
        let actual = format!("{}", hbox.truncate(0));
        let expected = String::new();
        assert_eq!(expected, actual);
    }

    #[test]
    fn trunctate_infinite_none_right() {
        let span = Span::<Style>::new(
            Cow::Owned(Color::Blue.normal()),
            Cow::Owned("=".to_string()),
        );
        use std::ops::Bound;
        span.slice_width((Bound::Unbounded, Bound::Included(5)));
        let repeat_widget = Repeat::new(span);
        let truncator_style = Color::Black.normal();
        let truncator_text = ".";
        let truncator_span = make_spans(&truncator_style, truncator_text);
        let truncation = TruncationStyle::Right(Some(truncator_span));
        let repeat_text_widget =
            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(repeat_text_widget));
        let actual = format!("{}", hbox.truncate(0));
        let expected = String::new();
        assert_eq!(expected, actual);
    }

    #[test]
    fn trunctate_infinite_none_inner() {
        let span = Span::<Style>::new(
            Cow::Owned(Color::Blue.normal()),
            Cow::Owned("=".to_string()),
        );
        use std::ops::Bound;
        span.slice_width((Bound::Unbounded, Bound::Included(5)));
        let repeat_widget = Repeat::new(span);
        let truncator_style = Color::Black.normal();
        let truncator_text = ".";
        let truncator_span = make_spans(&truncator_style, truncator_text);
        let truncation = TruncationStyle::Inner(Some(truncator_span));
        let repeat_text_widget =
            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(repeat_text_widget));
        let actual = format!("{}", hbox.truncate(0));
        let expected = String::new();
        assert_eq!(expected, actual);
    }

    #[test]
    fn trunctate_infinite_only_symbol() {
        let span = Span::<Style>::new(
            Cow::Owned(Color::Blue.normal()),
            Cow::Owned("=".to_string()),
        );
        use std::ops::Bound;
        span.slice_width((Bound::Unbounded, Bound::Included(5)));
        let repeat_widget = Repeat::new(span);
        let truncator_style = Color::Black.normal();
        let truncator_text = ".";
        let truncator_span = make_spans(&truncator_style, truncator_text);
        let truncation = TruncationStyle::Inner(Some(truncator_span));
        let repeat_text_widget =
            TextWidget::new(Cow::Borrowed(&repeat_widget), Cow::Borrowed(&truncation));
        let mut hbox = HBox::new();
        hbox.push(Box::new(repeat_text_widget));
        let actual = format!("{}", hbox.truncate(1));
        let expected = format!("{}", truncator_style.paint(truncator_text));
        assert_eq!(expected, actual);
    }
}
