use std::time::{Duration, Instant};

macro_rules! now { () => {
    time::OffsetDateTime::now_local()
    .unwrap_or_else(|_| time::OffsetDateTime::now_utc())
};}

macro_rules! time_from_ticks {
    ($ticks:expr) => { Instant::now() + Duration::from_millis(($ticks * 4).into()) };
}

macro_rules! ticks_since_time {
    ($time:expr) => { ($time.duration_since(Instant::now()).as_millis() / 4) as u16 };
}

/// Create a method to set the instant of a timer.
macro_rules! generate_set_timer_method {
    ($i:tt) => {mini_paste::item!{
        pub fn [< set_timer_ $i >] (&mut self) {
            self. [< timer_ $i _instant >] = time_from_ticks!(self. [< timer_ $i >]);
        }
    }};
}

/// Create a method to update the value of a timer.
macro_rules! generate_update_timer_method {
    ($i:tt) => {mini_paste::item!{
        pub fn [< update_timer_ $i >] (&mut self) {
            if self. [< timer_ $i >] > 0 {
                self. [< timer_ $i >] = ticks_since_time!(self.[< timer_ $i _instant >]);
                if self. [< timer_ $i >] == 0 { self.wake_flag = true; }
            }
        }
    }};
}

pub struct ClockDevice {
    pub wake_flag: bool,

    pub boot_time: Instant,
    pub cumulative_seconds: u16,

    pub timer_1_instant: Instant,
    pub timer_2_instant: Instant,
    pub timer_3_instant: Instant,
    pub timer_4_instant: Instant,
    pub timer_1: u16,
    pub timer_2: u16,
    pub timer_3: u16,
    pub timer_4: u16,
}

impl ClockDevice {
    pub fn new() -> Self {
        Self {
            wake_flag: false,

            boot_time: Instant::now(),
            cumulative_seconds: 0,

            timer_1_instant: Instant::now(),
            timer_2_instant: Instant::now(),
            timer_3_instant: Instant::now(),
            timer_4_instant: Instant::now(),
            timer_1: 0,
            timer_2: 0,
            timer_3: 0,
            timer_4: 0,
        }
    }

    pub fn update_cumulative_seconds(&mut self) {
        self.cumulative_seconds = self.boot_time.elapsed().as_secs() as u16;
    }

    generate_set_timer_method!{1}
    generate_set_timer_method!{2}
    generate_set_timer_method!{3}
    generate_set_timer_method!{4}

    generate_update_timer_method!{1}
    generate_update_timer_method!{2}
    generate_update_timer_method!{3}
    generate_update_timer_method!{4}

    pub fn year(&self) -> u8 {
        now!().year().saturating_sub(2000).try_into().unwrap_or(u8::MAX)
    }

    pub fn month(&self) -> u8 {
        now!().month() as u8 - 1
    }

    pub fn day(&self) -> u8 {
        now!().day() as u8 - 1
    }

    pub fn hour(&self) -> u8 {
        now!().hour()
    }

    pub fn minute(&self) -> u8 {
        now!().minute()
    }

    pub fn second(&self) -> u8 {
        now!().second()
    }

    pub fn shortest_active_timer(&self) -> Option<Duration> {
        let mut is_some = false;
        let mut value = u16::MAX;
        macro_rules! contribute_to_min {
            ($timer:expr) => {
                if $timer > 0 {
                    is_some = true;
                    value = std::cmp::min(value, $timer);
                }
            };
        }
        contribute_to_min!(self.timer_1);
        contribute_to_min!(self.timer_2);
        contribute_to_min!(self.timer_3);
        contribute_to_min!(self.timer_4);
        match is_some {
            true => Some(Duration::from_millis((value*4).into())),
            false => None,
        }
    }
}