use tyme4rs::tyme::enums::YinYang;
use tyme4rs::tyme::sixtycycle::HeavenStem;
use tyme4rs::tyme::sixtycycle::SixtyCycle;
use crate::enums::QimenDoor;
use crate::enums::QimenGod;
use crate::enums::QimenStar;
pub(crate) const SAN_QI_LIU_YI: [u8; 9] = [4, 5, 6, 7, 8, 9, 3, 2, 1];
pub(crate) const LUO_SHU_ORDER: [u8; 8] = [1, 8, 3, 4, 9, 2, 7, 6];
pub(crate) const LUO_SHU_INDEX: [u8; 10] = [0, 0, 5, 2, 3, 0, 7, 6, 1, 4];
pub(crate) const GOD_YANG_ORDER: [u8; 8] = [1, 8, 3, 4, 9, 2, 7, 6];
pub(crate) const GOD_YIN_ORDER: [u8; 8] = [1, 6, 7, 2, 9, 4, 3, 8];
pub(crate) const GODS_ORDER: [QimenGod; 8] = [
QimenGod::ZhiFu,
QimenGod::TengShe,
QimenGod::TaiYin,
QimenGod::LiuHe,
QimenGod::BaiHu,
QimenGod::XuanWu,
QimenGod::JiuDi,
QimenGod::JiuTian,
];
pub(crate) const GRID: [[u8; 3]; 3] = [[4, 9, 2], [3, 5, 7], [8, 1, 6]];
pub(crate) const PALACE_NAMES: [&str; 10] = ["", "坎", "坤", "震", "巽", "中", "乾", "兑", "艮", "离"];
pub(crate) const TERM_JU: [[u8; 3]; 24] = [
[1, 7, 4], [2, 8, 5], [3, 9, 6], [8, 5, 2], [9, 6, 3], [1, 7, 4], [3, 9, 6], [4, 1, 7], [5, 2, 8], [4, 1, 7], [5, 2, 8], [6, 3, 9], [9, 3, 6], [8, 2, 5], [7, 1, 4], [2, 5, 8], [1, 4, 7], [9, 3, 6], [7, 1, 4], [6, 9, 3], [5, 8, 2], [6, 9, 3], [5, 8, 2], [4, 7, 1], ];
pub(crate) const TEN_XUN_SHOU: [u8; 6] = [4, 5, 6, 7, 8, 9];
pub(crate) const TEN_XUN_START_BRANCH: [u8; 6] = [0, 10, 8, 6, 4, 2];
pub(crate) const TEN_KONG_BRANCHES: [[u8; 2]; 6] = [
[10, 11], [8, 9], [6, 7], [4, 5], [2, 3], [0, 1], ];
pub(crate) const BRANCH_TO_PALACE: [u8; 12] = [1, 8, 8, 3, 4, 4, 9, 2, 2, 7, 6, 6];
pub(crate) const STEM_TOMB_PALACE: [u8; 10] = [
2, 6, 6, 8, 6, 8, 8, 4, 4, 2, ];
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Plate<T> {
cells: [Option<T>; 9],
}
impl<T> Plate<T> {
pub(crate) fn empty() -> Self
where
T: Clone,
{
Self { cells: std::array::from_fn(|_| None) }
}
pub(crate) fn set(&mut self, palace: u8, value: T) {
debug_assert!((1..=9).contains(&palace), "palace must be 1..=9");
self.cells[palace as usize - 1] = Some(value);
}
pub(crate) fn get(&self, palace: u8) -> Option<&T> {
if !(1..=9).contains(&palace) {
return None;
}
self.cells[palace as usize - 1].as_ref()
}
}
impl<T: Clone> Plate<T> {
pub(crate) fn cloned(&self, palace: u8) -> Option<T> { self.get(palace).cloned() }
}
pub(crate) fn find_stem_palace(plate: &Plate<HeavenStem>, stem: &HeavenStem, exclude_center: bool) -> Option<u8> {
for palace in 1..=9u8 {
if exclude_center && palace == 5 {
continue;
}
if let Some(value) = plate.get(palace)
&& value == stem
{
return Some(palace);
}
}
None
}
pub(crate) fn find_hour_stem_palace(plate: &Plate<HeavenStem>, stem: &HeavenStem, zhi_fu_palace: u8) -> u8 {
if stem.get_index() == 0 {
return zhi_fu_palace;
}
if let Some(palace) = find_stem_palace(plate, stem, true) {
return palace;
}
if plate.get(5) == Some(stem) { 2 } else { zhi_fu_palace }
}
pub(crate) fn find_door_palace(plate: &Plate<QimenDoor>, door: QimenDoor) -> Option<u8> {
(1..=9u8).find(|&palace| plate.get(palace) == Some(&door))
}
pub(crate) fn build_earth_plate(yin_yang: YinYang, ju: u8) -> Plate<HeavenStem> {
let mut plate = Plate::empty();
let mut palace = ju;
for stem_index in SAN_QI_LIU_YI {
plate.set(palace, HeavenStem::from_index(stem_index as isize));
palace = step_palace(palace, yin_yang);
}
plate
}
pub(crate) fn build_heaven_plate(
earth: &Plate<HeavenStem>, yin_yang: YinYang, zhi_fu_palace: u8, hour_palace: u8,
) -> Plate<HeavenStem> {
let mut plate = Plate::empty();
let zhi_fu_index = LUO_SHU_INDEX[zhi_fu_palace as usize] as usize;
let hour_index = LUO_SHU_INDEX[hour_palace as usize] as usize;
let steps = match yin_yang {
YinYang::YANG => (hour_index + 8 - zhi_fu_index) % 8,
YinYang::YIN => (zhi_fu_index + 8 - hour_index) % 8,
};
for (i, palace) in LUO_SHU_ORDER.iter().enumerate() {
if let Some(stem) = earth.cloned(*palace) {
let target_index = match yin_yang {
YinYang::YANG => (i + steps) % 8,
YinYang::YIN => (i + 8 - steps) % 8,
};
plate.set(LUO_SHU_ORDER[target_index], stem);
}
}
if let Some(center) = earth.cloned(5) {
plate.set(5, center);
}
plate
}
pub(crate) fn build_star_plate(zhi_fu_palace: u8, hour_palace: u8) -> Plate<QimenStar> {
let mut plate = Plate::empty();
let zhi_fu_index = LUO_SHU_INDEX[zhi_fu_palace as usize] as usize;
let hour_index = LUO_SHU_INDEX[hour_palace as usize] as usize;
let steps = (hour_index + 8 - zhi_fu_index) % 8;
for (i, original_palace) in LUO_SHU_ORDER.iter().enumerate() {
let target_palace = LUO_SHU_ORDER[(i + steps) % 8];
let star = if *original_palace == 2 {
QimenStar::QinRui
} else {
QimenStar::from_palace(*original_palace).unwrap_or(QimenStar::TianRui)
};
plate.set(target_palace, star);
}
plate
}
pub(crate) fn build_door_plate(yin_yang: YinYang, zhi_fu_palace: u8, hour: &SixtyCycle) -> Plate<QimenDoor> {
let mut plate = Plate::empty();
let xun_start_branch_index = ten_xun_start_branch_index(hour);
let hour_branch_index = hour.get_earth_branch().get_index();
let branch_steps = (hour_branch_index + 12 - xun_start_branch_index) % 12;
let zhi_shi_palace = move_palace_by_steps(zhi_fu_palace, branch_steps, yin_yang);
let zhi_fu_index = LUO_SHU_INDEX[zhi_fu_palace as usize] as usize;
let zhi_shi_index = LUO_SHU_INDEX[zhi_shi_palace as usize] as usize;
let steps = match yin_yang {
YinYang::YANG => (zhi_shi_index + 8 - zhi_fu_index) % 8,
YinYang::YIN => (zhi_fu_index + 8 - zhi_shi_index) % 8,
};
for (i, original_palace) in LUO_SHU_ORDER.iter().enumerate() {
let target_index = match yin_yang {
YinYang::YANG => (i + steps) % 8,
YinYang::YIN => (i + 8 - steps) % 8,
};
if let Some(door) = QimenDoor::from_palace(*original_palace) {
plate.set(LUO_SHU_ORDER[target_index], door);
}
}
plate
}
pub(crate) fn build_god_plate(yin_yang: YinYang, zhi_fu_palace: u8) -> Plate<QimenGod> {
let mut plate = Plate::empty();
let order = match yin_yang {
YinYang::YANG => GOD_YANG_ORDER,
YinYang::YIN => GOD_YIN_ORDER,
};
let start_palace = if zhi_fu_palace == 5 { 2 } else { zhi_fu_palace };
let start_index = order.iter().position(|p| *p == start_palace).unwrap_or(0);
for (i, god) in GODS_ORDER.iter().enumerate() {
plate.set(order[(start_index + i) % 8], *god);
}
plate
}
pub(crate) fn build_hidden_plate(yin_yang: YinYang) -> Plate<HeavenStem> {
let mut plate = Plate::empty();
let mut palace = 8u8;
for stem_index in SAN_QI_LIU_YI {
plate.set(palace, HeavenStem::from_index(stem_index as isize));
palace = step_palace(palace, yin_yang);
}
plate
}
fn step_palace(palace: u8, yin_yang: YinYang) -> u8 {
match yin_yang {
YinYang::YANG => {
if palace == 9 {
1
} else {
palace + 1
}
}
YinYang::YIN => {
if palace == 1 {
9
} else {
palace - 1
}
}
}
}
pub(crate) fn move_palace_by_steps(palace: u8, steps: usize, yin_yang: YinYang) -> u8 {
let mut target = palace;
for _ in 0..steps {
target = step_palace(target, yin_yang);
}
if target == 5 { 2 } else { target }
}
pub(crate) fn ten_xun_start_branch_index(sixty_cycle: &SixtyCycle) -> usize {
let ten_index = sixty_cycle.get_ten().get_index();
TEN_XUN_START_BRANCH[ten_index.min(5)] as usize
}
pub(crate) const fn palace_branch_indices(palace: u8) -> &'static [u8] {
match palace {
1 => &[0], 2 => &[7, 8], 3 => &[3], 4 => &[4, 5], 6 => &[10, 11], 7 => &[9], 8 => &[1, 2], 9 => &[6], _ => &[],
}
}
pub(crate) const fn are_opposite_palaces(a: u8, b: u8) -> bool {
matches!((a, b), (1, 9) | (9, 1) | (2, 8) | (8, 2) | (3, 7) | (7, 3) | (4, 6) | (6, 4))
}
pub(crate) fn is_stem_in_tomb(stem: &HeavenStem, palace: u8) -> bool {
let idx = stem.get_index();
if idx >= 10 {
return false;
}
STEM_TOMB_PALACE[idx] == palace
}