use core::convert::TryFrom;
use core::marker::PhantomData;
use crate::hal::timer::{CountDown, Periodic};
use crate::stm32::{lptim1, lptim3};
use crate::stm32::{LPTIM1, LPTIM2, LPTIM3};
#[cfg(not(feature = "rm0455"))]
use crate::stm32::{LPTIM4, LPTIM5};
use crate::stm32::{
    TIM1, TIM12, TIM13, TIM14, TIM15, TIM16, TIM17, TIM2, TIM3, TIM4, TIM5,
    TIM6, TIM7, TIM8,
};
use cast::{u16, u32};
use nb;
use void::Void;
#[cfg(feature = "rm0455")]
use crate::stm32::rcc::{cdccip2r as ccip2r, srdccipr};
#[cfg(not(feature = "rm0455"))]
use crate::stm32::rcc::{d2ccip2r as ccip2r, d3ccipr as srdccipr};
use crate::rcc::{rec, CoreClocks, ResetEnable};
use crate::stm32;
use crate::time::Hertz;
use stm32h7::Variant::Val;
pub trait GetClk {
    fn get_clk(clocks: &CoreClocks) -> Option<Hertz>;
}
macro_rules! impl_tim_ker_ck {
    ($($ckX:ident: $($TIMX:ident),+)+) => {
        $(
            $(
                impl GetClk for $TIMX {
                    fn get_clk(clocks: &CoreClocks) -> Option<Hertz> {
                        Some(clocks.$ckX())
                    }
                }
            )+
        )+
    }
}
impl_tim_ker_ck! {
    timx_ker_ck: TIM2, TIM3, TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14
    timy_ker_ck: TIM1, TIM8, TIM15, TIM16, TIM17
}
impl GetClk for LPTIM1 {
    
    fn get_clk(clocks: &CoreClocks) -> Option<Hertz> {
        
        #[cfg(not(feature = "rm0455"))]
        let ccip2r = &unsafe { &*stm32::RCC::ptr() }.d2ccip2r;
        #[cfg(feature = "rm0455")]
        let ccip2r = &unsafe { &*stm32::RCC::ptr() }.cdccip2r;
        match ccip2r.read().lptim1sel().variant() {
            Val(ccip2r::LPTIM1SEL_A::RCC_PCLK1) => Some(clocks.pclk1()),
            Val(ccip2r::LPTIM1SEL_A::PLL2_P) => clocks.pll2_p_ck(),
            Val(ccip2r::LPTIM1SEL_A::PLL3_R) => clocks.pll3_r_ck(),
            Val(ccip2r::LPTIM1SEL_A::LSE) => unimplemented!(),
            Val(ccip2r::LPTIM1SEL_A::LSI) => unimplemented!(),
            Val(ccip2r::LPTIM1SEL_A::PER) => clocks.per_ck(),
            _ => unreachable!(),
        }
    }
}
impl GetClk for LPTIM2 {
    
    fn get_clk(clocks: &CoreClocks) -> Option<Hertz> {
        
        #[cfg(not(feature = "rm0455"))]
        let srdccipr = &unsafe { &*stm32::RCC::ptr() }.d3ccipr;
        #[cfg(feature = "rm0455")]
        let srdccipr = &unsafe { &*stm32::RCC::ptr() }.srdccipr;
        match srdccipr.read().lptim2sel().variant() {
            Val(srdccipr::LPTIM2SEL_A::RCC_PCLK4) => Some(clocks.pclk4()),
            Val(srdccipr::LPTIM2SEL_A::PLL2_P) => clocks.pll2_p_ck(),
            Val(srdccipr::LPTIM2SEL_A::PLL3_R) => clocks.pll3_r_ck(),
            Val(srdccipr::LPTIM2SEL_A::LSE) => unimplemented!(),
            Val(srdccipr::LPTIM2SEL_A::LSI) => unimplemented!(),
            Val(srdccipr::LPTIM2SEL_A::PER) => clocks.per_ck(),
            _ => unreachable!(),
        }
    }
}
#[cfg(feature = "rm0455")]
impl GetClk for LPTIM3 {
    
