extern crate parse_zoneinfo;

use parse_zoneinfo::line::{ChangeTime, DaySpec, Line, Month, TimeSpec, TimeType, Weekday, Year};
use parse_zoneinfo::table::{Format, RuleInfo, Saving, Table, TableBuilder, ZoneInfo};
use parse_zoneinfo::transitions::{FixedTimespan, FixedTimespanSet, TableTransitions};

#[test]
fn no_transitions() {
    let zone = ZoneInfo {
        offset: 1234,
        format: Format::new("TEST"),
        saving: Saving::NoSaving,
        end_time: None,
    };

    let mut table = Table::default();
    table.zonesets.insert("Test/Zone".to_owned(), vec![zone]);

    assert_eq!(
        table.timespans("Test/Zone"),
        Some(FixedTimespanSet {
            first: FixedTimespan {
                utc_offset: 1234,
                dst_offset: 0,
                name: "TEST".to_owned()
            },
            rest: vec![],
        })
    );
}

#[test]
fn one_transition() {
    let zone_1 = ZoneInfo {
        offset: 1234,
        format: Format::new("TEST"),
        saving: Saving::NoSaving,
        end_time: Some(ChangeTime::UntilTime(
            Year::Number(1970),
            Month::January,
            DaySpec::Ordinal(2),
            TimeSpec::HoursMinutesSeconds(10, 17, 36).with_type(TimeType::UTC),
        )),
    };

    let zone_2 = ZoneInfo {
        offset: 5678,
        format: Format::new("TSET"),
        saving: Saving::NoSaving,
        end_time: None,
    };

    let mut table = Table::default();
    table
        .zonesets
        .insert("Test/Zone".to_owned(), vec![zone_1, zone_2]);

    let expected = FixedTimespanSet {
        first: FixedTimespan {
            utc_offset: 1234,
            dst_offset: 0,
            name: "TEST".to_owned(),
        },
        rest: vec![(
            ((24 + 10) * 60 + 17) * 60 + 36,
            FixedTimespan {
                utc_offset: 5678,
                dst_offset: 0,
                name: "TSET".to_owned(),
            },
        )],
    };

    assert_eq!(table.timespans("Test/Zone"), Some(expected));
}

#[test]
fn two_transitions() {
    let zone_1 = ZoneInfo {
        offset: 1234,
        format: Format::new("TEST"),
        saving: Saving::NoSaving,
        end_time: Some(ChangeTime::UntilTime(
            Year::Number(1970),
            Month::January,
            DaySpec::Ordinal(2),
            TimeSpec::HoursMinutesSeconds(10, 17, 36).with_type(TimeType::Standard),
        )),
    };

    let zone_2 = ZoneInfo {
        offset: 3456,
        format: Format::new("TSET"),
        saving: Saving::NoSaving,
        end_time: Some(ChangeTime::UntilTime(
            Year::Number(1970),
            Month::January,
            DaySpec::Ordinal(3),
            TimeSpec::HoursMinutesSeconds(17, 9, 27).with_type(TimeType::Standard),
        )),
    };

    let zone_3 = ZoneInfo {
        offset: 5678,
        format: Format::new("ESTE"),
        saving: Saving::NoSaving,
        end_time: None,
    };

    let mut table = Table::default();
    table
        .zonesets
        .insert("Test/Zone".to_owned(), vec![zone_1, zone_2, zone_3]);

    let expected = FixedTimespanSet {
        first: FixedTimespan {
            utc_offset: 1234,
            dst_offset: 0,
            name: "TEST".to_owned(),
        },
        rest: vec![
            (
                122222,
                FixedTimespan {
                    utc_offset: 3456,
                    dst_offset: 0,
                    name: "TSET".to_owned(),
                },
            ),
            (
                231111,
                FixedTimespan {
                    utc_offset: 5678,
                    dst_offset: 0,
                    name: "ESTE".to_owned(),
                },
            ),
        ],
    };

    assert_eq!(table.timespans("Test/Zone"), Some(expected));
}

