//! CONUS grid at 0.125° resolution. 1:1 port of
//! `lib/microwaveprop/propagation/grid.ex`.

pub const LAT_MIN: f64 = 25.0;
pub const LAT_MAX: f64 = 50.0;
pub const LON_MIN: f64 = -125.0;
pub const LON_MAX: f64 = -66.0;
pub const STEP: f64 = 0.125;

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GridSpec {
    pub lon_start: f64,
    pub lon_count: usize,
    pub lon_step: f64,
    pub lat_start: f64,
    pub lat_count: usize,
    pub lat_step: f64,
}

pub fn wgrib2_grid_spec() -> GridSpec {
    let lon_count = ((LON_MAX - LON_MIN) / STEP).round() as usize + 1;
    let lat_count = ((LAT_MAX - LAT_MIN) / STEP).round() as usize + 1;
    GridSpec {
        lon_start: LON_MIN,
        lon_count,
        lon_step: STEP,
        lat_start: LAT_MIN,
        lat_count,
        lat_step: STEP,
    }
}

pub fn conus_points() -> Vec<(f64, f64)> {
    let spec = wgrib2_grid_spec();
    let mut out = Vec::with_capacity(spec.lat_count * spec.lon_count);
    for j in 0..spec.lat_count {
        let lat = round3(spec.lat_start + j as f64 * spec.lat_step);
        for i in 0..spec.lon_count {
            let lon = round3(spec.lon_start + i as f64 * spec.lon_step);
            out.push((lat, lon));
        }
    }
    out
}

/// Matches Elixir's `Float.round(x, 3)` — banker's rounding isn't used,
/// half-away-from-zero is. For grid coords this matches BEAM's behavior
/// for the values we care about (no exact half cases).
pub fn round3(x: f64) -> f64 {
    (x * 1000.0).round() / 1000.0
}

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

    #[test]
    fn grid_spec_matches_elixir() {
        let spec = wgrib2_grid_spec();
        // Elixir: lon_count = round((-66 - -125) / 0.125) + 1 = 473
        //        lat_count = round((50 - 25) / 0.125) + 1 = 201
        assert_eq!(spec.lon_count, 473);
        assert_eq!(spec.lat_count, 201);
        assert_eq!(spec.lon_start, -125.0);
        assert_eq!(spec.lat_start, 25.0);
        assert_eq!(spec.lon_step, 0.125);
        assert_eq!(spec.lat_step, 0.125);
    }

    #[test]
    fn conus_points_count_matches_grid_spec() {
        let points = conus_points();
        assert_eq!(points.len(), 473 * 201);
    }

    #[test]
    fn conus_points_corner_values() {
        let points = conus_points();
        assert_eq!(points[0], (25.0, -125.0));
        assert_eq!(points[points.len() - 1], (50.0, -66.0));
    }
}
