use crate::{
error::StemBranchError,
stem_branch::{EarthlyBranch, HeavenlyStem},
};
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct StemBranch {
stem: HeavenlyStem,
branch: EarthlyBranch,
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for StemBranch {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct RawStemBranch {
stem: HeavenlyStem,
branch: EarthlyBranch,
}
let raw = RawStemBranch::deserialize(deserializer)?;
StemBranch::try_new(raw.stem, raw.branch).map_err(serde::de::Error::custom)
}
}
impl StemBranch {
pub fn try_new(stem: HeavenlyStem, branch: EarthlyBranch) -> Result<Self, StemBranchError> {
if stem.index() % 2 == branch.index() % 2 {
Ok(Self { stem, branch })
} else {
Err(StemBranchError::InvalidStemBranchPair { stem, branch })
}
}
pub fn from_cycle_index(index: usize) -> Self {
let index = index % 60;
Self {
stem: HeavenlyStem::from_index(index),
branch: EarthlyBranch::from_index(index),
}
}
pub fn from_lunar_year(year: i32) -> Self {
let index = (year - 1984).rem_euclid(60) as usize;
Self::from_cycle_index(index)
}
pub const fn stem(&self) -> HeavenlyStem {
self.stem
}
pub const fn branch(&self) -> EarthlyBranch {
self.branch
}
pub fn cycle_index(&self) -> usize {
(0..60)
.find(|&index| index % 10 == self.stem.index() && index % 12 == self.branch.index())
.expect("StemBranch invariant guarantees a valid cycle index")
}
}