/*
Copyright 2020-2023, Savanni D'Gerinel <savanni@luminescent-dreams.com>

This file is part of the Luminescent Dreams Tools.

Luminescent Dreams Tools is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Luminescent Dreams Tools is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with Lumeto. If not, see <https://www.gnu.org/licenses/>.
*/

#[macro_use]
extern crate serde_derive;

extern crate chrono;
extern crate chrono_tz;
extern crate dimensioned;
extern crate emseries;

#[cfg(test)]
mod test {
    use chrono::{prelude::*};
    use chrono_tz::Etc::UTC;
    use dimensioned::si::{Kilogram, Meter, Second, M, S};

    use emseries::*;

    #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
    struct Distance(Meter<f64>);

    #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
    struct Duration(Second<f64>);

    #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
    struct BikeTrip {
        datetime: DateTime<FixedOffset>,
        distance: Distance,
        duration: Duration,
        comments: String,
    }

    impl Recordable for BikeTrip {
        fn timestamp(&self) -> Timestamp {
            Timestamp::DateTime(self.datetime)
        }
        fn tags(&self) -> Vec<String> {
            Vec::new()
        }
    }

    fn mk_trips() -> [BikeTrip; 5] {
        [
            BikeTrip {
                datetime: UTC
                    .with_ymd_and_hms(2011, 10, 29, 0, 0, 0)
                    .unwrap()
                    .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                distance: Distance(58741.055 * M),
                duration: Duration(11040.0 * S),
                comments: String::from("long time ago"),
            },
            BikeTrip {
                datetime: UTC
                    .with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
                    .unwrap()
                    .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                distance: Distance(17702.0 * M),
                duration: Duration(2880.0 * S),
                comments: String::from("day 2"),
            },
            BikeTrip {
                datetime: UTC
                    .with_ymd_and_hms(2011, 11, 02, 0, 0, 0)
                    .unwrap()
                    .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                distance: Distance(41842.945 * M),
                duration: Duration(7020.0 * S),
                comments: String::from("Do Some Distance!"),
            },
            BikeTrip {
                datetime: UTC
                    .with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
                    .unwrap()
                    .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                distance: Distance(34600.895 * M),
                duration: Duration(5580.0 * S),
                comments: String::from("I did a lot of distance back then"),
            },
            BikeTrip {
                datetime: UTC
                    .with_ymd_and_hms(2011, 11, 05, 0, 0, 0)
                    .unwrap()
                    .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                distance: Distance(6437.376 * M),
                duration: Duration(960.0 * S),
                comments: String::from("day 5"),
            },
        ]
    }

    fn run_test<T>(test: T)
    where
        T: FnOnce(tempfile::TempPath),
    {
        let tmp_file = tempfile::NamedTempFile::new().expect("temporary path created");
        let tmp_path = tmp_file.into_temp_path();
        test(tmp_path);
    }

    fn run<T>(test: T)
    where
        T: FnOnce(Series<BikeTrip>),
    {
        let tmp_file = tempfile::NamedTempFile::new().expect("temporary path created");
        let tmp_path = tmp_file.into_temp_path();
        let ts: Series<BikeTrip> =
            Series::open(&tmp_path).expect("the time series should open correctly");
        test(ts);
    }

    #[test]
    pub fn can_add_and_retrieve_entries() {
        run(|mut series| {
            let trips = mk_trips();
            let uuid = series
                .put(trips[0].clone())
                .expect("expect a successful put");
            let record_res = series.get(&uuid);

            for trip in &trips[1..=4] {
                series.put(trip.clone()).expect("expect a successful put");
            }

            match record_res {
                None => assert!(false, "There should have been a value here"),
                Some(tr) => {
                    assert_eq!(
                        tr.timestamp(),
                        Timestamp::DateTime(
                            UTC.with_ymd_and_hms(2011, 10, 29, 0, 0, 0)
                                .unwrap()
                                .with_timezone(&FixedOffset::east_opt(0).unwrap())
                        )
                    );
                    assert_eq!(tr.data.duration, Duration(11040.0 * S));
                    assert_eq!(tr.data.comments, String::from("long time ago"));
                    assert_eq!(tr.data, trips[0]);
                }
            }
        })
    }

    #[test]
    pub fn can_search_for_an_entry_with_exact_time() {
        run_test(|path| {
            let trips = mk_trips();
            let mut ts: Series<BikeTrip> =
                Series::open(&path).expect("expect the time series to open correctly");

            for trip in &trips[0..=4] {
                ts.put(trip.clone()).expect("expect a successful put");
            }

            let v: Vec<&Record<BikeTrip>> = ts
                .search(exact_time(Timestamp::DateTime(
                    UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
                        .unwrap()
                        .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                )))
                .collect();
            assert_eq!(v.len(), 1);
            assert_eq!(v[0].data, trips[1]);
        })
    }