    fn get_clk(clocks: &CoreClocks) -> Option<Hertz> {
        
        let srdccipr = &unsafe { &*stm32::RCC::ptr() }.srdccipr;
        match srdccipr.read().lptim3sel().bits() {
            0 => Some(clocks.pclk4()),
            1 => clocks.pll2_p_ck(),
            2 => clocks.pll3_r_ck(),
            3 => unimplemented!(),
            4 => unimplemented!(),
            5 => clocks.per_ck(),
            _ => unreachable!(),
        }
    }
}
#[cfg(not(feature = "rm0455"))]
macro_rules! impl_clk_lptim345 {
	($($TIMX:ident),+) => {
	    $(
            impl GetClk for $TIMX {
                
                fn get_clk(clocks: &CoreClocks) -> Option<Hertz> {
                    
                    let d3ccipr = &unsafe { &*stm32::RCC::ptr() }.d3ccipr;
                    match d3ccipr.read().lptim345sel().variant() {
                        Val(srdccipr::LPTIM345SEL_A::RCC_PCLK4) => Some(clocks.pclk4()),
                        Val(srdccipr::LPTIM345SEL_A::PLL2_P) => clocks.pll2_p_ck(),
                        Val(srdccipr::LPTIM345SEL_A::PLL3_R) => clocks.pll3_r_ck(),
                        Val(srdccipr::LPTIM345SEL_A::LSE) => unimplemented!(),
                        Val(srdccipr::LPTIM345SEL_A::LSI) => unimplemented!(),
                        Val(srdccipr::LPTIM345SEL_A::PER) => clocks.per_ck(),
                        _ => unreachable!(),
                    }
                }
            }
        )+
    }
}
#[cfg(not(feature = "rm0455"))]
impl_clk_lptim345! { LPTIM3, LPTIM4, LPTIM5 }
pub struct Enabled;
pub struct Disabled;
pub trait TimerExt<TIM> {
    type Rec: ResetEnable;
    
    
    
    fn timer<T>(self, timeout: T, prec: Self::Rec, clocks: &CoreClocks) -> TIM
    where
        T: Into<Hertz>;
    
    
    
    
    
    fn tick_timer<T>(
        self,
        frequency: T,
        prec: Self::Rec,
        clocks: &CoreClocks,
    ) -> TIM
    where
        T: Into<Hertz>;
}
#[derive(Debug)]
pub struct Timer<TIM> {
    clk: u32,
    tim: TIM,
}
#[derive(Debug)]
pub struct LpTimer<TIM, ED> {
    clk: u32,
    tim: TIM,
    timeout: Hertz,
    _enabled: PhantomData<ED>,
}
pub enum Event {
    
