use crate::core::model::ganzhi::{EarthlyBranch, StemBranch};
use crate::core::{
error::ChartError,
model::{
chart::{
Chart, DecorativeStarFamily, DecorativeStarPlacement, PALACE_COUNT, PalaceName,
StarPlacement,
},
star::StarName,
star::mutagen::{Mutagen, Scope},
},
};
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct HoroscopeTargetContext {
solar_date: HoroscopeSolarDate,
lunar_date: HoroscopeLunarDate,
time_index: u8,
}
impl HoroscopeTargetContext {
pub const fn new(
solar_date: HoroscopeSolarDate,
lunar_date: HoroscopeLunarDate,
time_index: u8,
) -> Self {
Self {
solar_date,
lunar_date,
time_index,
}
}
pub const fn solar_date(&self) -> HoroscopeSolarDate {
self.solar_date
}
pub const fn lunar_date(&self) -> HoroscopeLunarDate {
self.lunar_date
}
pub const fn time_index(&self) -> u8 {
self.time_index
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct HoroscopeSolarDate {
year: i32,
month: u8,
day: u8,
}
impl HoroscopeSolarDate {
pub const fn new(year: i32, month: u8, day: u8) -> Self {
Self { year, month, day }
}
pub const fn year(self) -> i32 {
self.year
}
pub const fn month(self) -> u8 {
self.month
}
pub const fn day(self) -> u8 {
self.day
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct HoroscopeLunarDate {
year: i32,
month: u8,
day: u8,
is_leap_month: bool,
}
impl HoroscopeLunarDate {
pub const fn new(year: i32, month: u8, day: u8, is_leap_month: bool) -> Self {
Self {
year,
month,
day,
is_leap_month,
}
}
pub const fn year(self) -> i32 {
self.year
}
pub const fn month(self) -> u8 {
self.month
}
pub const fn day(self) -> u8 {
self.day
}
pub const fn is_leap_month(self) -> bool {
self.is_leap_month
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TemporalContext {
Age {
stem_branch: StemBranch,
nominal_age: u8,
},
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::Age { .. } => Scope::Age,
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::Age { stem_branch, .. }
| 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 ScopedDecorativeStarPlacement {
branch: EarthlyBranch,
placement: DecorativeStarPlacement,
}
impl ScopedDecorativeStarPlacement {
pub fn try_new(
branch: EarthlyBranch,
placement: DecorativeStarPlacement,
) -> Result<Self, ChartError> {
if placement.scope() == Scope::Natal {
return Err(ChartError::NatalScopeInTemporalLayer);
}
Ok(Self { branch, placement })
}
pub const fn branch(&self) -> EarthlyBranch {
self.branch
}
pub const fn placement(&self) -> &DecorativeStarPlacement {
&self.placement
}
pub const fn scope(&self) -> Scope {
self.placement.scope()
}
pub const fn name(&self) -> StarName {
self.placement.name()
}
pub const fn family(&self) -> DecorativeStarFamily {
self.placement.family()
}
}
impl<'de> Deserialize<'de> for ScopedDecorativeStarPlacement {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct ScopedDecorativeStarPlacementData {
branch: EarthlyBranch,
placement: DecorativeStarPlacement,
}
let data = ScopedDecorativeStarPlacementData::deserialize(deserializer)?;
ScopedDecorativeStarPlacement::try_new(data.branch, data.placement)
.map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct TemporalPalaceName {
branch: EarthlyBranch,
palace_name: PalaceName,
}
impl TemporalPalaceName {
pub const fn new(branch: EarthlyBranch, palace_name: PalaceName) -> Self {
Self {
branch,
palace_name,
}
}
pub const fn branch(&self) -> EarthlyBranch {
self.branch
}
pub const fn palace_name(&self) -> PalaceName {
self.palace_name
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct TemporalPalaceLayout {
scope: Scope,
names: Vec<TemporalPalaceName>,
}
impl TemporalPalaceLayout {
pub fn try_new(scope: Scope, names: Vec<TemporalPalaceName>) -> Result<Self, ChartError> {
if scope == Scope::Natal {
return Err(ChartError::NatalScopeInTemporalLayer);
}
if names.len() != PALACE_COUNT {
return Err(ChartError::InvalidTemporalPalaceLayoutCount {
expected: PALACE_COUNT,
actual: names.len(),
});
}
for (index, name) in names.iter().enumerate() {
if names[..index]
.iter()
.any(|seen| seen.branch() == name.branch())
{
return Err(ChartError::DuplicateTemporalPalaceLayoutBranch {
branch: name.branch(),
});
}
if names[..index]
.iter()
.any(|seen| seen.palace_name() == name.palace_name())
{
return Err(ChartError::DuplicateTemporalPalaceLayoutName {
palace_name: name.palace_name(),
});
}
}
Ok(Self { scope, names })
}
pub const fn scope(&self) -> Scope {
self.scope
}
pub fn names(&self) -> &[TemporalPalaceName] {
&self.names
}
pub fn name_for_branch(&self, branch: EarthlyBranch) -> Option<PalaceName> {
self.names
.iter()
.find(|name| name.branch() == branch)
.map(TemporalPalaceName::palace_name)
}
}
impl<'de> Deserialize<'de> for TemporalPalaceLayout {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct TemporalPalaceLayoutData {
scope: Scope,
names: Vec<TemporalPalaceName>,
}
let data = TemporalPalaceLayoutData::deserialize(deserializer)?;
TemporalPalaceLayout::try_new(data.scope, data.names).map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct TemporalLayer {
scope: Scope,
context: TemporalContext,
placements: Vec<ScopedStarPlacement>,
activations: Vec<MutagenActivation>,
palace_layout: Option<TemporalPalaceLayout>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
decorative_placements: Vec<ScopedDecorativeStarPlacement>,
}
impl TemporalLayer {
pub fn try_new(
scope: Scope,
context: TemporalContext,
placements: Vec<ScopedStarPlacement>,
activations: Vec<MutagenActivation>,
) -> Result<Self, ChartError> {
Self::try_new_with_palace_layout(scope, context, placements, activations, None)
}
pub fn try_new_with_palace_layout(
scope: Scope,
context: TemporalContext,
placements: Vec<ScopedStarPlacement>,
activations: Vec<MutagenActivation>,
palace_layout: Option<TemporalPalaceLayout>,
) -> Result<Self, ChartError> {
Self::try_new_with_palace_layout_and_decorative_stars(
scope,
context,
placements,
activations,
palace_layout,
Vec::new(),
)
}
pub fn try_new_with_palace_layout_and_decorative_stars(
scope: Scope,
context: TemporalContext,
placements: Vec<ScopedStarPlacement>,
activations: Vec<MutagenActivation>,
palace_layout: Option<TemporalPalaceLayout>,
decorative_placements: Vec<ScopedDecorativeStarPlacement>,
) -> 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(),
});
}
if let Some(layout) = &palace_layout {
if layout.scope() != scope {
return Err(ChartError::TemporalPalaceLayoutScopeMismatch {
layer: scope,
layout: layout.scope(),
});
}
}
if let Some(decorative) = decorative_placements
.iter()
.find(|decorative| decorative.scope() != scope)
{
return Err(ChartError::TemporalDecorativeScopeMismatch {
layer: scope,
decorative: decorative.scope(),
});
}
Ok(Self {
scope,
context,
placements,
activations,
palace_layout,
decorative_placements,
})
}
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
}
pub const fn palace_layout(&self) -> Option<&TemporalPalaceLayout> {
self.palace_layout.as_ref()
}
pub fn temporal_decorative_stars(&self) -> &[ScopedDecorativeStarPlacement] {
&self.decorative_placements
}
}
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>,
#[serde(default)]
palace_layout: Option<TemporalPalaceLayout>,
#[serde(default)]
decorative_placements: Vec<ScopedDecorativeStarPlacement>,
}
let data = TemporalLayerData::deserialize(deserializer)?;
TemporalLayer::try_new_with_palace_layout_and_decorative_stars(
data.scope,
data.context,
data.placements,
data.activations,
data.palace_layout,
data.decorative_placements,
)
.map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct HoroscopeChart {
natal: Chart,
layers: Vec<TemporalLayer>,
#[serde(default, skip_serializing_if = "Option::is_none")]
target_context: Option<HoroscopeTargetContext>,
}
impl HoroscopeChart {
pub const fn new(natal: Chart) -> Self {
Self {
natal,
layers: Vec::new(),
target_context: None,
}
}
pub const fn with_layers(natal: Chart, layers: Vec<TemporalLayer>) -> Self {
Self {
natal,
layers,
target_context: None,
}
}
pub const fn with_layers_and_target_context(
natal: Chart,
layers: Vec<TemporalLayer>,
target_context: HoroscopeTargetContext,
) -> Self {
Self {
natal,
layers,
target_context: Some(target_context),
}
}
pub fn with_target_context(mut self, target_context: HoroscopeTargetContext) -> Self {
self.target_context = Some(target_context);
self
}
pub const fn natal(&self) -> &Chart {
&self.natal
}
pub fn layers(&self) -> &[TemporalLayer] {
&self.layers
}
pub const fn target_context(&self) -> Option<&HoroscopeTargetContext> {
self.target_context.as_ref()
}
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
}
}