use serde::Serialize;
use crate::catalog::{DegreeRun, SignDegrees, DEGREE_QUALITIES};
use crate::pyfloat;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub struct DegreeQualities {
pub sign: &'static str,
pub degree: u8,
pub gender: &'static str,
pub light: &'static str,
pub pitted: bool,
pub azimene: bool,
pub fortune: bool,
}
pub fn degree_quality_table() -> &'static [SignDegrees; 12] {
&DEGREE_QUALITIES
}
pub fn degree_qualities(longitude: f64) -> DegreeQualities {
let lon = pyfloat::rem(longitude, 360.0);
let sign = &DEGREE_QUALITIES[(pyfloat::floordiv(lon, 30.0) as usize) % 12];
let degree = (pyfloat::floordiv(pyfloat::rem(lon, 30.0), 1.0) as u8 + 1).clamp(1, 30);
DegreeQualities {
sign: sign.sign,
degree,
gender: run_quality(sign.gender, degree),
light: run_quality(sign.light, degree),
pitted: sign.pitted.contains(°ree),
azimene: sign.azimene.contains(°ree),
fortune: sign.fortune.contains(°ree),
}
}
fn run_quality(runs: &[DegreeRun], degree: u8) -> &'static str {
runs.iter()
.find(|run| degree <= run.end)
.map_or("", |run| run.quality)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_sign_is_covered() {
for sign in degree_quality_table() {
for runs in [sign.gender, sign.light] {
assert!(
runs.windows(2).all(|w| w[0].end < w[1].end),
"{}",
sign.sign
);
assert!(runs.windows(2).all(|w| w[0].quality != w[1].quality));
assert_eq!(runs.last().unwrap().end, 30, "{}", sign.sign);
}
assert!(sign
.gender
.iter()
.all(|r| matches!(r.quality, "masculine" | "feminine")));
assert!(sign
.light
.iter()
.all(|r| matches!(r.quality, "light" | "dark" | "smoky" | "void")));
for list in [sign.pitted, sign.azimene, sign.fortune] {
assert!(list.windows(2).all(|w| w[0] < w[1]), "{}", sign.sign);
assert!(list.iter().all(|d| (1..=30).contains(d)));
}
}
let signs: Vec<_> = degree_quality_table().iter().map(|s| s.sign).collect();
assert_eq!(signs, crate::horary::SIGNS);
}
#[test]
fn degrees_are_ordinal() {
let sixth = degree_qualities(5.99);
assert_eq!((sixth.sign, sixth.degree), ("Aries", 6));
assert!(sixth.pitted);
let seventh = degree_qualities(6.0);
assert_eq!(seventh.degree, 7);
assert!(!seventh.pitted);
assert_eq!(degree_qualities(0.0).degree, 1);
assert_eq!(degree_qualities(29.999).degree, 30);
}
#[test]
fn runs_end_on_their_last_degree() {
assert_eq!(degree_qualities(7.5).gender, "masculine");
assert_eq!(degree_qualities(8.5).gender, "feminine");
assert_eq!(degree_qualities(9.0).gender, "masculine");
assert_eq!(degree_qualities(2.9).light, "dark");
assert_eq!(degree_qualities(3.0).light, "light");
}
#[test]
fn longitudes_wrap() {
assert_eq!(degree_qualities(360.0), degree_qualities(0.0));
assert_eq!(degree_qualities(-0.5).sign, "Pisces");
assert_eq!(degree_qualities(-0.5).degree, 30);
assert_eq!(degree_qualities(725.0), degree_qualities(5.0));
}
#[test]
fn spot_checks() {
let taurus_8th = degree_qualities(37.5);
assert!(taurus_8th.azimene && !taurus_8th.pitted);
assert_eq!(taurus_8th.light, "void");
assert!(degree_qualities(32.0).fortune);
let leo_28th = degree_qualities(147.2);
assert_eq!(leo_28th.sign, "Leo");
assert!(leo_28th.pitted && leo_28th.azimene);
assert_eq!(leo_28th.light, "light");
let cap_13th = degree_qualities(282.5);
assert_eq!((cap_13th.light, cap_13th.fortune), ("smoky", true));
assert_eq!(cap_13th.gender, "feminine");
let last = degree_qualities(329.9);
assert_eq!((last.gender, last.light), ("feminine", "light"));
}
}