use serde::{Deserialize, Serialize};

pub const MONTH_SLOTS_PER_YEAR: u32 = 12;
pub const PHASE_SLOTS_PER_MONTH: u32 = 28;
pub const TICKS_PER_PHASE: u32 = 180;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct CalendarPoint {
    pub year: u32,
    pub month_slot: u32,
    pub phase_slot: u32,
    pub tick_slot: u32,
}

impl CalendarPoint {
    pub fn validate(&self) -> Result<(), String> {
        if self.month_slot >= MONTH_SLOTS_PER_YEAR {
            return Err(format!(
                "month_slot {} is out of range 0..{}",
                self.month_slot,
                MONTH_SLOTS_PER_YEAR - 1
            ));
        }
        if self.phase_slot >= PHASE_SLOTS_PER_MONTH {
            return Err(format!(
                "phase_slot {} is out of range 0..{}",
                self.phase_slot,
                PHASE_SLOTS_PER_MONTH - 1
            ));
        }
        if self.tick_slot >= TICKS_PER_PHASE {
            return Err(format!(
                "tick_slot {} is out of range 0..{}",
                self.tick_slot,
                TICKS_PER_PHASE - 1
            ));
        }
        Ok(())
    }

    pub fn step_forward(&mut self) -> BoundaryEventKind {
        self.tick_slot += 1;
        if self.tick_slot < TICKS_PER_PHASE {
            return BoundaryEventKind::Tick;
        }

        self.tick_slot = 0;
        self.phase_slot += 1;
        if self.phase_slot < PHASE_SLOTS_PER_MONTH {
            return BoundaryEventKind::PhaseRollover;
        }

        self.phase_slot = 0;
        self.month_slot += 1;
        if self.month_slot < MONTH_SLOTS_PER_YEAR {
            return BoundaryEventKind::MonthRollover;
        }

        self.month_slot = 0;
        self.year += 1;
        BoundaryEventKind::YearRollover
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoundaryEventKind {
    Tick,
    PhaseRollover,
    MonthRollover,
    YearRollover,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn validates_calendar_bounds() {
        let point = CalendarPoint {
            year: 1830,
            month_slot: 0,
            phase_slot: 0,
            tick_slot: 0,
        };
        assert!(point.validate().is_ok());

        let invalid = CalendarPoint {
            month_slot: MONTH_SLOTS_PER_YEAR,
            ..point
        };
        assert!(invalid.validate().is_err());
    }

    #[test]
    fn steps_across_year_boundary() {
        let mut point = CalendarPoint {
            year: 1830,
            month_slot: MONTH_SLOTS_PER_YEAR - 1,
            phase_slot: PHASE_SLOTS_PER_MONTH - 1,
            tick_slot: TICKS_PER_PHASE - 1,
        };

        let event = point.step_forward();
        assert_eq!(event, BoundaryEventKind::YearRollover);
        assert_eq!(
            point,
            CalendarPoint {
                year: 1831,
                month_slot: 0,
                phase_slot: 0,
                tick_slot: 0,
            }
        );
    }
}
