use super::parse_error::ParseError;
use super::{
    DAYS_PER_MONTH, HOURS_PER_DAY, MICROS_PER_SECOND, MINUTES_PER_HOUR, MONTHS_PER_YEAR,
    SECONDS_PER_MIN, scale_date, scale_time,
};
use crate::{Interval, interval_norm::IntervalNorm};

enum ParserCode {
    BadFormat,
    Good,
    DelimFound,
}

impl Interval {
    pub fn from_iso(iso_str: &str) -> Result<Interval, ParseError> {
        let mut date_part = true;
        let delim = vec!['Y', 'M', 'D', 'H', 'S'];
        let mut number = String::new();
        let mut interval_norm = IntervalNorm::default();
        if iso_str.rfind('P') == Some(1) {
            Err(ParseError::from_invalid_interval(
                "Invalid format must start with P.",
            ))
        } else if iso_str.len() < 2 {
            Err(ParseError::from_invalid_interval(
                "Invalid format length is less than 2.",
            ))
        } else {
            for x in iso_str.chars() {
                if x == 'P' {
                    continue;
                }
                if x == 'T' && date_part {
                    date_part = false;
                    continue;
                }
                let code = consume_number(&x, &mut number, &delim);
                match code {
                    ParserCode::BadFormat => {
                        return Err(ParseError::from_invalid_interval("Invalid format."));
                    }
                    ParserCode::Good => {
                        continue;
                    }
                    ParserCode::DelimFound => {
                        let val = parse_number(&mut number)?;
                        match x {
                            'Y' => {
                                let (year, month) = scale_date(val, MONTHS_PER_YEAR);
                                interval_norm.years += year;
                                interval_norm.months += month;
                            }
                            'M' => {
                                if date_part {
                                    let (month, day) = scale_date(val, DAYS_PER_MONTH);
                                    interval_norm.months += month;
                                    interval_norm.days += day;
                                } else {
                                    let (minutes, seconds) = scale_time(val, SECONDS_PER_MIN);
                                    interval_norm.minutes += minutes;
                                    interval_norm.seconds += seconds;
                                }
                            }
                            'D' => {
                                let (days, hours) = scale_date(val, HOURS_PER_DAY);
                                interval_norm.days += days;
                                interval_norm.hours += hours as i64;
                            }
                            'H' => {
                                let (hours, minutes) = scale_time(val, MINUTES_PER_HOUR);
                                interval_norm.hours += hours;
                                interval_norm.minutes += minutes;
                            }
                            'S' => {
                                if date_part {
                                    return Err(ParseError::from_invalid_interval(
                                        "Cannot have S in date part.",
                                    ));
                                }
                                let (seconds, microseconds) = scale_time(val, MICROS_PER_SECOND);
                                interval_norm.seconds += seconds;
                                interval_norm.microseconds += microseconds;
                            }
                            _ => {
                                return Err(ParseError::from_invalid_interval(
                                    "Invalid format unknown delimiter.",
                                ));
                            }
                        }
                    }
                }
            }
            if !number.is_empty() {
                Err(ParseError::from_invalid_interval(
                    "Invalid format could not parse whole interval.",
                ))
            } else {
                interval_norm.try_into_interval()
            }
        }
    }
}

fn consume_number<'a>(val: &'a char, number: &'a mut String, delim: &[char]) -> ParserCode {
    let is_first_char = number.is_empty() && *val == '-';
    let is_period_char = !number.is_empty() && *val == '.';
    if val.is_ascii_digit() || is_first_char || is_period_char {
        number.push(*val);
        ParserCode::Good
    } else if delim.contains(val) {
        ParserCode::DelimFound
    } else {
        ParserCode::BadFormat
    }
}