#[test]
fn one_rule() {
    let ruleset = vec![RuleInfo {
        from_year: Year::Number(1980),
        to_year: None,
        month: Month::February,
        day: DaySpec::Ordinal(4),
        time: 0,
        time_type: TimeType::UTC,
        time_to_add: 1000,
        letters: None,
    }];

    let lmt = ZoneInfo {
        offset: 0,
        format: Format::new("LMT"),
        saving: Saving::NoSaving,
        end_time: Some(ChangeTime::UntilYear(Year::Number(1980))),
    };

    let zone = ZoneInfo {
        offset: 2000,
        format: Format::new("TEST"),
        saving: Saving::Multiple("Dwayne".to_owned()),
        end_time: None,
    };

    let mut table = Table::default();
    table
        .zonesets
        .insert("Test/Zone".to_owned(), vec![lmt, zone]);
    table.rulesets.insert("Dwayne".to_owned(), ruleset);

    assert_eq!(
        table.timespans("Test/Zone"),
        Some(FixedTimespanSet {
            first: FixedTimespan {
                utc_offset: 0,
                dst_offset: 0,
                name: "LMT".to_owned()
            },
            rest: vec![(
                318_470_400,
                FixedTimespan {
                    utc_offset: 2000,
                    dst_offset: 1000,
                    name: "TEST".to_owned()
                }
            )],
        })
    );
}

#[test]
fn two_rules() {
    let ruleset = vec![
        RuleInfo {
            from_year: Year::Number(1980),
            to_year: None,
            month: Month::February,
            day: DaySpec::Ordinal(4),
            time: 0,
            time_type: TimeType::UTC,
            time_to_add: 1000,
            letters: None,
        },
        RuleInfo {
            from_year: Year::Number(1989),
            to_year: None,
            month: Month::January,
            day: DaySpec::Ordinal(12),
            time: 0,
            time_type: TimeType::UTC,
            time_to_add: 1500,
            letters: None,
        },
    ];

    let lmt = ZoneInfo {
        offset: 0,
        format: Format::new("LMT"),
        saving: Saving::NoSaving,
        end_time: Some(ChangeTime::UntilYear(Year::Number(1980))),
    };

    let zone = ZoneInfo {
        offset: 2000,
        format: Format::new("TEST"),
        saving: Saving::Multiple("Dwayne".to_owned()),
        end_time: None,
    };

    let mut table = Table::default();
    table
        .zonesets
        .insert("Test/Zone".to_owned(), vec![lmt, zone]);
    table.rulesets.insert("Dwayne".to_owned(), ruleset);

    assert_eq!(
        table.timespans("Test/Zone"),
        Some(FixedTimespanSet {
            first: FixedTimespan {
                utc_offset: 0,
                dst_offset: 0,
                name: "LMT".to_owned()
            },
            rest: vec![
                (
                    318_470_400,
                    FixedTimespan {
                        utc_offset: 2000,
                        dst_offset: 1000,
                        name: "TEST".to_owned()
                    }
                ),
                (
                    600_566_400,
                    FixedTimespan {
                        utc_offset: 2000,
                        dst_offset: 1500,
                        name: "TEST".to_owned()
                    }
                ),
            ],
        })
    );
}

