extern crate fwdansi;
extern crate proptest;
extern crate termcolor;

use fwdansi::write_ansi;
use proptest::prelude::*;
use std::io;
use termcolor::{Ansi, Color, ColorSpec, WriteColor};

proptest! {
    #[test]
    fn check_no_crash(ref ansi_string in any::<Vec<u8>>()) {
        let mut ansi = Ansi::new(Vec::with_capacity(ansi_string.len()));
        prop_assert!(write_ansi(&mut ansi, &ansi_string).is_ok());
    }

    #[test]
    fn ansi_idempotent(ref elements in prop::collection::vec(Element::any(), 0..100)) {
        // first, write the test string into an ANSI buffer.
        let mut original = Ansi::new(Vec::new());
        for e in elements {
            e.write(&mut original).unwrap();
        }

        // recover the original string, and forward it using `write_ansi`.
        let original = original.into_inner();
        let mut forwarded = Ansi::new(Vec::with_capacity(original.len()));
        prop_assert!(write_ansi(&mut forwarded, &original).is_ok());
        prop_assert_eq!(original, forwarded.into_inner());
    }
}

#[derive(Debug, Clone)]
enum Element {
    ColorSpec(ColorSpec),
    Reset,
    Text(Vec<u8>),
}

fn any_opt_color() -> impl Strategy<Value = Option<Color>> {
    let color = prop_oneof![
        Just(Color::Black),
        Just(Color::Red),
        Just(Color::Green),
        Just(Color::Yellow),
        Just(Color::Blue),
        Just(Color::Magenta),
        Just(Color::Cyan),
        Just(Color::White),
        any::<u8>().prop_map(Color::Ansi256),
        any::<[u8; 3]>().prop_map(|[r, g, b]| Color::Rgb(r, g, b)),
    ];
    prop::option::weighted(0.9, color)
}

prop_compose! {
    fn any_color_spec()(
        bold in any::<bool>(),
        underline in any::<bool>(),
        intense in any::<bool>(),
        fg_color in any_opt_color(),
        bg_color in any_opt_color(),
    ) -> ColorSpec {
        let mut spec = ColorSpec::new();
        spec.set_bold(bold)
            .set_underline(underline)
            .set_intense(intense)
            .set_fg(fg_color)
            .set_bg(bg_color);
        spec
    }
}

impl Element {
    fn any() -> impl Strategy<Value = Self> {
        prop_oneof![
            Just(Element::Reset),
            any_color_spec().prop_map(Element::ColorSpec),
            any::<Vec<u8>>().prop_filter_map("ignored empty SGR", |v| {
                if v.windows(3).find(|w| w == b"\x1b[m").is_some() {
                    None
                } else {
                    Some(Element::Text(v))
                }
            }),
        ]
    }

    fn write<W: WriteColor>(&self, mut w: W) -> io::Result<()> {
        w.write_all(b"~")?;
        match self {
            Element::ColorSpec(cs) => w.set_color(cs),
            Element::Reset => w.reset(),
            Element::Text(text) => w.write_all(text),
        }
    }
}
