use bedrock_core::*;

use chrono::prelude::*;
use std::time::{Duration, Instant};


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) {
            let uptime = self.uptime();
            if self.$write > 0 {
                self.$end = uptime.saturating_add(self.$write as u32);
            } else {
                self.$end = 0;
            }
        }
    };
}


pub struct ClockDevice {
    pub wake: bool,

    pub start: Instant,
    pub uptime_read: u16,

    pub t1_end: u32,
    pub t2_end: u32,
    pub t3_end: u32,
    pub t4_end: u32,
    pub t1_read: u16,
    pub t2_read: u16,
    pub t3_read: u16,
    pub t4_read: u16,
    pub t1_write: u16,
    pub t2_write: u16,
    pub t3_write: u16,
    pub t4_write: u16,
}

impl ClockDevice {
    pub fn new() -> Self {
        Self {
            start: 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) -> u32 {
        (self.start.elapsed().as_millis() / 4) as u32
    }

    pub fn read_uptime(&mut self) {
        self.uptime_read = self.uptime() as u16;
    }

    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) }


    pub fn time_remaining(&mut self) -> Option<Duration> {
        use std::cmp::max;
        let uptime = self.uptime();

        let end = max(self.t1_end, max(self.t2_end, max(self.t3_end, self.t4_end)));
        let remaining = end.saturating_sub(uptime);
        match remaining > 0 {
            true => Some(Duration::from_millis(remaining as u64) * 4),
            false => None,
        }
    }
}

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.read_uptime(); 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();
        macro_rules! check_timer {
            ($end:ident) => {
                if self.$end > 0 && self.$end <= uptime {
                    self.$end = 0;
                    self.wake = true;
                }
            };
        }
        check_timer!(t1_end);
        check_timer!(t2_end);
        check_timer!(t3_end);
        check_timer!(t4_end);
        return std::mem::take(&mut self.wake);
    }
}