    TimeOut,
}
macro_rules! hal {
    ($($TIMX:ident: ($timX:ident, $Rec:ident, $cntType:ty),)+) => {
        $(
            impl Periodic for Timer<$TIMX> {}
            impl CountDown for Timer<$TIMX> {
                type Time = Hertz;
                fn start<T>(&mut self, timeout: T)
                where
                    T: Into<Hertz>,
                {
                    
                    self.pause();
                    
                    self.tim.cnt.reset();
                    
                    self.tim.cr1.modify(|_, w| w.urs().counter_only());
                    self.clear_uif_bit();
                    
                    self.set_freq(timeout);
                    
                    
                    self.tim.egr.write(|w| w.ug().set_bit());
                    
                    self.resume()
                }
                fn wait(&mut self) -> nb::Result<(), Void> {
                    if self.tim.sr.read().uif().bit_is_clear() {
                        Err(nb::Error::WouldBlock)
                    } else {
                        self.clear_uif_bit();
                        Ok(())
                    }
                }
            }
            impl TimerExt<Timer<$TIMX>> for $TIMX {
                type Rec = rec::$Rec;
                fn timer<T>(self, timeout: T,
                            prec: Self::Rec, clocks: &CoreClocks
                ) -> Timer<$TIMX>
                where
                    T: Into<Hertz>,
                {
                    let mut timer = Timer::$timX(self, prec, clocks);
                    timer.start(timeout);
                    timer
                }
                fn tick_timer<T>(self, frequency: T,
                                 prec: Self::Rec, clocks: &CoreClocks
                ) -> Timer<$TIMX>
                where
                    T: Into<Hertz>,
                {
                    let mut timer = Timer::$timX(self, prec, clocks);
                    timer.pause();
                    
                    timer.tim.cr1.modify(|_, w| w.urs().counter_only());
                    timer.clear_uif_bit();
                    
                    timer.set_tick_freq(frequency);
                    
                    
                    
                    timer.tim.egr.write(|w| w.ug().set_bit());
                    
                    timer.resume();
                    timer
                }
            }
            impl Timer<$TIMX> {
                
                
                pub fn $timX(tim: $TIMX, prec: rec::$Rec, clocks: &CoreClocks) -> Self
                {
                    
                    prec.enable().reset();
                    let clk = $TIMX::get_clk(clocks)
                        .expect("Timer input clock not running!").0;
                    Timer {
                        clk,
                        tim,
                    }
                }
                
                
                pub fn set_freq<T>(&mut self, timeout: T)
                where
                    T: Into<Hertz>,
                {
                    let timeout = timeout.into();
                    let ticks = self.clk / timeout.0;
                    self.set_timeout_ticks(ticks);
                }
                
                
                
                
                
                
                
                
                
                
                
                
                
                
                
                
                pub fn set_timeout<T>(&mut self, timeout: T)
                where
                    T: Into<core::time::Duration>
                {
                    const NANOS_PER_SECOND: u64 = 1_000_000_000;
                    let timeout = timeout.into();
                    let clk = self.clk as u64;
                    let ticks = u32::try_from(
                        clk * timeout.as_secs() +
                        clk * u64::from(timeout.subsec_nanos()) / NANOS_PER_SECOND,
                    )
                    .unwrap_or(u32::max_value());
                    self.set_timeout_ticks(ticks.max(1));
                }
                fn set_timeout_ticks(&mut self, ticks: u32) {
                    let psc = u16((ticks - 1) / (1 << 16)).unwrap();
                    self.tim.psc.write(|w| w.psc().bits(psc));
                    let arr = u16(ticks / u32(psc + 1)).unwrap();
                    self.tim.arr.write(|w| unsafe { w.bits(u32(arr)) });
                }
                
                
                
                
                
                pub fn set_tick_freq<T>(&mut self, frequency: T)
                where
                    T: Into<Hertz>,
                {
                    let frequency = frequency.into();
                    let div = self.clk / frequency.0;
                    let psc = u16(div - 1).unwrap();
                    self.tim.psc.write(|w| w.psc().bits(psc));
                    let counter_max = u32(<$cntType>::MAX);
                    self.tim.arr.write(|w| unsafe { w.bits(counter_max) });
                }
                
                
                
                
                
                pub fn apply_freq(&mut self) {
                    self.tim.egr.write(|w| w.ug().set_bit());
                }
                
                pub fn clear_uif_bit(&mut self) {
                    self.tim.sr.modify(|_, w| w.uif().clear_bit());
                }
                
                pub fn pause(&mut self) {
                    self.tim.cr1.modify(|_, w| w.cen().clear_bit());
                }
                
                pub fn resume(&mut self) {
                    self.tim.cr1.modify(|_, w| w.cen().set_bit());
                }
                
                pub fn reset_counter(&mut self) {
                    self.tim.cnt.reset();
                }
                
                pub fn counter(&self) -> u32 {
                    self.tim.cnt.read().bits()
                }
                
                pub fn listen(&mut self, event: Event) {
                    match event {
                        Event::TimeOut => {
                            
                            self.tim.dier.write(|w| w.uie().set_bit());
                        }
                    }
                }
                
                pub fn unlisten(&mut self, event: Event) {
                    match event {
                        Event::TimeOut => {
                            
                            self.tim.dier.write(|w| w.uie().clear_bit());
                        }
                    }
                }
                
                pub fn clear_irq(&mut self) {
                    self.tim.sr.modify(|_, w| {
                        
                        w.uif().clear_bit()
                    });
                }
                
                pub fn free(mut self) -> ($TIMX, rec::$Rec) {
                    
                    self.pause();
                    (self.tim, rec::$Rec { _marker: PhantomData })
                }
            }
        )+
    }
}
hal! {
    
    TIM1: (tim1, Tim1, u16),
    TIM8: (tim8, Tim8, u16),
    
    TIM2: (tim2, Tim2, u32),
    TIM3: (tim3, Tim3, u16),
    TIM4: (tim4, Tim4, u16),
    TIM5: (tim5, Tim5, u32),
    
    TIM6: (tim6, Tim6, u16),
    TIM7: (tim7, Tim7, u16),
    
    TIM12: (tim12, Tim12, u16),
    TIM13: (tim13, Tim13, u16),
    TIM14: (tim14, Tim14, u16),
    
    TIM15: (tim15, Tim15, u16),
    TIM16: (tim16, Tim16, u16),
    TIM17: (tim17, Tim17, u16),
}
macro_rules! lptim_hal {
    ($($TIMX:ident: ($timx:ident, $Rec:ident, $timXpac:ident),)+) => {
        $(
            impl Periodic for LpTimer<$TIMX, Enabled> {}
            impl CountDown for LpTimer<$TIMX, Enabled> {
                type Time = Hertz;
                fn start<T>(&mut self, timeout: T)
                where
                    T: Into<Hertz>,
                {
                    
                    self.reset_counter();
                    
                    self.tim.cr.write(|w| w.enable().disabled());
                    
                    self.priv_set_freq(timeout); 
                    
                    self.clear_irq();
                    
                    self.tim.cr.write(|w| w.cntstrt().set_bit().enable().enabled());
                }
                fn wait(&mut self) -> nb::Result<(), Void> {
                    if self.tim.isr.read().arrm().bit_is_clear() {
                        Err(nb::Error::WouldBlock)
                    } else {
                        self.clear_irq();
                        Ok(())
                    }
                }
            }
            impl TimerExt<LpTimer<$TIMX, Enabled>> for $TIMX {
                type Rec = rec::$Rec;
                fn timer<T>(self, timeout: T,
                            prec: Self::Rec, clocks: &CoreClocks
                ) -> LpTimer<$TIMX, Enabled>
                    where
                        T: Into<Hertz>,
                {
                    LpTimer::$timx(self, timeout, prec, clocks)
                }
                fn tick_timer<T>(self, _frequency: T,
                                 _prec: Self::Rec, _clocks: &CoreClocks
                ) -> LpTimer<$TIMX, Enabled>
                where
                    T: Into<Hertz>,
                {
                    unimplemented!()
                }
            }
            impl LpTimer<$TIMX, Enabled> {
                
                pub fn $timx<T>(tim: $TIMX, timeout: T,
                                prec: rec::$Rec, clocks: &CoreClocks
                ) -> Self
                where
                    T: Into<Hertz>,
                {
                    
                    prec.enable().reset();
                    let clk = $TIMX::get_clk(clocks)
                        .expect("Timer input clock not running!").0;
                    let mut timer = LpTimer {
                        clk,
                        tim,
                        timeout: Hertz(0),
                        _enabled: PhantomData,
                    };
                    timer.start(timeout);
                    timer
                }
                
                
                pub fn reset_counter(&mut self) {
                    
                    self.tim.cr.write(|w| w.countrst().set_bit().enable().enabled());
                    while self.tim.cr.read().countrst().bit_is_set() {}
                }
                
                pub fn pause(self) -> LpTimer<$TIMX, Disabled> {
                    
                    self.tim.cr.write(|w| w.enable().disabled());
                    LpTimer {
                        clk: self.clk,
                        tim: self.tim,
                        timeout: self.timeout,
                        _enabled: PhantomData,
                    }
                }
            }
            impl LpTimer<$TIMX, Disabled> {
                
                
                
                pub fn set_freq<T>(&mut self, timeout: T)
                where
                    T: Into<Hertz>,
                {
                    self.priv_set_freq(timeout); 
                    
                    self.tim.cr.write(|w| w.enable().disabled());
                }
                
                
                
                pub fn listen(&mut self, event: Event) {
                    match event {
                        Event::TimeOut => {
                            
                            self.tim.ier.modify(|_, w| w.arrmie().set_bit());
                        }
                    }
                }
                
                
                
                pub fn unlisten(&mut self, event: Event) {
                    match event {
                        Event::TimeOut => {
                            
                            self.tim.ier.modify(|_, w| w.arrmie().clear_bit());
                        }
                    }
                }
                
                pub fn resume(self) -> LpTimer<$TIMX, Enabled> {
                    
                    self.tim.cr.write(|w| w.enable().enabled());
                    self.tim.cr.write(|w| w.cntstrt().set_bit().enable().enabled());
                    LpTimer {
                        clk: self.clk,
                        tim: self.tim,
                        timeout: self.timeout,
                        _enabled: PhantomData,
                    }
                }
            }
            impl<ED> LpTimer<$TIMX, ED> {
                
                
                
                fn priv_set_freq<T>(&mut self, timeout: T)
                where
                    T: Into<Hertz>,
                {
                    self.timeout = timeout.into();
                    let clk = self.clk;
                    let frequency = self.timeout.0;
                    let ticks = clk / frequency;
                    assert!(ticks < 128 * (1 << 16));
                    
                    let (prescale, prescale_div) = match ticks / (1 << 16) {
                        0 => ($timXpac::cfgr::PRESC_A::DIV1, 1),
                        1 => ($timXpac::cfgr::PRESC_A::DIV2, 2),
                        2..=3 => ($timXpac::cfgr::PRESC_A::DIV4, 4),
                        4..=7 => ($timXpac::cfgr::PRESC_A::DIV8, 8),
                        8..=15 => ($timXpac::cfgr::PRESC_A::DIV16, 16),
                        16..=31 => ($timXpac::cfgr::PRESC_A::DIV32, 32),
                        32..=63 => ($timXpac::cfgr::PRESC_A::DIV64, 64),
                        _ => ($timXpac::cfgr::PRESC_A::DIV128, 128),
                    };
                    
                    let arr = ticks / prescale_div;
                    assert!(arr <= 0xFFFF);
                    assert!(arr > 0);
                    
                    self.tim.cfgr.modify(|_, w| w.presc().variant(prescale));
                    
                    self.tim.cr.write(|w| w.enable().enabled());
                    
                    self.tim.arr.write(|w| w.arr().bits(arr as u16));
                    while self.tim.isr.read().arrok().bit_is_clear() {}
                    self.tim.icr.write(|w| w.arrokcf().clear());
                }
                
                pub fn counter(&self) -> u32 {
                    loop {
                        
                        let count1 = self.tim.cnt.read().bits();
                        
                        let count2 = self.tim.cnt.read().bits();
                        if count1 == count2 { return count2; }
                    }
                }
                
                pub fn clear_irq(&mut self) {
                    
                    self.tim.icr.write(|w| w.arrmcf().set_bit());
                }
                
                pub fn free(self) -> ($TIMX, rec::$Rec) {
                    
                    self.tim.cr.write(|w| w.enable().disabled());
                    (self.tim, rec::$Rec { _marker: PhantomData })
                }
            }
        )+
    }
}
lptim_hal! {
    LPTIM1: (lptim1, Lptim1, lptim1),
    LPTIM2: (lptim2, Lptim2, lptim1),
    LPTIM3: (lptim3, Lptim3, lptim3),
}
#[cfg(not(feature = "rm0455"))]
lptim_hal! {
    LPTIM4: (lptim4, Lptim4, lptim3),
    LPTIM5: (lptim5, Lptim5, lptim3),
}