use crate::core::error::ChartError;
use crate::core::model::bureau::FiveElementBureau;
use crate::core::model::chart::{Chart, Palace, StarPlacement};
use crate::core::model::ganzhi::{EarthlyBranch, HeavenlyStem};
use crate::core::model::star::mutagen::Scope;
use crate::core::model::star::{Brightness, StarKind, StarMetadata, StarName};
use crate::core::placement::natal::life_body::LunarDay;
pub use crate::core::model::star::mutagen::birth_year_major_star_mutagen;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct MajorStarPlacementInput {
lunar_day: LunarDay,
five_element_bureau: FiveElementBureau,
birth_year_stem: HeavenlyStem,
}
impl MajorStarPlacementInput {
pub const fn new(
lunar_day: LunarDay,
five_element_bureau: FiveElementBureau,
birth_year_stem: HeavenlyStem,
) -> Self {
Self {
lunar_day,
five_element_bureau,
birth_year_stem,
}
}
pub const fn lunar_day(self) -> LunarDay {
self.lunar_day
}
pub const fn five_element_bureau(self) -> FiveElementBureau {
self.five_element_bureau
}
pub const fn birth_year_stem(self) -> HeavenlyStem {
self.birth_year_stem
}
}
pub trait MajorStarPlacer {
fn place_major_stars(
&self,
chart: Chart,
input: MajorStarPlacementInput,
) -> Result<Chart, ChartError>;
}
const ZI_WEI_SERIES: [(StarName, isize); 6] = [
(StarName::ZiWei, 0),
(StarName::TianJi, -1),
(StarName::TaiYang, -3),
(StarName::WuQu, -4),
(StarName::TianTong, -5),
(StarName::LianZhen, -8),
];
const TIAN_FU_SERIES: [(StarName, isize); 8] = [
(StarName::TianFu, 0),
(StarName::TaiYin, 1),
(StarName::TanLang, 2),
(StarName::JuMen, 3),
(StarName::TianXiang, 4),
(StarName::TianLiang, 5),
(StarName::QiSha, 6),
(StarName::PoJun, 10),
];
pub const fn major_star_metadata_table() -> &'static [StarMetadata; 14] {
crate::core::model::star::major_star_metadata_table()
}
pub fn major_star_metadata(star: StarName) -> &'static StarMetadata {
crate::core::model::star::major_star_metadata(star)
}
pub fn try_major_star_metadata(star: StarName) -> Option<&'static StarMetadata> {
crate::core::model::star::try_major_star_metadata(star)
}
pub fn major_star_brightness(star: StarName, branch: EarthlyBranch) -> Brightness {
const MIAO: Brightness = Brightness::Temple;
const WANG: Brightness = Brightness::Prosperous;
const DE: Brightness = Brightness::Advantage;
const LI: Brightness = Brightness::Favourable;
const PING: Brightness = Brightness::Flat;
const BU: Brightness = Brightness::Weak;
const XIAN: Brightness = Brightness::Trapped;
let brightness_by_yin_order = match star {
StarName::ZiWei => [
WANG, WANG, DE, WANG, MIAO, MIAO, WANG, WANG, DE, WANG, PING, MIAO,
],
StarName::TianJi => [
DE, WANG, LI, PING, MIAO, XIAN, DE, WANG, LI, PING, MIAO, XIAN,
],
StarName::TaiYang => [
WANG, MIAO, WANG, WANG, WANG, DE, DE, XIAN, BU, XIAN, XIAN, BU,
],
StarName::WuQu => [
DE, LI, MIAO, PING, WANG, MIAO, DE, LI, MIAO, PING, WANG, MIAO,
],
StarName::TianTong => [
LI, PING, PING, MIAO, XIAN, BU, WANG, PING, PING, MIAO, WANG, BU,
],
StarName::LianZhen => [
MIAO, PING, LI, XIAN, PING, LI, MIAO, PING, LI, XIAN, PING, LI,
],
StarName::TianFu => [
MIAO, DE, MIAO, DE, WANG, MIAO, DE, WANG, MIAO, DE, MIAO, MIAO,
],
StarName::TaiYin => [
WANG, XIAN, XIAN, XIAN, BU, BU, LI, BU, WANG, MIAO, MIAO, MIAO,
],
StarName::TanLang => [
PING, LI, MIAO, XIAN, WANG, MIAO, PING, LI, MIAO, XIAN, WANG, MIAO,
],
StarName::JuMen => [
MIAO, MIAO, XIAN, WANG, WANG, BU, MIAO, MIAO, XIAN, WANG, WANG, BU,
],
StarName::TianXiang => [MIAO, XIAN, DE, DE, MIAO, DE, MIAO, XIAN, DE, DE, MIAO, MIAO],
StarName::TianLiang => [
MIAO, MIAO, MIAO, XIAN, MIAO, WANG, XIAN, DE, MIAO, XIAN, MIAO, WANG,
],
StarName::QiSha => [
MIAO, WANG, MIAO, PING, WANG, MIAO, MIAO, MIAO, MIAO, PING, WANG, MIAO,
],
StarName::PoJun => [
DE, XIAN, WANG, PING, MIAO, WANG, DE, XIAN, WANG, PING, MIAO, WANG,
],
_ => return Brightness::Unknown,
};
let yin_order_index = (branch.index() + 12 - EarthlyBranch::Yin.index()) % 12;
brightness_by_yin_order[yin_order_index]
}
pub fn zi_wei_branch(bureau: FiveElementBureau, day: LunarDay) -> EarthlyBranch {
let bureau = i32::from(bureau.number());
let day = i32::from(day.value());
let borrow = (bureau - day % bureau) % bureau;
let quotient = ((day + borrow) / bureau) % 12;
let base = quotient - 1;
let steps = if borrow % 2 == 0 {
base + borrow
} else {
base - borrow
};
EarthlyBranch::Yin.offset(steps as isize)
}
pub fn tian_fu_branch(zi_wei: EarthlyBranch) -> EarthlyBranch {
let zi_wei_index = zi_wei.index() as isize - EarthlyBranch::Yin.index() as isize;
EarthlyBranch::Yin.offset(-zi_wei_index)
}
fn major_star_placements(
bureau: FiveElementBureau,
day: LunarDay,
) -> Vec<(EarthlyBranch, StarName)> {
let zi_wei = zi_wei_branch(bureau, day);
let tian_fu = tian_fu_branch(zi_wei);
ZI_WEI_SERIES
.iter()
.map(|&(star, offset)| (zi_wei.offset(offset), star))
.chain(
TIAN_FU_SERIES
.iter()
.map(|&(star, offset)| (tian_fu.offset(offset), star)),
)
.collect()
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash)]
pub struct DeterministicMajorStarPlacer;
impl MajorStarPlacer for DeterministicMajorStarPlacer {
fn place_major_stars(
&self,
chart: Chart,
input: MajorStarPlacementInput,
) -> Result<Chart, ChartError> {
let placements = major_star_placements(input.five_element_bureau(), input.lunar_day());
let palaces = chart
.palaces()
.iter()
.map(|palace| {
let mut stars = palace.stars().to_vec();
for &(branch, name) in &placements {
if branch == palace.branch() {
stars.push(StarPlacement::new(
name,
StarKind::Major,
major_star_brightness(name, branch),
birth_year_major_star_mutagen(input.birth_year_stem(), name),
Scope::Natal,
));
}
}
Palace::new(palace.name(), palace.branch(), palace.stem(), stars)
})
.collect();
Chart::try_new(
chart.birth_context().clone(),
chart.birth_year(),
chart.method_profile().clone(),
palaces,
chart.body_palace_branch(),
chart.five_element_bureau(),
)
}
}