use std::convert::TryInto;

use super::nom_prelude::complete::*;
use crate::color_context::{Change, ChangeSet, ColorTag};

/// Parses a color tag.
#[rustfmt::skip]
pub fn one_color_tag<'a>(input: &'a str) -> IResult<&'a str, ColorTag> {
    // TODO: handle whitespace
    map(
        consumed(
            delimited(
                tag("<"),
                pair(
                    opt(tag("/")),
                    separated_list0(
                        tag(";"),
                        one_change
                    )
                ),
                tag(">"),
            )
        ),
        |(source, (close_opt, changes))| ColorTag {
            source: Some(source),
            span: None,
            is_close: close_opt.is_some(),
            change_set: ChangeSet::from(changes.as_ref()),
        }
    )
    (input)
}

/// Parses a sub-part of a color tag, like `"red"` inside `"<red;bold>"`.
#[rustfmt::skip]
pub fn one_change<'a>(input: &'a str) -> IResult<&'a str, Change> {
    map_res(
        recognize(
            many1(none_of(";>"))
        ),
        |s: &'a str| s.trim().try_into()
    )
    (input)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::color_context::{BaseColor, Color, Intensity};

    #[test]
    fn parse_change() {
        let change = one_change("b").unwrap().1;
        assert_eq!(
            change,
            Change::Foreground(Color::new(BaseColor::Blue, Intensity::Normal))
        );
        let change = one_change("s").unwrap().1;
        assert_eq!(change, Change::Bold);
    }

    #[test]
    fn parse_tag() {
        let tag = one_color_tag("<s>").unwrap().1;
        assert_eq!(
            tag,
            ColorTag {
                source: Some("<s>"),
                span: None,
                is_close: false,
                change_set: ChangeSet::from(&[Change::Bold][..]),
            }
        );

        let tag = one_color_tag("<s;y!>...").unwrap().1;
        assert_eq!(
            tag,
            ColorTag {
                source: Some("<s;y!>"),
                span: None,
                is_close: false,
                change_set: ChangeSet::from(
                    &[
                        Change::Bold,
                        Change::Foreground(Color::new(BaseColor::Yellow, Intensity::Bright)),
                    ][..]
                ),
            }
        );

        let tag = one_color_tag("</u;y;k;B>...").unwrap().1;
        assert_eq!(
            tag,
            ColorTag {
                source: Some("</u;y;k;B>"),
                span: None,
                is_close: true,
                change_set: ChangeSet::from(
                    &[
                        Change::Underline,
                        Change::Foreground(Color::new(BaseColor::Black, Intensity::Normal)),
                        Change::Background(Color::new(BaseColor::Blue, Intensity::Normal)),
                    ][..]
                ),
            }
        );
    }
}
