use crate::core::calendar::ResolvedTemporalTarget;
use crate::core::error::ChartError;
use crate::core::model::chart::{
Chart, HoroscopeChart, HoroscopeLunarDate, HoroscopeSolarDate, HoroscopeTargetContext,
TemporalLayer, build_age_period, build_daily_period, build_decadal_frame, build_hourly_period,
build_monthly_period, build_yearly_period, nominal_age_for_target_year,
};
use crate::core::placement::overlay::age::build_age_horoscope_layer;
use crate::core::placement::overlay::daily_horoscope::build_daily_horoscope_layer;
use crate::core::placement::overlay::decadal_horoscope::build_decadal_horoscope_layer;
use crate::core::placement::overlay::hourly_horoscope::build_hourly_horoscope_layer;
use crate::core::placement::overlay::monthly_horoscope::build_monthly_horoscope_layer;
use crate::core::placement::overlay::yearly_horoscope::build_yearly_horoscope_layer;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum PartialHoroscope {
Decadal { period_index: usize },
Yearly {
period_index: usize,
lunar_year: i32,
},
Monthly {
period_index: usize,
lunar_year: i32,
target: ResolvedTemporalTarget,
},
Daily {
period_index: usize,
lunar_year: i32,
target: ResolvedTemporalTarget,
},
Hourly {
period_index: usize,
lunar_year: i32,
target: ResolvedTemporalTarget,
},
}
impl PartialHoroscope {
const fn period_index(&self) -> usize {
match self {
Self::Decadal { period_index }
| Self::Yearly { period_index, .. }
| Self::Monthly { period_index, .. }
| Self::Daily { period_index, .. }
| Self::Hourly { period_index, .. } => *period_index,
}
}
const fn lunar_year(&self) -> Option<i32> {
match self {
Self::Decadal { .. } => None,
Self::Yearly { lunar_year, .. }
| Self::Monthly { lunar_year, .. }
| Self::Daily { lunar_year, .. }
| Self::Hourly { lunar_year, .. } => Some(*lunar_year),
}
}
const fn target(&self) -> Option<ResolvedTemporalTarget> {
match self {
Self::Decadal { .. } | Self::Yearly { .. } => None,
Self::Monthly { target, .. }
| Self::Daily { target, .. }
| Self::Hourly { target, .. } => Some(*target),
}
}
}
pub(crate) fn build_partial_horoscope_chart(
natal: Chart,
spec: PartialHoroscope,
) -> Result<HoroscopeChart, ChartError> {
let frame = build_decadal_frame(&natal)?;
let period_index = spec.period_index();
let period =
frame
.periods()
.get(period_index)
.ok_or(ChartError::InvalidDecadalPeriodIndex {
index: period_index,
len: frame.periods().len(),
})?;
let mut layers: Vec<TemporalLayer> = vec![build_decadal_horoscope_layer(&natal, period)?];
if let Some(lunar_year) = spec.lunar_year() {
let nominal_age = nominal_age_for_target_year(&natal, lunar_year)?;
let age_period = build_age_period(&natal, nominal_age)?;
layers.push(build_age_horoscope_layer(&natal, &age_period)?);
let yearly_period = build_yearly_period(lunar_year)?;
layers.push(build_yearly_horoscope_layer(&natal, &yearly_period)?);
}
if let Some(target) = spec.target() {
let monthly_period = build_monthly_period(
&natal,
target.solar_year,
target.solar_month,
target.solar_day,
target.target_time,
)?;
layers.push(build_monthly_horoscope_layer(&natal, &monthly_period)?);
if matches!(
spec,
PartialHoroscope::Daily { .. } | PartialHoroscope::Hourly { .. }
) {
let daily_period = build_daily_period(
&natal,
target.solar_year,
target.solar_month,
target.solar_day,
target.target_time,
)?;
layers.push(build_daily_horoscope_layer(&natal, &daily_period)?);
}
if matches!(spec, PartialHoroscope::Hourly { .. }) {
let hourly_period = build_hourly_period(
&natal,
target.solar_year,
target.solar_month,
target.solar_day,
target.target_time,
)?;
layers.push(build_hourly_horoscope_layer(&natal, &hourly_period)?);
}
}
let chart = HoroscopeChart::with_layers(natal, layers);
Ok(match spec.target() {
Some(target) => chart.with_target_context(HoroscopeTargetContext::new(
HoroscopeSolarDate::new(
target.solar_year,
target.solar_month.value(),
target.solar_day.value(),
),
HoroscopeLunarDate::new(
target.lunar_year,
target.lunar_month,
target.lunar_day,
target.is_leap_month,
),
target.target_time.iztro_time_index(),
)),
None => chart,
})
}