use serde::{Deserialize, Serialize};
use crate::data::stars::StarKey;
use crate::data::types::*;
use crate::models::palace::PalaceData;
use crate::models::star::Star;
use crate::models::surpalaces::SurroundedPalaces;
use crate::utils::fix_index;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct RawLunarDate {
pub lunar_year: i64,
pub lunar_month: u32,
pub lunar_day: u32,
pub is_leap: bool,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct RawChineseDate {
pub yearly: (HeavenlyStem, EarthlyBranch),
pub monthly: (HeavenlyStem, EarthlyBranch),
pub daily: (HeavenlyStem, EarthlyBranch),
pub hourly: (HeavenlyStem, EarthlyBranch),
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct RawDates {
pub lunar_date: RawLunarDate,
pub chinese_date: RawChineseDate,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Astrolabe {
pub gender: Gender,
pub solar_date: String,
pub lunar_date: String,
pub chinese_date: String,
pub time: String,
pub time_range: String,
pub sign: String,
pub zodiac: String,
pub earthly_branch_of_soul_palace: EarthlyBranch,
pub earthly_branch_of_body_palace: EarthlyBranch,
pub soul: StarKey,
pub body: StarKey,
pub five_elements_class: FiveElementsClass,
pub palaces: [PalaceData; 12],
pub raw_dates: RawDates,
pub time_index: u8,
pub fix_leap: bool,
pub language: Language,
pub config: Config,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PalaceTarget {
Index(usize),
Name(Palace),
Body,
Original,
}
impl From<usize> for PalaceTarget {
fn from(index: usize) -> Self {
PalaceTarget::Index(index)
}
}
impl From<Palace> for PalaceTarget {
fn from(name: Palace) -> Self {
PalaceTarget::Name(name)
}
}
#[derive(Clone, Copy)]
pub struct PalaceRef<'a> {
palace: &'a PalaceData,
astrolabe: &'a Astrolabe,
}
impl<'a> std::ops::Deref for PalaceRef<'a> {
type Target = PalaceData;
fn deref(&self) -> &Self::Target {
self.palace
}
}
impl<'a> PalaceRef<'a> {
pub fn data(&self) -> &'a PalaceData {
self.palace
}
pub fn astrolabe(&self) -> &'a Astrolabe {
self.astrolabe
}
pub fn opposite_palace(&self) -> PalaceRef<'a> {
self.astrolabe
.palace_at(fix_index(self.palace.index as i32 + 6, 12))
}
pub fn surrounded_palaces(&self) -> SurroundedPalaces<'a> {
self.astrolabe.surrounded_at(self.palace.index)
}
pub fn flies_to(&self, target: impl Into<PalaceTarget>, mutagens: &[Mutagen]) -> bool {
match self.astrolabe.palace(target) {
Some(to) => self.palace.flies_to(&to, mutagens),
None => false,
}
}
pub fn flies_one_of_to(&self, target: impl Into<PalaceTarget>, mutagens: &[Mutagen]) -> bool {
match self.astrolabe.palace(target) {
Some(to) => self.palace.flies_one_of_to(&to, mutagens),
None => false,
}
}
pub fn not_fly_to(&self, target: impl Into<PalaceTarget>, mutagens: &[Mutagen]) -> bool {
match self.astrolabe.palace(target) {
Some(to) => self.palace.not_fly_to(&to, mutagens),
None => false,
}
}
pub fn mutaged_places(&self) -> Vec<Option<PalaceRef<'a>>> {
self.palace
.mutaged_places(&self.astrolabe.palaces)
.into_iter()
.map(|idx| idx.map(|i| self.astrolabe.palace_at(i)))
.collect()
}
}
#[derive(Clone, Copy)]
pub struct StarRef<'a> {
star: &'a Star,
palace: PalaceRef<'a>,
}
impl<'a> std::ops::Deref for StarRef<'a> {
type Target = Star;
fn deref(&self) -> &Self::Target {
self.star
}
}
impl<'a> StarRef<'a> {
pub fn palace(&self) -> PalaceRef<'a> {
self.palace
}
pub fn opposite_palace(&self) -> PalaceRef<'a> {
self.palace.opposite_palace()
}
pub fn surrounded_palaces(&self) -> SurroundedPalaces<'a> {
self.palace.surrounded_palaces()
}
}
impl Astrolabe {
pub fn palace(&self, target: impl Into<PalaceTarget>) -> Option<PalaceRef<'_>> {
let palace = match target.into() {
PalaceTarget::Index(index) => self.palaces.get(index),
PalaceTarget::Name(name) => self.palaces.iter().find(|p| p.name == name),
PalaceTarget::Body => self.palaces.iter().find(|p| p.is_body_palace),
PalaceTarget::Original => self.palaces.iter().find(|p| p.is_original_palace),
}?;
Some(PalaceRef {
palace,
astrolabe: self,
})
}
pub(crate) fn palace_at(&self, index: usize) -> PalaceRef<'_> {
PalaceRef {
palace: &self.palaces[fix_index(index as i32, 12)],
astrolabe: self,
}
}
fn surrounded_at(&self, index: usize) -> SurroundedPalaces<'_> {
SurroundedPalaces {
target: &self.palaces[fix_index(index as i32, 12)],
opposite: &self.palaces[fix_index(index as i32 + 6, 12)],
career: &self.palaces[fix_index(index as i32 + 4, 12)],
wealth: &self.palaces[fix_index(index as i32 + 8, 12)],
}
}
pub fn surrounded_palaces(
&self,
target: impl Into<PalaceTarget>,
) -> Option<SurroundedPalaces<'_>> {
let palace = self.palace(target)?;
Some(self.surrounded_at(palace.index))
}
pub fn is_surrounded(&self, target: impl Into<PalaceTarget>, stars: &[StarKey]) -> bool {
self.surrounded_palaces(target)
.is_some_and(|sp| sp.have(stars))
}
pub fn is_surrounded_one_of(&self, target: impl Into<PalaceTarget>, stars: &[StarKey]) -> bool {
self.surrounded_palaces(target)
.is_some_and(|sp| sp.have_one_of(stars))
}
pub fn not_surrounded(&self, target: impl Into<PalaceTarget>, stars: &[StarKey]) -> bool {
self.surrounded_palaces(target)
.is_some_and(|sp| sp.not_have(stars))
}
pub fn star(&self, key: StarKey) -> Option<StarRef<'_>> {
for p in &self.palaces {
for s in p
.major_stars
.iter()
.chain(p.minor_stars.iter())
.chain(p.adjective_stars.iter())
{
if s.key == key {
return Some(StarRef {
star: s,
palace: self.palace_at(p.index),
});
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::astro::builder::by_solar;
use crate::data::stars::StarKey;
fn make_astrolabe() -> super::Astrolabe {
by_solar(
"2000-8-16",
2,
Gender::Female,
true,
Language::ZhCN,
Config::default(),
)
.unwrap()
}
#[test]
fn test_palace_by_index() {
let a = make_astrolabe();
for i in 0..12 {
assert_eq!(a.palace(i).unwrap().index, i);
}
assert!(a.palace(12).is_none());
}
#[test]
fn test_palace_by_body_and_original() {
let a = make_astrolabe();
let body = a.palace(PalaceTarget::Body).expect("每张盘必有身宫");
assert!(body.is_body_palace);
if let Some(original) = a.palace(PalaceTarget::Original) {
assert!(original.is_original_palace);
}
}
#[test]
fn test_palace_by_name() {
let a = make_astrolabe();
let soul = a.palace(Palace::Soul);
assert!(soul.is_some());
assert_eq!(soul.unwrap().name, Palace::Soul);
let names = [
Palace::Soul,
Palace::Parents,
Palace::Spirit,
Palace::Property,
Palace::Career,
Palace::Friends,
Palace::Surface,
Palace::Health,
Palace::Wealth,
Palace::Children,
Palace::Spouse,
Palace::Siblings,
];
for name in &names {
assert!(a.palace(*name).is_some());
}
}
#[test]
fn test_star_lookup() {
let a = make_astrolabe();
let major_keys = [
StarKey::ZiweiMaj,
StarKey::TianjiMaj,
StarKey::TaiyangMaj,
StarKey::WuquMaj,
StarKey::TiantongMaj,
StarKey::LianzhenMaj,
StarKey::TianfuMaj,
StarKey::TaiyinMaj,
StarKey::TanlangMaj,
StarKey::JumenMaj,
StarKey::TianxiangMaj,
StarKey::TianliangMaj,
StarKey::QishaMaj,
StarKey::PojunMaj,
];
for key in &major_keys {
let star = a.star(*key);
assert!(star.is_some(), "Star {:?} should be found", key);
let star = star.unwrap();
assert_eq!(star.key, *key);
assert!(star.palace().has(&[*key]));
assert_eq!(
star.opposite_palace().index,
(star.palace().index + 6) % 12,
"Star {:?} opposite palace mismatch",
key
);
assert_eq!(star.surrounded_palaces().target.index, star.palace().index);
}
}
#[test]
fn test_palace_ref_back_reference() {
let a = make_astrolabe();
let soul = a.palace(Palace::Soul).unwrap();
assert_eq!(soul.astrolabe().solar_date, a.solar_date);
assert_eq!(soul.name, Palace::Soul);
assert_eq!(soul.opposite_palace().index, (soul.index + 6) % 12);
assert_eq!(soul.surrounded_palaces().target.index, soul.index);
}
#[test]
fn test_palace_ref_flies_to_accepts_target() {
let a = make_astrolabe();
let soul = a.palace(Palace::Soul).unwrap();
let career = a.palace(Palace::Career).unwrap();
for m in [Mutagen::Lu, Mutagen::Quan, Mutagen::Ke, Mutagen::Ji] {
let expected = soul.data().flies_to(&career, &[m]);
assert_eq!(soul.flies_to(career.index, &[m]), expected);
assert_eq!(soul.flies_to(Palace::Career, &[m]), expected);
let expected_one = soul.data().flies_one_of_to(&career, &[m]);
assert_eq!(soul.flies_one_of_to(Palace::Career, &[m]), expected_one);
assert_eq!(
soul.not_fly_to(Palace::Career, &[m]),
soul.data().not_fly_to(&career, &[m])
);
}
assert!(!soul.flies_to(99, &[Mutagen::Lu]));
assert!(!soul.flies_one_of_to(99, &[Mutagen::Lu]));
assert!(!soul.not_fly_to(99, &[Mutagen::Lu]));
}
#[test]
fn test_palace_ref_mutaged_places() {
let a = make_astrolabe();
let soul = a.palace(Palace::Soul).unwrap();
let places = soul.mutaged_places();
assert_eq!(places.len(), 4);
let indices = soul.data().mutaged_places(&a.palaces);
for (place, index) in places.iter().zip(indices.iter()) {
assert_eq!(place.map(|p| p.index), *index);
}
assert!(places.iter().all(|p| p.is_some()));
}
#[test]
fn test_surrounded_palaces_indices() {
let a = make_astrolabe();
let sp = a.surrounded_palaces(3).unwrap();
assert_eq!(sp.target.index, 3);
assert_eq!(sp.opposite.index, 9); assert_eq!(sp.career.index, 7); assert_eq!(sp.wealth.index, 11);
let soul = a.palace(Palace::Soul).unwrap();
let by_name = a.surrounded_palaces(Palace::Soul).unwrap();
assert_eq!(by_name.target.index, soul.index);
}
}