    #[test]
    pub fn can_get_entries_in_time_range() {
        run_test(|path| {
            let trips = mk_trips();
            let mut ts: Series<BikeTrip> =
                Series::open(&path).expect("expect the time series to open correctly");

            for trip in &trips[0..=4] {
                ts.put(trip.clone()).expect("expect a successful put");
            }

            let v: Vec<&Record<BikeTrip>> = ts.search_sorted(
                time_range(
                    Timestamp::DateTime(
                        UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
                            .unwrap()
                            .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                    ),
                    true,
                    Timestamp::DateTime(
                        UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
                            .unwrap()
                            .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                    ),
                    true,
                ),
                |l, r| l.timestamp().cmp(&r.timestamp()),
            );
            assert_eq!(v.len(), 3);
            assert_eq!(v[0].data, trips[1]);
            assert_eq!(v[1].data, trips[2]);
            assert_eq!(v[2].data, trips[3]);
        })
    }

    #[test]
    pub fn persists_and_reads_an_entry() {
        run_test(|path| {
            let trips = mk_trips();

            {
                let mut ts: Series<BikeTrip> =
                    Series::open(&path).expect("expect the time series to open correctly");

                for trip in &trips[0..=4] {
                    ts.put(trip.clone()).expect("expect a successful put");
                }
            }

            {
                let ts: Series<BikeTrip> =
                    Series::open(&path).expect("expect the time series to open correctly");
                let v: Vec<&Record<BikeTrip>> = ts.search_sorted(
                    time_range(
                        Timestamp::DateTime(
                            UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
                                .unwrap()
                                .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                        ),
                        true,
                        Timestamp::DateTime(
                            UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
                                .unwrap()
                                .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                        ),
                        true,
                    ),
                    |l, r| l.timestamp().cmp(&r.timestamp()),
                );
                assert_eq!(v.len(), 3);
                assert_eq!(v[0].data, trips[1]);
                assert_eq!(v[1].data, trips[2]);
                assert_eq!(v[2].data, trips[3]);
            }
        })
    }

    #[test]
    pub fn can_write_to_existing_file() {
        run_test(|path| {
            let trips = mk_trips();

            {
                let mut ts: Series<BikeTrip> =
                    Series::open(&path).expect("expect the time series to open correctly");

                for trip in &trips[0..=2] {
                    ts.put(trip.clone()).expect("expect a successful put");
                }
            }

            {
                let mut ts: Series<BikeTrip> =
                    Series::open(&path).expect("expect the time series to open correctly");
                let v: Vec<&Record<BikeTrip>> = ts.search_sorted(
                    time_range(
                        Timestamp::DateTime(
                            UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
                                .unwrap()
                                .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                        ),
                        true,
                        Timestamp::DateTime(
                            UTC.with_ymd_and_hms(2011, 11, 04, 0, 0, 0)
                                .unwrap()
                                .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                        ),
                        true,
                    ),
                    |l, r| l.timestamp().cmp(&r.timestamp()),
                );
                assert_eq!(v.len(), 2);
                assert_eq!(v[0].data, trips[1]);
                assert_eq!(v[1].data, trips[2]);
                ts.put(trips[3].clone()).expect("expect a successful put");
                ts.put(trips[4].clone()).expect("expect a successful put");
            }

            {
                let ts: Series<BikeTrip> =
                    Series::open(&path).expect("expect the time series to open correctly");
                let v: Vec<&Record<BikeTrip>> = ts.search_sorted(
                    time_range(
                        Timestamp::DateTime(
                            UTC.with_ymd_and_hms(2011, 10, 31, 0, 0, 0)
                                .unwrap()
                                .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                        ),
                        true,
                        Timestamp::DateTime(
                            UTC.with_ymd_and_hms(2011, 11, 05, 0, 0, 0)
                                .unwrap()
                                .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                        ),
                        true,
                    ),
                    |l, r| l.timestamp().cmp(&r.timestamp()),
                );
                assert_eq!(v.len(), 4);
                assert_eq!(v[0].data, trips[1]);
                assert_eq!(v[1].data, trips[2]);
                assert_eq!(v[2].data, trips[3]);
                assert_eq!(v[3].data, trips[4]);
            }
        })
    }

