use chumsky::prelude::*;

use crate::{
    parsers::some_whitespace, traits::Parsable, CloudLayer, VerticalVisibility, Visibility,
    Weather, Wind,
};

/// How is the weather expected to change in the near future?
#[derive(PartialEq, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)]
pub enum Trend {
    NoSignificantChanges,
    NoSignificantWeather,
    Becoming(TrendNewCondition),
    Temporarily(TrendNewCondition),
}

impl Parsable for Trend {
    fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<crate::MetarError<'src>>> {
        choice((
            just("NOSIG").map(|_| Trend::NoSignificantChanges),
            just("NSW").map(|_| Trend::NoSignificantWeather),
            just("BECMG ")
                .then(TrendNewCondition::parser())
                .map(|(_, cond)| Trend::Becoming(cond)),
            just("TEMPO ")
                .then(TrendNewCondition::parser())
                .map(|(_, cond)| Trend::Temporarily(cond)),
        ))
    }
}

/// New conditions apply
#[derive(PartialEq, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TrendNewCondition {
    /// The time from which conditions apply
    pub time: Vec<TrendTime>,
    /// New wind values, if specified
    pub wind: Option<Wind>,
    /// New visibility values, if specified
    pub visibility: Option<Visibility>,
    /// New weather conditions, if specified
    pub weather: Vec<Weather>,
    /// New cloud layers, if specified
    pub cloud: Vec<CloudLayer>,
    /// New vertical visibility, if specified
    pub vertical_visibility: Option<VerticalVisibility>,
}

impl Parsable for TrendNewCondition {
    fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<crate::MetarError<'src>>> {
        group((
            TrendTime::parser()
                .separated_by(some_whitespace())
                .allow_trailing()
                .collect::<Vec<_>>(),
            Wind::parser().map(Some).or(empty().map(|()| None)),
            Visibility::parser()
                .map(Some)
                .then_ignore(some_whitespace())
                .or(empty().map(|()| None)),
            choice((
                just("NSW").map(|_| vec![]).then_ignore(some_whitespace()),
                Weather::parser()
                    .separated_by(some_whitespace())
                    .allow_trailing()
                    .collect::<Vec<_>>(),
            )),
            CloudLayer::parser()
                .separated_by(some_whitespace())
                .allow_trailing()
                .collect::<Vec<_>>(),
            VerticalVisibility::parser()
                .then_ignore(some_whitespace())
                .map(Some)
                .or(empty().map(|()| None)),
        ))
        .map(
            |(time, wind, visibility, weather, cloud, vertical_visibility)| TrendNewCondition {
                time,
                wind,
                visibility,
                weather,
                cloud,
                vertical_visibility,
            },
        )
    }
}

/// The time at which conditions change
#[derive(PartialEq, Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TrendTime {
    /// From a particular time, in 24 hour format, eg. 1345
    From(u16),
    /// Until a particular time, in 24 hour format, eg. 1345
    Until(u16),
    /// At a particular time, in 24 hour format, eg. 1345
    At(u16),
}

impl Parsable for TrendTime {
    fn parser<'src>() -> impl Parser<'src, &'src str, Self, extra::Err<crate::MetarError<'src>>> {
        let time = text::digits(10)
            .exactly(4)
            .to_slice()
            .map(|d: &str| d.parse().unwrap());
        choice((
            just("FM").then(time).map(|(_, time)| TrendTime::From(time)),
            just("TL")
                .then(time)
                .map(|(_, time)| TrendTime::Until(time)),
            just("AT").then(time).map(|(_, time)| TrendTime::At(time)),
        ))
    }
}
