use crate::core::{
error::ChartError,
model::{
bureau::FiveElementBureau,
calendar::BirthContext,
chart::palace::PalaceName,
chart::snapshot::ChartStackSnapshot,
profile::MethodProfile,
star::mutagen::{Mutagen, Scope},
star::{
Brightness, KnownStarFamily, StarCategory, StarKind, StarName, try_known_star_metadata,
},
},
};
use lunar_lite::{EarthlyBranch, HeavenlyStem, StemBranch};
use serde::{Deserialize, Deserializer, Serialize};
pub const PALACE_COUNT: usize = 12;
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Chart {
birth_context: BirthContext,
birth_year: StemBranch,
method_profile: MethodProfile,
palaces: Vec<Palace>,
body_palace_branch: Option<EarthlyBranch>,
five_element_bureau: Option<FiveElementBureau>,
}
impl Chart {
pub fn try_new(
birth_context: BirthContext,
birth_year: StemBranch,
method_profile: MethodProfile,
palaces: Vec<Palace>,
body_palace_branch: Option<EarthlyBranch>,
five_element_bureau: Option<FiveElementBureau>,
) -> Result<Self, ChartError> {
if palaces.len() != PALACE_COUNT {
return Err(ChartError::InvalidPalaceCount {
expected: PALACE_COUNT,
actual: palaces.len(),
});
}
Ok(Self {
birth_context,
birth_year,
method_profile,
palaces,
body_palace_branch,
five_element_bureau,
})
}
pub const fn birth_context(&self) -> &BirthContext {
&self.birth_context
}
pub const fn birth_year(&self) -> StemBranch {
self.birth_year
}
pub const fn method_profile(&self) -> &MethodProfile {
&self.method_profile
}
pub fn palaces(&self) -> &[Palace] {
&self.palaces
}
pub fn stack_snapshot(&self) -> ChartStackSnapshot {
ChartStackSnapshot::from_natal_chart(self)
}
pub const fn body_palace_branch(&self) -> Option<EarthlyBranch> {
self.body_palace_branch
}
pub fn is_body_palace_branch(&self, branch: EarthlyBranch) -> bool {
self.body_palace_branch == Some(branch)
}
pub fn body_palace(&self) -> Option<&Palace> {
let body_branch = self.body_palace_branch?;
self.palaces
.iter()
.find(|palace| palace.branch() == body_branch)
}
pub const fn five_element_bureau(&self) -> Option<FiveElementBureau> {
self.five_element_bureau
}
pub fn life_palace(&self) -> Option<&Palace> {
self.palaces
.iter()
.find(|palace| palace.name() == PalaceName::Life)
}
pub fn major_stars(&self) -> Vec<MajorStarPlacementRef<'_>> {
self.stars_by_category(StarCategory::Major)
}
pub fn major_star(&self, name: StarName) -> Option<MajorStarPlacementRef<'_>> {
self.star(name)
.filter(|fact| fact.placement().category() == StarCategory::Major)
}
pub fn palace_by_major_star(&self, name: StarName) -> Option<&Palace> {
self.major_star(name).map(|fact| fact.palace())
}
pub fn major_stars_in_palace(&self, name: PalaceName) -> Vec<MajorStarPlacementRef<'_>> {
self.stars_in_palace(name)
.into_iter()
.filter(|fact| fact.placement().category() == StarCategory::Major)
.collect()
}
pub fn major_stars_in_branch(&self, branch: EarthlyBranch) -> Vec<MajorStarPlacementRef<'_>> {
self.stars_in_branch(branch)
.into_iter()
.filter(|fact| fact.placement().category() == StarCategory::Major)
.collect()
}
pub fn stars(&self) -> Vec<StarPlacementRef<'_>> {
self.palaces.iter().flat_map(stars_in).collect()
}
pub fn star(&self, name: StarName) -> Option<StarPlacementRef<'_>> {
self.palaces.iter().find_map(|palace| {
palace
.stars()
.iter()
.find(|star| star.name() == name)
.map(|placement| StarPlacementRef::new(palace, placement))
})
}
pub fn palace_containing_star(&self, name: StarName) -> Option<&Palace> {
self.star(name).map(|fact| fact.palace())
}
pub fn stars_in_palace(&self, name: PalaceName) -> Vec<StarPlacementRef<'_>> {
self.palaces
.iter()
.filter(|palace| palace.name() == name)
.flat_map(stars_in)
.collect()
}
pub fn stars_in_branch(&self, branch: EarthlyBranch) -> Vec<StarPlacementRef<'_>> {
self.palaces
.iter()
.filter(|palace| palace.branch() == branch)
.flat_map(stars_in)
.collect()
}
pub fn stars_by_category(&self, category: StarCategory) -> Vec<StarPlacementRef<'_>> {
self.stars()
.into_iter()
.filter(|fact| fact.placement().category() == category)
.collect()
}
pub fn stars_by_kind(&self, kind: StarKind) -> Vec<StarPlacementRef<'_>> {
self.stars()
.into_iter()
.filter(|fact| fact.placement().kind() == kind)
.collect()
}
pub fn decorative_stars(&self) -> Vec<DecorativeStarPlacementRef<'_>> {
self.palaces.iter().flat_map(decorative_stars_in).collect()
}
pub fn decorative_star(&self, name: StarName) -> Option<DecorativeStarPlacementRef<'_>> {
self.palaces.iter().find_map(|palace| {
palace
.decorative_stars()
.iter()
.find(|star| star.name() == name)
.map(|placement| DecorativeStarPlacementRef::new(palace, placement))
})
}
}
fn stars_in(palace: &Palace) -> impl Iterator<Item = StarPlacementRef<'_>> {
palace
.stars()
.iter()
.map(|placement| StarPlacementRef::new(palace, placement))
}
fn decorative_stars_in(palace: &Palace) -> impl Iterator<Item = DecorativeStarPlacementRef<'_>> {
palace
.decorative_stars()
.iter()
.map(|placement| DecorativeStarPlacementRef::new(palace, placement))
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct StarPlacementRef<'a> {
palace: &'a Palace,
placement: &'a StarPlacement,
}
impl<'a> StarPlacementRef<'a> {
pub const fn new(palace: &'a Palace, placement: &'a StarPlacement) -> Self {
Self { palace, placement }
}
pub const fn palace(&self) -> &'a Palace {
self.palace
}
pub const fn placement(&self) -> &'a StarPlacement {
self.placement
}
}
pub type MajorStarPlacementRef<'a> = StarPlacementRef<'a>;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DecorativeStarPlacementRef<'a> {
palace: &'a Palace,
placement: &'a DecorativeStarPlacement,
}
impl<'a> DecorativeStarPlacementRef<'a> {
pub const fn new(palace: &'a Palace, placement: &'a DecorativeStarPlacement) -> Self {
Self { palace, placement }
}
pub const fn palace(&self) -> &'a Palace {
self.palace
}
pub const fn placement(&self) -> &'a DecorativeStarPlacement {
self.placement
}
pub const fn branch(&self) -> EarthlyBranch {
self.palace.branch()
}
pub const fn name(&self) -> StarName {
self.placement.name()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Palace {
name: PalaceName,
branch: EarthlyBranch,
stem: HeavenlyStem,
stars: Vec<StarPlacement>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
decorative_stars: Vec<DecorativeStarPlacement>,
}
impl Palace {
pub fn new(
name: PalaceName,
branch: EarthlyBranch,
stem: HeavenlyStem,
stars: Vec<StarPlacement>,
) -> Self {
Self {
name,
branch,
stem,
stars,
decorative_stars: Vec::new(),
}
}
pub fn with_decorative_stars(mut self, decorative_stars: Vec<DecorativeStarPlacement>) -> Self {
self.decorative_stars = decorative_stars;
self
}
pub const fn name(&self) -> PalaceName {
self.name
}
pub const fn branch(&self) -> EarthlyBranch {
self.branch
}
pub const fn stem(&self) -> HeavenlyStem {
self.stem
}
pub fn stars(&self) -> &[StarPlacement] {
&self.stars
}
pub fn decorative_stars(&self) -> &[DecorativeStarPlacement] {
&self.decorative_stars
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct StarPlacement {
name: StarName,
kind: StarKind,
brightness: Brightness,
mutagen: Option<Mutagen>,
scope: Scope,
}
impl StarPlacement {
pub const fn new(
name: StarName,
kind: StarKind,
brightness: Brightness,
mutagen: Option<Mutagen>,
scope: Scope,
) -> Self {
Self {
name,
kind,
brightness,
mutagen,
scope,
}
}
pub const fn name(&self) -> StarName {
self.name
}
pub const fn kind(&self) -> StarKind {
self.kind
}
pub const fn category(&self) -> StarCategory {
self.kind.category()
}
pub const fn brightness(&self) -> Brightness {
self.brightness
}
pub const fn mutagen(&self) -> Option<Mutagen> {
self.mutagen
}
pub const fn scope(&self) -> Scope {
self.scope
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DecorativeStarFamily {
Changsheng12,
Boshi12,
Suiqian12,
Jiangqian12,
}
impl DecorativeStarFamily {
pub const fn known_family(self) -> KnownStarFamily {
match self {
Self::Changsheng12 => KnownStarFamily::Changsheng12,
Self::Boshi12 => KnownStarFamily::Boshi12,
Self::Suiqian12 => KnownStarFamily::Suiqian12,
Self::Jiangqian12 => KnownStarFamily::Jiangqian12,
}
}
pub const fn from_known_family(family: KnownStarFamily) -> Option<Self> {
match family {
KnownStarFamily::Changsheng12 => Some(Self::Changsheng12),
KnownStarFamily::Boshi12 => Some(Self::Boshi12),
KnownStarFamily::Suiqian12 => Some(Self::Suiqian12),
KnownStarFamily::Jiangqian12 => Some(Self::Jiangqian12),
_ => None,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct DecorativeStarPlacement {
name: StarName,
family: DecorativeStarFamily,
scope: Scope,
}
impl DecorativeStarPlacement {
pub fn try_new(
name: StarName,
family: DecorativeStarFamily,
scope: Scope,
) -> Result<Self, ChartError> {
let metadata = try_known_star_metadata(name)
.ok_or(ChartError::InvalidDecorativeStarPlacement { star: name })?;
if metadata.family() != family.known_family() || metadata.kind().is_some() {
return Err(ChartError::InvalidDecorativeStarPlacement { star: name });
}
Ok(Self {
name,
family,
scope,
})
}
pub const fn name(&self) -> StarName {
self.name
}
pub const fn family(&self) -> DecorativeStarFamily {
self.family
}
pub const fn scope(&self) -> Scope {
self.scope
}
}
impl<'de> Deserialize<'de> for DecorativeStarPlacement {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct DecorativeStarPlacementData {
name: StarName,
family: DecorativeStarFamily,
scope: Scope,
}
let data = DecorativeStarPlacementData::deserialize(deserializer)?;
Self::try_new(data.name, data.family, data.scope).map_err(serde::de::Error::custom)
}
}