use crate::data::stars::StarKey;
use crate::data::types::{Brightness, EarthlyBranch, Mutagen, Palace, Scope, StarType};
use crate::models::astrolabe::Astrolabe;
use crate::models::horoscope::HoroscopeData;
use crate::models::palace::PalaceData;
use crate::models::star::Star;
use crate::utils::fix_index;
use super::{BrightnessSource, PatternConfig, PatternHit, PatternKey, StarAt};
pub const SHA6: [StarKey; 6] = [
StarKey::HuoxingMin,
StarKey::LingxingMin,
StarKey::QingyangMin,
StarKey::TuoluoMin,
StarKey::DikongMin,
StarKey::DijieMin,
];
pub const SHA4: [StarKey; 4] = [
StarKey::QingyangMin,
StarKey::TuoluoMin,
StarKey::HuoxingMin,
StarKey::LingxingMin,
];
pub const JI6: [StarKey; 6] = [
StarKey::ZuofuMin,
StarKey::YoubiMin,
StarKey::WenchangMin,
StarKey::WenquMin,
StarKey::TiankuiMin,
StarKey::TianyueMin,
];
pub const KONG_WANG: [StarKey; 4] = [
StarKey::Xunkong,
StarKey::Kongwang,
StarKey::Jielu,
StarKey::Jiekong,
];
pub const KONG_YAO: [StarKey; 4] = [
StarKey::Tiankong,
StarKey::DikongMin,
StarKey::Jiekong,
StarKey::Xunkong,
];
pub const SPL: [StarKey; 3] = [StarKey::QishaMaj, StarKey::PojunMaj, StarKey::TanlangMaj];
pub const JYTL: [StarKey; 4] = [
StarKey::TianjiMaj,
StarKey::TaiyinMaj,
StarKey::TiantongMaj,
StarKey::TianliangMaj,
];
pub const BRIGHT: [Brightness; 2] = [Brightness::Miao, Brightness::Wang];
pub const DARK: [Brightness; 2] = [Brightness::Xian, Brightness::Bu];
fn flow_counterparts(key: StarKey) -> &'static [StarKey] {
use StarKey::*;
match key {
LucunMin => &[Yunlu, Liulu],
TianmaMin => &[Yunma, Liuma],
WenchangMin => &[Yunchang, Liuchang],
WenquMin => &[Yunqu, Liuqu],
QingyangMin => &[Yunyang, Liuyang],
TuoluoMin => &[Yuntuo, Liutuo],
TiankuiMin => &[Yunkui, Liukui],
TianyueMin => &[Yunyue, Liuyue],
Hongluan => &[Yunluan, Liuluan],
Tianxi => &[Yunxi, Liuxi],
_ => &[],
}
}
pub struct ViewPalace<'a> {
pub natal: &'a PalaceData,
pub flow: Vec<&'a Star>,
}
pub struct ChartView<'a> {
astrolabe: &'a Astrolabe,
horoscope: Option<&'a HoroscopeData>,
scope: Scope,
palaces: Vec<ViewPalace<'a>>,
names: [Palace; 12],
soul: usize,
body: Option<usize>,
scope_mutagen: Option<[StarKey; 4]>,
config: PatternConfig,
}
impl<'a> ChartView<'a> {
pub fn natal(astrolabe: &'a Astrolabe, config: &PatternConfig) -> Self {
let palaces = astrolabe
.palaces
.iter()
.map(|p| ViewPalace {
natal: p,
flow: Vec::new(),
})
.collect();
let mut names = [Palace::Soul; 12];
for (i, p) in astrolabe.palaces.iter().enumerate() {
names[i] = p.name;
}
ChartView {
astrolabe,
horoscope: None,
scope: Scope::Origin,
palaces,
names,
soul: astrolabe
.palaces
.iter()
.position(|p| p.name == Palace::Soul)
.unwrap_or(0),
body: astrolabe.palaces.iter().position(|p| p.is_body_palace),
scope_mutagen: None,
config: *config,
}
}
pub fn at(
astrolabe: &'a Astrolabe,
horoscope: &'a HoroscopeData,
scope: Scope,
config: &PatternConfig,
) -> Self {
let Some(item) = horoscope.scope_item(scope) else {
return Self::natal(astrolabe, config);
};
let layers: Vec<&Vec<Vec<Star>>> = match scope {
Scope::Decadal => horoscope.decadal.stars.iter().collect(),
_ => horoscope
.decadal
.stars
.iter()
.chain(horoscope.yearly.base.stars.iter())
.collect(),
};
let palaces = astrolabe
.palaces
.iter()
.enumerate()
.map(|(i, p)| ViewPalace {
natal: p,
flow: layers
.iter()
.filter_map(|l| l.get(i))
.flat_map(|stars| stars.iter())
.collect(),
})
.collect();
let mut names = [Palace::Soul; 12];
for (i, n) in item.palace_names.iter().take(12).enumerate() {
names[i] = *n;
}
let mut mutagen = [StarKey::ZiweiMaj; 4];
for (i, m) in item.mutagen.iter().take(4).enumerate() {
mutagen[i] = *m;
}
ChartView {
astrolabe,
horoscope: Some(horoscope),
scope,
palaces,
names,
soul: fix_index(item.index as i32, 12),
body: None,
scope_mutagen: Some(mutagen),
config: *config,
}
}
pub fn astrolabe(&self) -> &'a Astrolabe {
self.astrolabe
}
pub fn horoscope(&self) -> Option<&'a HoroscopeData> {
self.horoscope
}
pub fn scope(&self) -> Scope {
self.scope
}
pub fn config(&self) -> &PatternConfig {
&self.config
}
pub fn soul(&self) -> usize {
self.soul
}
pub fn body(&self) -> Option<usize> {
self.body
}
pub fn soul_and_body(&self) -> Vec<usize> {
match self.body {
Some(b) if b != self.soul => vec![self.soul, b],
_ => vec![self.soul],
}
}
pub fn index_of(&self, name: Palace) -> usize {
self.names
.iter()
.position(|n| *n == name)
.unwrap_or(self.soul)
}
pub fn name_of(&self, i: usize) -> Palace {
self.names[fix_index(i as i32, 12)]
}
pub fn branch(&self, i: usize) -> EarthlyBranch {
self.palace(i).natal.earthly_branch
}
pub fn palace(&self, i: usize) -> &ViewPalace<'a> {
&self.palaces[fix_index(i as i32, 12)]
}
pub fn opposite(&self, i: usize) -> usize {
fix_index(i as i32 + 6, 12)
}
pub fn trine(&self, i: usize) -> [usize; 2] {
[fix_index(i as i32 + 4, 12), fix_index(i as i32 + 8, 12)]
}
pub fn surround(&self, i: usize) -> [usize; 4] {
[
fix_index(i as i32, 12),
self.opposite(i),
fix_index(i as i32 + 4, 12),
fix_index(i as i32 + 8, 12),
]
}
pub fn prev(&self, i: usize) -> usize {
fix_index(i as i32 - 1, 12)
}
pub fn next(&self, i: usize) -> usize {
fix_index(i as i32 + 1, 12)
}
pub fn hidden(&self, i: usize) -> usize {
let b = self.branch(i).index() as i32;
let partner = fix_index(1 - b, 12); (0..12)
.find(|j| self.branch(*j).index() == partner)
.unwrap_or(i)
}
pub fn jia(&self, i: usize, a: &[StarKey], b: &[StarKey]) -> Option<(usize, usize)> {
let (p, n) = (self.prev(i), self.next(i));
if self.has_any(p, a) && self.has_any(n, b) {
Some((p, n))
} else if self.has_any(p, b) && self.has_any(n, a) {
Some((n, p))
} else {
None
}
}
pub fn stars(&self, i: usize) -> impl Iterator<Item = &'a Star> + '_ {
let p = self.palace(i);
p.natal
.major_stars
.iter()
.chain(p.natal.minor_stars.iter())
.chain(p.natal.adjective_stars.iter())
.chain(p.flow.iter().copied())
}
pub fn has(&self, i: usize, key: StarKey) -> bool {
self.find(i, key).is_some()
}
pub fn has_any(&self, i: usize, keys: &[StarKey]) -> bool {
keys.iter().any(|k| self.has(i, *k))
}
pub fn has_all(&self, i: usize, keys: &[StarKey]) -> bool {
keys.iter().all(|k| self.has(i, *k))
}
pub fn in_surround(&self, i: usize, key: StarKey) -> bool {
self.surround(i).iter().any(|p| self.has(*p, key))
}
pub fn surround_has_any(&self, i: usize, keys: &[StarKey]) -> bool {
self.surround(i).iter().any(|p| self.has_any(*p, keys))
}
pub fn surround_has_all(&self, i: usize, keys: &[StarKey]) -> bool {
keys.iter().all(|k| self.in_surround(i, *k))
}
pub fn find(&self, i: usize, key: StarKey) -> Option<&'a Star> {
let flow = if self.config.flow_stars {
flow_counterparts(key)
} else {
&[]
};
self.stars(i)
.find(|s| s.key == key || flow.contains(&s.key))
}
pub fn find_in_surround(&self, i: usize, key: StarKey) -> Option<(usize, &'a Star)> {
self.surround(i)
.into_iter()
.find_map(|p| self.find(p, key).map(|s| (p, s)))
}
pub fn locate(&self, key: StarKey) -> Option<(usize, &'a Star)> {
(0..12).find_map(|p| self.find(p, key).map(|s| (p, s)))
}
pub fn majors(&self, i: usize) -> Vec<&'a Star> {
self.palace(i)
.natal
.major_stars
.iter()
.filter(|s| s.star_type == StarType::Major)
.collect()
}
pub fn majors_borrowed(&self, i: usize) -> Vec<(&'a Star, bool)> {
let own = self.majors(i);
if own.is_empty() && self.config.borrow {
self.majors(self.opposite(i))
.into_iter()
.map(|s| (s, true))
.collect()
} else {
own.into_iter().map(|s| (s, false)).collect()
}
}
pub fn find_major_at(&self, i: usize, key: StarKey) -> Option<(usize, &'a Star)> {
let own = fix_index(i as i32, 12);
self.majors_borrowed(i)
.into_iter()
.find(|(s, _)| s.key == key)
.map(|(s, borrowed)| (if borrowed { self.opposite(i) } else { own }, s))
}
pub fn has_major(&self, i: usize, key: StarKey) -> bool {
self.find_major_at(i, key).is_some()
}
pub fn is_empty(&self, i: usize) -> bool {
self.majors(i).is_empty()
}
pub fn alone(&self, i: usize, key: StarKey) -> bool {
let m = self.majors(i);
m.len() == 1 && m[0].key == key
}
pub fn brightness_in(&self, i: usize, key: StarKey, set: &[Brightness]) -> bool {
self.find(i, key).is_some_and(|s| s.with_brightness(set))
}
pub fn sun_moon_bright(&self, i: usize, key: StarKey) -> bool {
match self.config.brightness_source {
BrightnessSource::Table => self.brightness_in(i, key, &BRIGHT),
BrightnessSource::Positional => {
self.has(i, key) && positional(key, self.branch(i)) == Some(true)
}
}
}
pub fn sun_moon_dark(&self, i: usize, key: StarKey) -> bool {
match self.config.brightness_source {
BrightnessSource::Table => self.brightness_in(i, key, &DARK),
BrightnessSource::Positional => {
self.has(i, key) && positional(key, self.branch(i)) == Some(false)
}
}
}
pub fn mutagen_of(&self, star: &Star) -> Option<Mutagen> {
match self.scope_mutagen {
None => star.mutagen,
Some(m) => [Mutagen::Lu, Mutagen::Quan, Mutagen::Ke, Mutagen::Ji]
.into_iter()
.zip(m)
.find(|(_, k)| *k == star.key)
.map(|(mu, _)| mu),
}
}
pub fn mutagen_star(&self, i: usize, m: Mutagen) -> Option<&'a Star> {
self.stars(i).find(|s| self.mutagen_of(s) == Some(m))
}
pub fn has_mutagen(&self, i: usize, m: Mutagen) -> bool {
self.mutagen_star(i, m).is_some()
}
pub fn find_mutagen_in_surround(&self, i: usize, m: Mutagen) -> Option<(usize, &'a Star)> {
self.surround(i)
.into_iter()
.find_map(|p| self.mutagen_star(p, m).map(|s| (p, s)))
}
pub fn no_sha(&self, i: usize) -> bool {
!self.surround_has_any(i, &SHA6) && self.find_mutagen_in_surround(i, Mutagen::Ji).is_none()
}
pub fn boshi12(&self, i: usize) -> StarKey {
self.palace(i).natal.boshi12
}
pub fn star_at(&self, i: usize, star: &Star) -> StarAt {
StarAt {
star: star.key,
palace: fix_index(i as i32, 12),
brightness: star.brightness,
mutagen: self.mutagen_of(star),
}
}
pub fn hit(&self, key: PatternKey, palace: usize, stars: Vec<(usize, &Star)>) -> PatternHit {
PatternHit {
key,
scope: self.scope,
palace: fix_index(palace as i32, 12),
variant: None,
broken: false,
stars: stars.into_iter().map(|(p, s)| self.star_at(p, s)).collect(),
}
}
}
fn positional(key: StarKey, branch: EarthlyBranch) -> Option<bool> {
use EarthlyBranch::*;
match key {
StarKey::TaiyangMaj => match branch {
Yin | Mao | Chen | Si | Wu => Some(true),
You | Xu | Hai | Zi | Chou => Some(false),
_ => None,
},
StarKey::TaiyinMaj => match branch {
You | Xu | Hai | Zi | Chou => Some(true),
Mao | Chen | Si | Wu | Wei => Some(false),
_ => None,
},
_ => None,
}
}