    #[test]
    pub fn can_overwrite_existing_entry() {
        run_test(|path| {
            let trips = mk_trips();

            let mut ts: Series<BikeTrip> =
                Series::open(&path).expect("expect the time series to open correctly");

            ts.put(trips[0].clone()).expect("expect a successful put");
            ts.put(trips[1].clone()).expect("expect a successful put");
            let trip_id = ts.put(trips[2].clone()).expect("expect a successful put");

            match ts.get(&trip_id) {
                None => assert!(false, "record not found"),
                Some(mut trip) => {
                    trip.data.distance = Distance(50000.0 * M);
                    ts.update(trip).expect("expect record to update");
                }
            };

            match ts.get(&trip_id) {
                None => assert!(false, "record not found"),
                Some(trip) => {
                    assert_eq!(
                        trip.data.datetime,
                        UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0).unwrap()
                    );
                    assert_eq!(trip.data.distance, Distance(50000.0 * M));
                    assert_eq!(trip.data.duration, Duration(7020.0 * S));
                    assert_eq!(trip.data.comments, String::from("Do Some Distance!"));
                }
            }
        })
    }

    #[test]
    pub fn record_overwrites_get_persisted() {
        run_test(|path| {
            let trips = mk_trips();

            {
                let mut ts: Series<BikeTrip> =
                    Series::open(&path).expect("expect the time series to open correctly");

                ts.put(trips[0].clone()).expect("expect a successful put");
                ts.put(trips[1].clone()).expect("expect a successful put");
                let trip_id = ts.put(trips[2].clone()).expect("expect a successful put");

                match ts.get(&trip_id) {
                    None => assert!(false, "record not found"),
                    Some(mut trip) => {
                        trip.data.distance = Distance(50000.0 * M);
                        ts.update(trip).expect("expect record to update");
                    }
                };
            }

            {
                let ts: Series<BikeTrip> =
                    Series::open(&path).expect("expect the time series to open correctly");

                let trips: Vec<&Record<BikeTrip>> = ts.records().collect();
                assert_eq!(trips.len(), 3);

                let trips: Vec<&Record<BikeTrip>> = ts
                    .search(exact_time(Timestamp::DateTime(
                        UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0)
                            .unwrap()
                            .with_timezone(&FixedOffset::east_opt(0).unwrap()),
                    )))
                    .collect();
                assert_eq!(trips.len(), 1);
                assert_eq!(
                    trips[0].data.datetime,
                    UTC.with_ymd_and_hms(2011, 11, 02, 0, 0, 0)
                        .unwrap()
                        .with_timezone(&FixedOffset::east_opt(0).unwrap())
                );
                assert_eq!(trips[0].data.distance, Distance(50000.0 * M));
                assert_eq!(trips[0].data.duration, Duration(7020.0 * S));
                assert_eq!(trips[0].data.comments, String::from("Do Some Distance!"));
            }
        })
    }

    #[test]
    pub fn can_delete_an_entry() {
        run_test(|path| {
            let trips = mk_trips();

            {
                let mut ts: Series<BikeTrip> =
                    Series::open(&path).expect("expect the time series to open correctly");
                let trip_id = ts.put(trips[0].clone()).expect("expect a successful put");
                ts.put(trips[1].clone()).expect("expect a successful put");
                ts.put(trips[2].clone()).expect("expect a successful put");
                ts.delete(&trip_id).expect("successful delete");

                let recs: Vec<&Record<BikeTrip>> = ts.records().collect();
                assert_eq!(recs.len(), 2);
            }

            {
                let ts: Series<BikeTrip> =
                    Series::open(&path).expect("expect the time series to open correctly");
                let recs: Vec<&Record<BikeTrip>> = ts.records().collect();
                assert_eq!(recs.len(), 2);
            }
        })
    }

    #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
    pub struct Weight(Kilogram<f64>);

    #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
    pub struct WeightRecord {
        pub date: chrono::NaiveDate,
        pub weight: Weight,
    }

    impl Recordable for WeightRecord {
        fn timestamp(&self) -> Timestamp {
            Timestamp::Date(self.date)
        }

        fn tags(&self) -> Vec<String> {
            Vec::new()
        }
    }

    /*
    #[test]
    pub fn legacy_file_load() {
        let ts: Series<WeightRecord> =
            Series::open("fixtures/weight.json").expect("legacy series should open correctly");

        let uid = "3330c5b0-783f-4919-b2c4-8169c38f65ff"
            .parse()
            .expect("something is wrong with this ID");
        let rec = ts.get(&uid);
        match rec {
            None => assert!(false, "no record found"),
            Some(rec) => assert_eq!(rec.weight, Weight(77.79109 * KG)),
        }
    }
    */
}
