use crate::hal::watchdog;
use crate::pac::{IWDG, WWDG};
use crate::rcc::Rcc;
use crate::time::Hertz;

pub struct IndependedWatchdog {
    iwdg: IWDG,
}

impl IndependedWatchdog {
    pub fn set_config(&mut self, pre: u8, reload: u16) {
        self.iwdg.kr.write(|w| w.key().reset());
        self.iwdg.kr.write(|w| w.key().enable());

        while self.iwdg.sr.read().pvu().bit() {}
        self.iwdg.pr.write(|w| w.pr().bits(pre));

        while self.iwdg.sr.read().rvu().bit() {}
        self.iwdg.rlr.write(|w| w.rl().bits(reload));

        self.iwdg.kr.write(|w| w.key().start());
        self.iwdg.kr.write(|w| w.key().reset());
    }
}

impl watchdog::Watchdog for IndependedWatchdog {
    fn feed(&mut self) {
        self.iwdg.kr.write(|w| w.key().reset());
    }
}

impl watchdog::WatchdogEnable for IndependedWatchdog {
    type Time = Hertz;

    fn start<T>(&mut self, period: T)
    where
        T: Into<Hertz>,
    {
        const LSI_CLOCK: u32 = 38_000_u32;

        let freq = period.into().0;
        let mut timeout = LSI_CLOCK / freq / 4;
        let mut pre = 0;
        let mut reload = 0;
        while pre < 7 {
            reload = timeout;
            if reload <= 0xFFF {
                break;
            }
            pre += 1;
            timeout /= 2;
        }
        self.set_config(pre, reload as u16);
    }
}

pub trait IndependedWatchdogExt {
    fn watchdog(self) -> IndependedWatchdog;
}

impl IndependedWatchdogExt for IWDG {
    fn watchdog(self) -> IndependedWatchdog {
        IndependedWatchdog { iwdg: self }
    }
}

pub struct WindowWatchdog {
    wwdg: WWDG,
    clk: u32,
}

impl watchdog::Watchdog for WindowWatchdog {
    fn feed(&mut self) {
        self.wwdg.cr.write(|w| w.t().bits(0xFF));
    }
}

impl WindowWatchdog {
    pub fn set_window<T>(&mut self, window: T)
    where
        T: Into<Hertz>,
    {
        let freq = window.into().0;
        let mut ticks = self.clk / freq;
        let mut pre = 0;
        let mut threshold = 0;
        while pre < 3 {
            threshold = ticks;
            if threshold <= 0x7F {
                break;
            }
            pre += 1;
            ticks /= 2;
        }

        let window_bits = if threshold >= 0xFF {
            0
        } else {
            0xFF - threshold as u8
        };
        self.wwdg
            .cfr
            .write(|w| w.wdgtb().bits(pre).w().bits(window_bits));
    }

    pub fn listen(&mut self) {
        self.wwdg.cfr.write(|w| w.ewi().set_bit());
    }
}

impl watchdog::WatchdogEnable for WindowWatchdog {
    type Time = Hertz;

    fn start<T>(&mut self, period: T)
    where
        T: Into<Hertz>,
    {
        self.set_window(period);
        self.wwdg.cr.write(|w| w.wdga().set_bit().t().bits(0xFF));
    }
}

pub trait WindowWatchdogExt {
    fn watchdog(self, rcc: &mut Rcc) -> WindowWatchdog;
}

impl WindowWatchdogExt for WWDG {
    fn watchdog(self, rcc: &mut Rcc) -> WindowWatchdog {
        rcc.rb.apb1enr.modify(|_, w| w.wwdgen().set_bit());
        WindowWatchdog {
            wwdg: self,
            clk: rcc.clocks.apb1_clk().0 / 4096,
        }
    }
}