fn parse_number(number: &mut String) -> Result<f64, ParseError> {
    let parse_num = number.parse::<f64>()?;
    if parse_num > i32::MAX as f64 {
        Err(ParseError::from_invalid_interval("Exceeded max value"))
    } else {
        *number = "".to_owned();
        Ok(parse_num)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_from_iso_1() {
        let interval = Interval::from_iso("P1Y").unwrap();
        let interval_exp = Interval::new(12, 0, 0);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_2() {
        let interval = Interval::from_iso("P1Y1M").unwrap();
        let interval_exp = Interval::new(13, 0, 0);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_3() {
        let interval = Interval::from_iso("P1Y1M1D").unwrap();
        let interval_exp = Interval::new(13, 1, 0);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_4() {
        let interval = Interval::from_iso("P1Y1M1DT1H").unwrap();
        let interval_exp = Interval::new(13, 1, 3600000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_5() {
        let interval = Interval::from_iso("P1Y1M1DT1H10M").unwrap();
        let interval_exp = Interval::new(13, 1, 4200000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_6() {
        let interval = Interval::from_iso("P1Y1M1DT1H10M15S").unwrap();
        let interval_exp = Interval::new(13, 1, 4215000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_7() {
        let interval = Interval::from_iso("PT1H").unwrap();
        let interval_exp = Interval::new(0, 0, 3600000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_8() {
        let interval = Interval::from_iso("PT1H10M").unwrap();
        let interval_exp = Interval::new(0, 0, 4200000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_9() {
        let interval = Interval::from_iso("PT1H10M15S").unwrap();
        let interval_exp = Interval::new(0, 0, 4215000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_10() {
        let interval = Interval::from_iso("P-1Y").unwrap();
        let interval_exp = Interval::new(-12, 0, 0);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_11() {
        let interval = Interval::from_iso("P-1Y-1M").unwrap();
        let interval_exp = Interval::new(-13, 0, 0);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_12() {
        let interval = Interval::from_iso("P-1Y-1M-1D").unwrap();
        let interval_exp = Interval::new(-13, -1, 0);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_13() {
        let interval = Interval::from_iso("P-1Y-1M-1DT-1H").unwrap();
        let interval_exp = Interval::new(-13, -1, -3600000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_14() {
        let interval = Interval::from_iso("P-1Y-1M-1DT-1H-10M").unwrap();
        let interval_exp = Interval::new(-13, -1, -4200000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_15() {
        let interval = Interval::from_iso("P-1Y-1M-1DT-1H-10M-15S").unwrap();
        let interval_exp = Interval::new(-13, -1, -4215000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_16() {
        let interval = Interval::from_iso("PT-1H").unwrap();
        let interval_exp = Interval::new(0, 0, -3600000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_17() {
        let interval = Interval::from_iso("PT-1H-10M").unwrap();
        let interval_exp = Interval::new(0, 0, -4200000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_18() {
        let interval = Interval::from_iso("PT-1H-10M-15S").unwrap();
        let interval_exp = Interval::new(0, 0, -4215000000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_19() {
        let interval = Interval::from_iso("PTT");
        assert!(interval.is_err());
    }

    #[test]
    fn test_from_8601_20() {
        let interval = Interval::from_iso("PT-");
        assert!(interval.is_err());
    }

    #[test]
    fn test_from_8601_21() {
        let interval = Interval::from_iso("PT10");
        assert!(interval.is_err());
    }

    #[test]
    fn test_from_8601_22() {
        let interval = Interval::from_iso("P1.2YT0S").unwrap();
        let interval_exp = Interval::new(14, 0, 0);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_23() {
        let interval = Interval::from_iso("P1.2MT0S").unwrap();
        let interval_exp = Interval::new(1, 6, 0);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_24() {
        let interval = Interval::from_iso("PT1.2S").unwrap();
        let interval_exp = Interval::new(0, 0, 1_200_000);
        assert_eq!(interval, interval_exp);
    }

    #[test]
    fn test_from_8601_25() {
        let interval = Interval::from_iso("PT5S5S").unwrap();
        let interval_exp = Interval::new(0, 0, 10000000);
        assert_eq!(interval, interval_exp);
    }
}
