use crate::*;

use chrono::prelude::*;


pub struct ClockDevice {
    pub epoch: Instant,
    pub uptime_read: u16,

    // End time for each timer as ticks since epoch, zero if not set.
    pub t1_end: u64,
    pub t2_end: u64,
    pub t3_end: u64,
    pub t4_end: u64,
    // Cached read value for each timer.
    pub t1_read: u16,
    pub t2_read: u16,
    pub t3_read: u16,
    pub t4_read: u16,
    // Cached write value for each timer.
    pub t1_write: u16,
    pub t2_write: u16,
    pub t3_write: u16,
    pub t4_write: u16,

    pub wake: bool,
}


impl Device for ClockDevice {
    fn read(&mut self, port: u8) -> u8 {
        match port {
            0x0 => Local::now().year().saturating_sub(2000) as u8,
            0x1 => Local::now().month().saturating_sub(1) as u8,
            0x2 => Local::now().day().saturating_sub(1) as u8,
            0x3 => Local::now().hour() as u8,
            0x4 => Local::now().minute() as u8,
            0x5 => Local::now().second() as u8,
            0x6 => { self.uptime_read = self.uptime() as u16;
                     read_h!(self.uptime_read) },
            0x7 =>   read_l!(self.uptime_read),
            0x8 => { self.read_t1(); read_h!(self.t1_read) },
            0x9 =>                   read_l!(self.t1_read),
            0xa => { self.read_t2(); read_h!(self.t2_read) },
            0xb =>                   read_l!(self.t2_read),
            0xc => { self.read_t3(); read_h!(self.t3_read) },
            0xd =>                   read_l!(self.t3_read),
            0xe => { self.read_t4(); read_h!(self.t4_read) },
            0xf =>                   read_l!(self.t4_read),
            _ => unreachable!(),
        }
    }

    fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
        match port {
            0x0 => (),
            0x1 => (),
            0x2 => (),
            0x3 => (),
            0x4 => (),
            0x5 => (),
            0x6 => (),
            0x7 => (),
            0x8 =>   write_h!(self.t1_write, value),
            0x9 => { write_l!(self.t1_write, value); self.set_t1() },
            0xa =>   write_h!(self.t2_write, value),
            0xb => { write_l!(self.t2_write, value); self.set_t2() },
            0xc =>   write_h!(self.t3_write, value),
            0xd => { write_l!(self.t3_write, value); self.set_t3() },
            0xe =>   write_h!(self.t4_write, value),
            0xf => { write_l!(self.t4_write, value); self.set_t4() },
            _ => unreachable!(),
        };
        return None;
    }

    fn wake(&mut self) -> bool {
        let uptime = self.uptime();

        if self.t1_end > 0 && self.t1_end <= uptime {
            self.t1_end = 0; self.wake = true; }
        if self.t2_end > 0 && self.t2_end <= uptime {
            self.t2_end = 0; self.wake = true; }
        if self.t3_end > 0 && self.t3_end <= uptime {
            self.t3_end = 0; self.wake = true; }
        if self.t4_end > 0 && self.t4_end <= uptime {
            self.t4_end = 0; self.wake = true; }

        return std::mem::take(&mut self.wake);
    }
}


impl ClockDevice {
    pub fn new() -> Self {
        Self {
            epoch: Instant::now(),
            uptime_read: 0,
            wake: false,

            t1_end: 0,
            t2_end: 0,
            t3_end: 0,
            t4_end: 0,
            t1_read: 0,
            t2_read: 0,
            t3_read: 0,
            t4_read: 0,
            t1_write: 0,
            t2_write: 0,
            t3_write: 0,
            t4_write: 0,
        }
    }

    pub fn uptime(&self) -> u64 {
        (self.epoch.elapsed().as_nanos() * 256 / 1_000_000_000) as u64
    }

    /// Duration remaining until the next timer expires, if any timer is set.
    pub fn duration_remaining(&mut self) -> Option<Duration> {
        let mut end = u64::MAX;
        if self.t1_end > 0 { end = std::cmp::min(end, self.t1_end); }
        if self.t2_end > 0 { end = std::cmp::min(end, self.t2_end); }
        if self.t3_end > 0 { end = std::cmp::min(end, self.t3_end); }
        if self.t4_end > 0 { end = std::cmp::min(end, self.t4_end); }

        if end != u64::MAX {
            let remaining = end.saturating_sub(self.uptime());
            Some(Duration::from_nanos(remaining * 1_000_000_000 / 256))
        } else {
            None
        }
    }
}

macro_rules! fn_read_timer {
    ($fn_name:ident($read:ident, $end:ident)) => {
        pub fn $fn_name(&mut self) {
            let uptime = self.uptime();
            if self.$end > uptime {
                self.$read = (self.$end.saturating_sub(uptime)) as u16;
            } else {
                if self.$end > 0 {
                    self.$end = 0;
                    self.wake = true;
                }
                self.$read = 0;
            }
        }
    };
}

macro_rules! fn_set_timer {
    ($fn_name:ident($write:ident, $end:ident)) => {
        pub fn $fn_name(&mut self) {
            match self.$write > 0 {
                true => self.$end = self.uptime().saturating_add(self.$write as u64),
                false => self.$end = 0,
            };
        }
    };
}

impl ClockDevice {
    fn_read_timer!{ read_t1(t1_read, t1_end) }
    fn_read_timer!{ read_t2(t2_read, t2_end) }
    fn_read_timer!{ read_t3(t3_read, t3_end) }
    fn_read_timer!{ read_t4(t4_read, t4_end) }

    fn_set_timer!{ set_t1(t1_write, t1_end) }
    fn_set_timer!{ set_t2(t2_write, t2_end) }
    fn_set_timer!{ set_t3(t3_write, t3_end) }
    fn_set_timer!{ set_t4(t4_write, t4_end) }
}