use crate::core::model::ganzhi::{EarthlyBranch, FourPillars, HeavenlyStem, StemBranch};
use crate::core::model::{
bureau::FiveElementBureau,
calendar::BirthContext,
chart::{
Chart, DecorativeStarPlacement, HoroscopeChart, MutagenActivation, PalaceName,
ScopedStarPlacement, StarPlacement, TemporalContext, TemporalLayer,
},
profile::MethodProfile,
star::{
Brightness, StarCategory, StarKind, StarName,
mutagen::{Mutagen, Scope},
},
};
use serde::{Deserialize, Serialize};
pub const VISUAL_BRANCH_ORDER: [EarthlyBranch; 12] = [
EarthlyBranch::Si,
EarthlyBranch::Wu,
EarthlyBranch::Wei,
EarthlyBranch::Shen,
EarthlyBranch::Chen,
EarthlyBranch::You,
EarthlyBranch::Mao,
EarthlyBranch::Xu,
EarthlyBranch::Yin,
EarthlyBranch::Chou,
EarthlyBranch::Zi,
EarthlyBranch::Hai,
];
pub const fn palace_grid_position(branch: EarthlyBranch) -> PalaceGridPosition {
match branch {
EarthlyBranch::Si => PalaceGridPosition::new(0, 0),
EarthlyBranch::Wu => PalaceGridPosition::new(0, 1),
EarthlyBranch::Wei => PalaceGridPosition::new(0, 2),
EarthlyBranch::Shen => PalaceGridPosition::new(0, 3),
EarthlyBranch::Chen => PalaceGridPosition::new(1, 0),
EarthlyBranch::You => PalaceGridPosition::new(1, 3),
EarthlyBranch::Mao => PalaceGridPosition::new(2, 0),
EarthlyBranch::Xu => PalaceGridPosition::new(2, 3),
EarthlyBranch::Yin => PalaceGridPosition::new(3, 0),
EarthlyBranch::Chou => PalaceGridPosition::new(3, 1),
EarthlyBranch::Zi => PalaceGridPosition::new(3, 2),
EarthlyBranch::Hai => PalaceGridPosition::new(3, 3),
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ChartStackSnapshot {
birth_context: BirthContext,
birth_year: StemBranch,
#[serde(default)]
four_pillars: Option<FourPillars>,
method_profile: MethodProfile,
life_palace_branch: Option<EarthlyBranch>,
body_palace_branch: Option<EarthlyBranch>,
five_element_bureau: Option<FiveElementBureau>,
layers: Vec<ChartLayerSnapshot>,
}
impl ChartStackSnapshot {
pub fn from_natal_chart(chart: &Chart) -> Self {
Self {
birth_context: chart.birth_context().clone(),
birth_year: chart.birth_year(),
four_pillars: chart.four_pillars().copied(),
method_profile: chart.method_profile().clone(),
life_palace_branch: chart.life_palace().map(|palace| palace.branch()),
body_palace_branch: chart.body_palace_branch(),
five_element_bureau: chart.five_element_bureau(),
layers: vec![ChartLayerSnapshot::from_natal_chart(chart)],
}
}
pub fn from_horoscope_chart(chart: &HoroscopeChart) -> Self {
let natal = chart.natal();
let mut layers = Vec::with_capacity(chart.layers().len() + 1);
layers.push(ChartLayerSnapshot::from_natal_chart(natal));
layers.extend(chart.layers().iter().enumerate().map(|(index, layer)| {
ChartLayerSnapshot::from_temporal_layer(natal, layer, index + 1)
}));
Self {
birth_context: natal.birth_context().clone(),
birth_year: natal.birth_year(),
four_pillars: natal.four_pillars().copied(),
method_profile: natal.method_profile().clone(),
life_palace_branch: natal.life_palace().map(|palace| palace.branch()),
body_palace_branch: natal.body_palace_branch(),
five_element_bureau: natal.five_element_bureau(),
layers,
}
}
pub const fn birth_context(&self) -> &BirthContext {
&self.birth_context
}
pub const fn birth_year(&self) -> StemBranch {
self.birth_year
}
pub const fn four_pillars(&self) -> Option<&FourPillars> {
self.four_pillars.as_ref()
}
pub const fn method_profile(&self) -> &MethodProfile {
&self.method_profile
}
pub const fn life_palace_branch(&self) -> Option<EarthlyBranch> {
self.life_palace_branch
}
pub const fn body_palace_branch(&self) -> Option<EarthlyBranch> {
self.body_palace_branch
}
pub const fn five_element_bureau(&self) -> Option<FiveElementBureau> {
self.five_element_bureau
}
pub fn layers(&self) -> &[ChartLayerSnapshot] {
&self.layers
}
pub fn layer(&self, kind: ChartLayerKind) -> Option<&ChartLayerSnapshot> {
self.layers.iter().find(|layer| layer.kind == kind)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ChartLayerSnapshot {
kind: ChartLayerKind,
z_index: usize,
context: Option<TemporalContext>,
cells: Vec<PalaceLayerCellSnapshot>,
}
impl ChartLayerSnapshot {
fn from_natal_chart(chart: &Chart) -> Self {
let cells = VISUAL_BRANCH_ORDER
.into_iter()
.map(|branch| PalaceLayerCellSnapshot::from_natal_chart(chart, branch))
.collect();
Self {
kind: ChartLayerKind::Natal,
z_index: 0,
context: None,
cells,
}
}
fn from_temporal_layer(chart: &Chart, layer: &TemporalLayer, z_index: usize) -> Self {
let cells = VISUAL_BRANCH_ORDER
.into_iter()
.map(|branch| PalaceLayerCellSnapshot::from_temporal_layer(chart, layer, branch))
.collect();
Self {
kind: ChartLayerKind::from_scope(layer.scope()),
z_index,
context: Some(*layer.context()),
cells,
}
}
pub const fn kind(&self) -> ChartLayerKind {
self.kind
}
pub const fn z_index(&self) -> usize {
self.z_index
}
pub const fn context(&self) -> Option<&TemporalContext> {
self.context.as_ref()
}
pub fn cells(&self) -> &[PalaceLayerCellSnapshot] {
&self.cells
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChartLayerKind {
Natal,
Age,
Decadal,
Yearly,
Monthly,
Daily,
Hourly,
}
impl ChartLayerKind {
pub const fn from_scope(scope: Scope) -> Self {
match scope {
Scope::Natal => Self::Natal,
Scope::Age => Self::Age,
Scope::Decadal => Self::Decadal,
Scope::Yearly => Self::Yearly,
Scope::Monthly => Self::Monthly,
Scope::Daily => Self::Daily,
Scope::Hourly => Self::Hourly,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct PalaceGridPosition {
row: u8,
column: u8,
}
impl PalaceGridPosition {
pub const fn new(row: u8, column: u8) -> Self {
Self { row, column }
}
pub const fn row(&self) -> u8 {
self.row
}
pub const fn column(&self) -> u8 {
self.column
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct PalaceLayerCellSnapshot {
branch: EarthlyBranch,
grid_position: PalaceGridPosition,
natal_palace_name: Option<PalaceName>,
natal_palace_stem: Option<HeavenlyStem>,
temporal_palace_name: Option<PalaceName>,
roles: Vec<PalaceRoleSnapshot>,
typed_stars: Vec<TypedStarSnapshot>,
decorative_stars: Vec<DecorativeStarSnapshot>,
scoped_stars: Vec<ScopedStarSnapshot>,
temporal_decorative_stars: Vec<DecorativeStarSnapshot>,
mutagen_activations: Vec<MutagenActivationSnapshot>,
}
impl PalaceLayerCellSnapshot {
fn from_natal_chart(chart: &Chart, branch: EarthlyBranch) -> Self {
let palace = chart
.palaces()
.iter()
.find(|palace| palace.branch() == branch);
let mut roles = Vec::new();
if let Some(palace) = palace {
roles.push(PalaceRoleSnapshot::new(PalaceRoleKind::NatalPalace(
palace.name(),
)));
}
if chart.is_body_palace_branch(branch) {
roles.push(PalaceRoleSnapshot::new(PalaceRoleKind::NatalBodyPalace));
}
Self {
branch,
grid_position: palace_grid_position(branch),
natal_palace_name: palace.map(|palace| palace.name()),
natal_palace_stem: palace.map(|palace| palace.stem()),
temporal_palace_name: None,
roles,
typed_stars: palace
.map(|palace| {
palace
.stars()
.iter()
.map(TypedStarSnapshot::from_star_placement)
.collect()
})
.unwrap_or_default(),
decorative_stars: palace
.map(|palace| {
palace
.decorative_stars()
.iter()
.map(DecorativeStarSnapshot::from_decorative_star_placement)
.collect()
})
.unwrap_or_default(),
scoped_stars: Vec::new(),
temporal_decorative_stars: Vec::new(),
mutagen_activations: Vec::new(),
}
}
fn from_temporal_layer(chart: &Chart, layer: &TemporalLayer, branch: EarthlyBranch) -> Self {
let palace = chart
.palaces()
.iter()
.find(|palace| palace.branch() == branch);
Self {
branch,
grid_position: palace_grid_position(branch),
natal_palace_name: palace.map(|palace| palace.name()),
natal_palace_stem: palace.map(|palace| palace.stem()),
temporal_palace_name: layer
.palace_layout()
.and_then(|layout| layout.name_for_branch(branch)),
roles: Vec::new(),
typed_stars: Vec::new(),
decorative_stars: Vec::new(),
scoped_stars: layer
.placements()
.iter()
.filter(|placement| placement.branch() == branch)
.map(ScopedStarSnapshot::from_scoped_star_placement)
.collect(),
temporal_decorative_stars: layer
.temporal_decorative_stars()
.iter()
.filter(|placement| placement.branch() == branch)
.map(|placement| {
DecorativeStarSnapshot::from_decorative_star_placement(placement.placement())
})
.collect(),
mutagen_activations: layer
.activations()
.iter()
.filter(|activation| activation.target_branch() == branch)
.map(MutagenActivationSnapshot::from_mutagen_activation)
.collect(),
}
}
pub const fn branch(&self) -> EarthlyBranch {
self.branch
}
pub const fn grid_position(&self) -> PalaceGridPosition {
self.grid_position
}
pub const fn natal_palace_name(&self) -> Option<PalaceName> {
self.natal_palace_name
}
pub const fn natal_palace_stem(&self) -> Option<HeavenlyStem> {
self.natal_palace_stem
}
pub const fn temporal_palace_name(&self) -> Option<PalaceName> {
self.temporal_palace_name
}
pub fn roles(&self) -> &[PalaceRoleSnapshot] {
&self.roles
}
pub fn typed_stars(&self) -> &[TypedStarSnapshot] {
&self.typed_stars
}
pub fn decorative_stars(&self) -> &[DecorativeStarSnapshot] {
&self.decorative_stars
}
pub fn temporal_decorative_stars(&self) -> &[DecorativeStarSnapshot] {
&self.temporal_decorative_stars
}
pub fn scoped_stars(&self) -> &[ScopedStarSnapshot] {
&self.scoped_stars
}
pub fn mutagen_activations(&self) -> &[MutagenActivationSnapshot] {
&self.mutagen_activations
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct PalaceRoleSnapshot {
kind: PalaceRoleKind,
}
impl PalaceRoleSnapshot {
pub const fn new(kind: PalaceRoleKind) -> Self {
Self { kind }
}
pub const fn kind(&self) -> PalaceRoleKind {
self.kind
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PalaceRoleKind {
NatalPalace(PalaceName),
NatalBodyPalace,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct TypedStarSnapshot {
name: StarName,
kind: StarKind,
category: StarCategory,
brightness: Brightness,
mutagen: Option<Mutagen>,
scope: Scope,
}
impl TypedStarSnapshot {
fn from_star_placement(placement: &StarPlacement) -> Self {
Self {
name: placement.name(),
kind: placement.kind(),
category: placement.category(),
brightness: placement.brightness(),
mutagen: placement.mutagen(),
scope: placement.scope(),
}
}
pub const fn name(&self) -> StarName {
self.name
}
pub const fn kind(&self) -> StarKind {
self.kind
}
pub const fn category(&self) -> StarCategory {
self.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, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct DecorativeStarSnapshot {
name: StarName,
family: crate::core::model::chart::DecorativeStarFamily,
scope: Scope,
}
impl DecorativeStarSnapshot {
fn from_decorative_star_placement(placement: &DecorativeStarPlacement) -> Self {
Self {
name: placement.name(),
family: placement.family(),
scope: placement.scope(),
}
}
pub const fn name(&self) -> StarName {
self.name
}
pub const fn family(&self) -> crate::core::model::chart::DecorativeStarFamily {
self.family
}
pub const fn scope(&self) -> Scope {
self.scope
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ScopedStarSnapshot {
name: StarName,
kind: StarKind,
category: StarCategory,
brightness: Brightness,
mutagen: Option<Mutagen>,
scope: Scope,
}
impl ScopedStarSnapshot {
fn from_scoped_star_placement(placement: &ScopedStarPlacement) -> Self {
Self {
name: placement.placement().name(),
kind: placement.placement().kind(),
category: placement.placement().category(),
brightness: placement.placement().brightness(),
mutagen: placement.placement().mutagen(),
scope: placement.scope(),
}
}
pub const fn name(&self) -> StarName {
self.name
}
pub const fn kind(&self) -> StarKind {
self.kind
}
pub const fn category(&self) -> StarCategory {
self.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)]
pub struct MutagenActivationSnapshot {
source_scope: Scope,
target_star: StarName,
target_branch: EarthlyBranch,
mutagen: Mutagen,
}
impl MutagenActivationSnapshot {
fn from_mutagen_activation(activation: &MutagenActivation) -> Self {
Self {
source_scope: activation.source_scope(),
target_star: activation.target_star(),
target_branch: activation.target_branch(),
mutagen: activation.mutagen(),
}
}
pub const fn source_scope(&self) -> Scope {
self.source_scope
}
pub const fn target_star(&self) -> StarName {
self.target_star
}
pub const fn target_branch(&self) -> EarthlyBranch {
self.target_branch
}
pub const fn mutagen(&self) -> Mutagen {
self.mutagen
}
}