#[test]
fn tripoli() {
    let libya = vec![
        RuleInfo {
            from_year: Year::Number(1951),
            to_year: None,
            month: Month::October,
            day: DaySpec::Ordinal(14),
            time: 7200,
            time_type: TimeType::Wall,
            time_to_add: 3600,
            letters: Some("S".to_owned()),
        },
        RuleInfo {
            from_year: Year::Number(1952),
            to_year: None,
            month: Month::January,
            day: DaySpec::Ordinal(1),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 0,
            letters: None,
        },
        RuleInfo {
            from_year: Year::Number(1953),
            to_year: None,
            month: Month::October,
            day: DaySpec::Ordinal(9),
            time: 7200,
            time_type: TimeType::Wall,
            time_to_add: 3600,
            letters: Some("S".to_owned()),
        },
        RuleInfo {
            from_year: Year::Number(1954),
            to_year: None,
            month: Month::January,
            day: DaySpec::Ordinal(1),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 0,
            letters: None,
        },
        RuleInfo {
            from_year: Year::Number(1955),
            to_year: None,
            month: Month::September,
            day: DaySpec::Ordinal(30),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 3600,
            letters: Some("S".to_owned()),
        },
        RuleInfo {
            from_year: Year::Number(1956),
            to_year: None,
            month: Month::January,
            day: DaySpec::Ordinal(1),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 0,
            letters: None,
        },
        RuleInfo {
            from_year: Year::Number(1982),
            to_year: Some(Year::Number(1984)),
            month: Month::April,
            day: DaySpec::Ordinal(1),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 3600,
            letters: Some("S".to_owned()),
        },
        RuleInfo {
            from_year: Year::Number(1982),
            to_year: Some(Year::Number(1985)),
            month: Month::October,
            day: DaySpec::Ordinal(1),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 0,
            letters: None,
        },
        RuleInfo {
            from_year: Year::Number(1985),
            to_year: None,
            month: Month::April,
            day: DaySpec::Ordinal(6),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 3600,
            letters: Some("S".to_owned()),
        },
        RuleInfo {
            from_year: Year::Number(1986),
            to_year: None,
            month: Month::April,
            day: DaySpec::Ordinal(4),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 3600,
            letters: Some("S".to_owned()),
        },
        RuleInfo {
            from_year: Year::Number(1986),
            to_year: None,
            month: Month::October,
            day: DaySpec::Ordinal(3),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 0,
            letters: None,
        },
        RuleInfo {
            from_year: Year::Number(1987),
            to_year: Some(Year::Number(1989)),
            month: Month::April,
            day: DaySpec::Ordinal(1),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 3600,
            letters: Some("S".to_owned()),
        },
        RuleInfo {
            from_year: Year::Number(1987),
            to_year: Some(Year::Number(1989)),
            month: Month::October,
            day: DaySpec::Ordinal(1),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 0,
            letters: None,
        },
        RuleInfo {
            from_year: Year::Number(1997),
            to_year: None,
            month: Month::April,
            day: DaySpec::Ordinal(4),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 3600,
            letters: Some("S".to_owned()),
        },
        RuleInfo {
            from_year: Year::Number(1997),
            to_year: None,
            month: Month::October,
            day: DaySpec::Ordinal(4),
            time: 0,
            time_type: TimeType::Wall,
            time_to_add: 0,
            letters: None,
        },
        RuleInfo {
            from_year: Year::Number(2013),
            to_year: None,
            month: Month::March,
            day: DaySpec::Last(Weekday::Friday),
            time: 3600,
            time_type: TimeType::Wall,
            time_to_add: 3600,
            letters: Some("S".to_owned()),
        },
        RuleInfo {
            from_year: Year::Number(2013),
            to_year: None,
            month: Month::October,
            day: DaySpec::Last(Weekday::Friday),
            time: 7200,
            time_type: TimeType::Wall,
            time_to_add: 0,
            letters: None,
        },
    ];

    let zone = vec![
        ZoneInfo {
            offset: 3164,
            format: Format::new("LMT"),
            saving: Saving::NoSaving,
            end_time: Some(ChangeTime::UntilYear(Year::Number(1920))),
        },
        ZoneInfo {
            offset: 3600,
            format: Format::new("CE%sT"),
            saving: Saving::Multiple("Libya".to_owned()),
            end_time: Some(ChangeTime::UntilYear(Year::Number(1959))),
        },
        ZoneInfo {
            offset: 7200,
            format: Format::new("EET"),
            saving: Saving::NoSaving,
            end_time: Some(ChangeTime::UntilYear(Year::Number(1982))),
        },
        ZoneInfo {
            offset: 3600,
            format: Format::new("CE%sT"),
            saving: Saving::Multiple("Libya".to_owned()),
            end_time: Some(ChangeTime::UntilDay(
                Year::Number(1990),
                Month::May,
                DaySpec::Ordinal(4),
            )),
        },
        ZoneInfo {
            offset: 7200,
            format: Format::new("EET"),
            saving: Saving::NoSaving,
            end_time: Some(ChangeTime::UntilDay(
                Year::Number(1996),
                Month::September,
                DaySpec::Ordinal(30),
            )),
        },
        ZoneInfo {
            offset: 3600,
            format: Format::new("CE%sT"),
            saving: Saving::Multiple("Libya".to_owned()),
            end_time: Some(ChangeTime::UntilDay(
                Year::Number(1997),
                Month::October,
                DaySpec::Ordinal(4),
            )),
        },
        ZoneInfo {
            offset: 7200,
            format: Format::new("EET"),
            saving: Saving::NoSaving,
            end_time: Some(ChangeTime::UntilTime(
                Year::Number(2012),
                Month::November,
                DaySpec::Ordinal(10),
                TimeSpec::HoursMinutes(2, 0).with_type(TimeType::Wall),
            )),
        },
        ZoneInfo {
            offset: 3600,
            format: Format::new("CE%sT"),
            saving: Saving::Multiple("Libya".to_owned()),
            end_time: Some(ChangeTime::UntilTime(
                Year::Number(2013),
                Month::October,
                DaySpec::Ordinal(25),
                TimeSpec::HoursMinutes(2, 0).with_type(TimeType::Wall),
            )),
        },
        ZoneInfo {
            offset: 7200,
            format: Format::new("EET"),
            saving: Saving::NoSaving,
            end_time: None,
        },
    ];

    let mut table = Table::default();
    table.zonesets.insert("Test/Zone".to_owned(), zone);
    table.rulesets.insert("Libya".to_owned(), libya);

    assert_eq!(
        table.timespans("Test/Zone"),
        Some(FixedTimespanSet {
            first: FixedTimespan {
                utc_offset: 3164,
                dst_offset: 0,
                name: "LMT".to_owned()
            },
            rest: vec![
                (
                    -1_577_926_364,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    -574_902_000,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    -568_087_200,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    -512_175_600,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    -504_928_800,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    -449_888_400,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    -441_856_800,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    -347_158_800,
                    FixedTimespan {
                        utc_offset: 7200,
                        dst_offset: 0,
                        name: "EET".to_owned()
                    }
                ),
                (
                    378_684_000,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    386_463_600,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    402_271_200,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    417_999_600,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    433_807_200,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    449_622_000,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    465_429_600,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    481_590_000,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    496_965_600,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    512_953_200,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    528_674_400,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    544_230_000,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    560_037_600,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    575_852_400,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    591_660_000,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    607_388_400,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    623_196_000,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    641_775_600,
                    FixedTimespan {
                        utc_offset: 7200,
                        dst_offset: 0,
                        name: "EET".to_owned()
                    }
                ),
                (
                    844_034_400,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    860_108_400,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    875_916_000,
                    FixedTimespan {
                        utc_offset: 7200,
                        dst_offset: 0,
                        name: "EET".to_owned()
                    }
                ),
                (
                    1_352_505_600,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 0,
                        name: "CET".to_owned()
                    }
                ),
                (
                    1_364_515_200,
                    FixedTimespan {
                        utc_offset: 3600,
                        dst_offset: 3600,
                        name: "CEST".to_owned()
                    }
                ),
                (
                    1_382_659_200,
                    FixedTimespan {
                        utc_offset: 7200,
                        dst_offset: 0,
                        name: "EET".to_owned()
                    }
                ),
            ],
        })
    );
}

#[test]
fn dushanbe() {
    static ZONEINFO: &str = r#"
Zone    Asia/Dushanbe   4:35:12 -   LMT 1924 May  2
            5:00    1:00    +05/+06 1991 Sep  9  2:00s
"#;

    let mut table = TableBuilder::new();
    for line in ZONEINFO.lines() {
        let line = Line::new(line).unwrap();
        match line {
            Line::Zone(zone) => table.add_zone_line(zone).unwrap(),
            Line::Continuation(cont) => table.add_continuation_line(cont).unwrap(),
            Line::Rule(rule) => table.add_rule_line(rule).unwrap(),
            Line::Link(link) => table.add_link_line(link).unwrap(),
            Line::Space => {}
        }
    }
    let table = table.build();
    let _ = table.timespans("Asia/Dushanbe").unwrap();
}
