use crate::core::{
error::ChartError,
model::{
chart::{Chart, StarPlacement},
star::StarName,
star::mutagen::{Mutagen, Scope},
},
};
use lunar_lite::{EarthlyBranch, StemBranch};
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TemporalContext {
Decadal {
stem_branch: StemBranch,
start_age: u8,
},
Yearly {
stem_branch: StemBranch,
lunar_year: i32,
},
Monthly {
stem_branch: StemBranch,
lunar_month: u8,
},
Daily {
stem_branch: StemBranch,
lunar_day: u8,
},
Hourly {
stem_branch: StemBranch,
},
}
impl TemporalContext {
pub const fn scope(&self) -> Scope {
match self {
Self::Decadal { .. } => Scope::Decadal,
Self::Yearly { .. } => Scope::Yearly,
Self::Monthly { .. } => Scope::Monthly,
Self::Daily { .. } => Scope::Daily,
Self::Hourly { .. } => Scope::Hourly,
}
}
pub const fn stem_branch(&self) -> StemBranch {
match self {
Self::Decadal { stem_branch, .. }
| Self::Yearly { stem_branch, .. }
| Self::Monthly { stem_branch, .. }
| Self::Daily { stem_branch, .. }
| Self::Hourly { stem_branch } => *stem_branch,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct MutagenActivation {
source_scope: Scope,
target_star: StarName,
target_branch: EarthlyBranch,
mutagen: Mutagen,
}
impl MutagenActivation {
pub const fn new(
source_scope: Scope,
target_star: StarName,
target_branch: EarthlyBranch,
mutagen: Mutagen,
) -> Self {
Self {
source_scope,
target_star,
target_branch,
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
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ScopedStarPlacement {
branch: EarthlyBranch,
placement: StarPlacement,
}
impl ScopedStarPlacement {
pub const fn new(branch: EarthlyBranch, placement: StarPlacement) -> Self {
Self { branch, placement }
}
pub const fn branch(&self) -> EarthlyBranch {
self.branch
}
pub const fn placement(&self) -> &StarPlacement {
&self.placement
}
pub const fn scope(&self) -> Scope {
self.placement.scope()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct TemporalLayer {
scope: Scope,
context: TemporalContext,
placements: Vec<ScopedStarPlacement>,
activations: Vec<MutagenActivation>,
}
impl TemporalLayer {
pub fn try_new(
scope: Scope,
context: TemporalContext,
placements: Vec<ScopedStarPlacement>,
activations: Vec<MutagenActivation>,
) -> Result<Self, ChartError> {
if scope == Scope::Natal {
return Err(ChartError::NatalScopeInTemporalLayer);
}
if scope != context.scope() {
return Err(ChartError::TemporalScopeMismatch {
layer: scope,
context: context.scope(),
});
}
if let Some(placement) = placements
.iter()
.find(|placement| placement.scope() != scope)
{
return Err(ChartError::TemporalPlacementScopeMismatch {
layer: scope,
placement: placement.scope(),
});
}
if let Some(activation) = activations
.iter()
.find(|activation| activation.source_scope() != scope)
{
return Err(ChartError::TemporalActivationScopeMismatch {
layer: scope,
activation: activation.source_scope(),
});
}
Ok(Self {
scope,
context,
placements,
activations,
})
}
pub const fn scope(&self) -> Scope {
self.scope
}
pub const fn context(&self) -> &TemporalContext {
&self.context
}
pub fn placements(&self) -> &[ScopedStarPlacement] {
&self.placements
}
pub fn activations(&self) -> &[MutagenActivation] {
&self.activations
}
}
impl<'de> Deserialize<'de> for TemporalLayer {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct TemporalLayerData {
scope: Scope,
context: TemporalContext,
placements: Vec<ScopedStarPlacement>,
activations: Vec<MutagenActivation>,
}
let data = TemporalLayerData::deserialize(deserializer)?;
TemporalLayer::try_new(data.scope, data.context, data.placements, data.activations)
.map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct HoroscopeChart {
natal: Chart,
layers: Vec<TemporalLayer>,
}
impl HoroscopeChart {
pub const fn new(natal: Chart) -> Self {
Self {
natal,
layers: Vec::new(),
}
}
pub const fn with_layers(natal: Chart, layers: Vec<TemporalLayer>) -> Self {
Self { natal, layers }
}
pub const fn natal(&self) -> &Chart {
&self.natal
}
pub fn layers(&self) -> &[TemporalLayer] {
&self.layers
}
pub fn push_layer(&mut self, layer: TemporalLayer) {
self.layers.push(layer);
}
pub fn layers_in_scope(&self, scope: Scope) -> impl Iterator<Item = &TemporalLayer> {
self.layers
.iter()
.filter(move |layer| layer.scope() == scope)
}
pub fn into_natal(self) -> Chart {
self.natal
}
}