use crate::rcc::AHB;
use bobbin_bits::*;
use core::intrinsics::transmute;
use core::marker::PhantomData;
#[allow(deprecated)]
use hal::digital::{toggleable, v2, InputPin, OutputPin, StatefulOutputPin};
pub trait GPIOPin {
    
    fn group(&self) -> Group;
    
    fn index(&self) -> u8;
}
pub trait PinMode {
    
    fn pin_mode() -> U2;
}
pub struct Input;
impl PinMode for Input {
    
    fn pin_mode() -> U2 {
        U2::B00
    }
}
pub struct Output<OT: OutputType, OS: OutputSpeed> {
    _output_mode: PhantomData<OT>,
    _output_speed: PhantomData<OS>,
}
impl<OT: OutputType, OS: OutputSpeed> PinMode for Output<OT, OS> {
    fn pin_mode() -> U2 {
        U2::B01
    }
}
pub struct AltFn<AN: AltFnNum, OT: OutputType, OS: OutputSpeed> {
    _afnum: PhantomData<AN>,
    _output_mode: PhantomData<OT>,
    _output_speed: PhantomData<OS>,
}
impl<AN: AltFnNum, OT: OutputType, OS: OutputSpeed> PinMode
    for AltFn<AN, OT, OS>
{
    fn pin_mode() -> U2 {
        U2::B10
    }
}
pub struct Analog;
impl PinMode for Analog {
    fn pin_mode() -> U2 {
        U2::B11
    }
}
pub trait PullType {
    
    fn pull_type(&self) -> U2;
}
pub struct PullNone;
impl PullType for PullNone {
    fn pull_type(&self) -> U2 {
        U2::B00
    }
}
pub struct PullUp;
impl PullType for PullUp {
    fn pull_type(&self) -> U2 {
        U2::B01
    }
}
pub struct PullDown;
impl PullType for PullDown {
    fn pull_type(&self) -> U2 {
        U2::B10
    }
}
pub struct PullReserved;
impl PullType for PullReserved {
    fn pull_type(&self) -> U2 {
        U2::B11
    }
}
pub trait OutputSpeed {
    
    fn output_speed(&self) -> U2;
}
pub struct LowSpeed;
impl OutputSpeed for LowSpeed {
    fn output_speed(&self) -> U2 {
        U2::B00
    }
}
pub struct MediumSpeed;
impl OutputSpeed for MediumSpeed {
    fn output_speed(&self) -> U2 {
        U2::B01
    }
}
pub struct FastSpeed;
impl OutputSpeed for FastSpeed {
    fn output_speed(&self) -> U2 {
        U2::B10
    }
}
pub struct HighSpeed;
impl OutputSpeed for HighSpeed {
    fn output_speed(&self) -> U2 {
        U2::B11
    }
}
pub trait OutputType {
    
    fn output_type(&self) -> U1;
}
pub struct PushPull;
impl OutputType for PushPull {
    fn output_type(&self) -> U1 {
        U1::B0
    }
}
pub struct OpenDrain;
impl OutputType for OpenDrain {
    fn output_type(&self) -> U1 {
        U1::B1
    }
}
pub trait AltFnNum {
    
    fn alt_fn_num(&self) -> U4;
}
macro_rules! gen_af {
    ([$(($af:ident, $bit:ident)) , +]) => {
        $(
            
            pub struct $af;
            impl AltFnNum for $af {
                fn alt_fn_num(&self) -> U4 {
                    U4::$bit
                }
            }
        )+
    }
}
gen_af!([(AF0, B0000),
         (AF1, B0001),
         (AF2, B0010),
         (AF3, B0011),
         (AF4, B0100),
         (AF5, B0101),
         (AF6, B0110),
         (AF7, B0111),
         (AF8, B1000),
         (AF9, B1001),
         (AF10, B1010),
         (AF11, B1011),
         (AF12, B1100),
         (AF13, B1101),
         (AF14, B1110),
         (AF15, B1111)]);
pub trait GpioExt {
    
    type Ports;
    
    fn split(self, ahb: &mut AHB) -> Self::Ports;
}
macro_rules! gpio {
    ($GPIOX:ident, $Gpiox:ident, $gpiox:ident, $iopxenr:ident, $iopxrst:ident, $group: ident, $PXx:ident, [
        $($PXi:ident: ($pxi:ident, $i:expr, $AFR:ident),)+
    ]) => {
        use crate::pac::$GPIOX;
        
        pub struct $Gpiox {
            $(
                
                pub $pxi: $PXi<PullNone, Input>,
            )+
        }
        impl GpioExt for $GPIOX {
            type Ports = $Gpiox;
            fn split(self, ahb: &mut AHB) -> Self::Ports {
                ahb.enr().modify(|_, w| w.$iopxenr().enabled());
                ahb.rstr().modify(|_, w| w.$iopxrst().set_bit());
                ahb.rstr().modify(|_, w| w.$iopxrst().clear_bit());
                $Gpiox {
                    $(
                        $pxi: $PXi {
                            _pullup_state: PhantomData,
                            _pin_mode: PhantomData
                        },
                    )+
                }
            }
        }
        impl $Gpiox {
            
            fn set_pin_mode<PM: PinMode>(index: u32) {
                let offset = 2 * index;
                let mode_bits:u32 = PM::pin_mode().into();
                let moder = unsafe { &(*$GPIOX::ptr()).moder };
                
                moder.modify(|r, w| unsafe {
                    w.bits((r.bits()
                            & !(0b11 << offset))
                           | (mode_bits << offset))
                });
            }
            fn set_output_speed<OS: OutputSpeed>(index: u32, os: OS) {
                let offset = 2 * index;
                let ospeedr = unsafe { &(*$GPIOX::ptr()).ospeedr };
                let speed_bits:u32 = os.output_speed().into();
                ospeedr.modify(|r, w| unsafe {
                    w.bits((r.bits()
                            & !(0b11 << offset))
                           | (speed_bits << offset))
                });
            }
            fn set_output_type<OT: OutputType>(index: u32, ot: OT) {
                let otyper = unsafe { &(*$GPIOX::ptr()).otyper };
                let type_bits:u32 = ot.output_type().into();
                otyper.modify(|r, w| unsafe {
                    w.bits(r.bits() | (type_bits << index))
                });
            }
        }
        
        pub struct $PXx<PT: PullType, PM: PinMode> {
            i: u8,
            _pullup_state: PhantomData<PT>,
            _pin_mode: PhantomData<PM>
        }
        #[allow(deprecated)]
        impl<PT: PullType, OT: OutputType, OS: OutputSpeed>
            OutputPin for $PXx<PT, Output<OT, OS>> {
                fn set_high(&mut self) {
                    
                    unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << self.i)) }
                }
                fn set_low(&mut self) {
                    
                    unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << (16 + self.i))) }
                }
            }
        #[allow(deprecated)]
        impl<PT: PullType,
        AN: AltFnNum,
        OT: OutputType,
        OS: OutputSpeed> OutputPin for $PXx<PT, AltFn<AN, OT, OS>> {
            fn set_high(&mut self) {
                
                unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << self.i)) }
            }
            fn set_low(&mut self) {
                
                unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << (16 + self.i))) }
            }
        }
        #[allow(deprecated)]
        impl<PT: PullType> InputPin for $PXx<PT, Input> {
            fn is_high(&self) -> bool {
                !self.is_low()
            }
            fn is_low(&self) -> bool {
                
                unsafe { (*$GPIOX::ptr()).idr.read().bits() & (1 << self.i) == 0 }
            }
        }
        $(
            
            pub struct $PXi<PT: PullType, PM: PinMode> {
                _pullup_state: PhantomData<PT>,
                _pin_mode: PhantomData<PM>
            }
            impl <PT: PullType, PM: PinMode> GPIOPin for $PXi<PT, PM> {
                fn group(&self) -> Group {
                    Group::$group
                }
                fn index(&self) -> u8 {
                    $i
                }
            }
            impl<PT: PullType, PM: PinMode> $PXi<PT, PM> {
                
                
                
                
                pub fn downgrade(self) -> $PXx<PT, PM> {
                    $PXx {
                        i: $i,
                        _pullup_state: PhantomData,
                        _pin_mode: PhantomData
                    }
                }
                
                pub fn pull_type<NPT: PullType>(self, pt: NPT)
                                                -> $PXi<NPT, PM>
                {
                    let shift = 0 + ($i << 1);
                    let pupdr = unsafe { &(*$GPIOX::ptr()).pupdr };
                    let pd_bits:u32 = pt.pull_type().into();
                    pupdr.modify(|r, w| unsafe {
                        w.bits((r.bits() & !(0b11 << shift))
                               | (pd_bits << shift))
                    });
                    unsafe { transmute(self) }
                }
                
                
                
                
                pub fn input(self) -> $PXi<PT, Input> {
                    $Gpiox::set_pin_mode::<Input>($i);
                    unsafe { transmute(self) }
                }
                
                pub fn analog(self) -> $PXi<PT, Analog> {
                    $Gpiox::set_pin_mode::<Analog>($i);
                    unsafe { transmute(self) }
                }
                
                pub fn output(self) -> $PXi<PT, Output<PushPull, LowSpeed>> {
                    let result: $PXi<PT, Output<PushPull, LowSpeed>> =
                        unsafe { transmute(self) };
                    
                    let result2 = result
                        .output_type(PushPull)
                        .output_speed(LowSpeed);
                    $Gpiox::set_pin_mode::<Output<PushPull, LowSpeed>>($i);
                    result2
                }
                
                pub fn alternating<AFN: AltFnNum>(self, af: AFN) -> $PXi<PT, AltFn<AFN, PushPull, LowSpeed>> {
                    let result: $PXi<PT, AltFn<AFN, PushPull, LowSpeed>> =
                        unsafe { transmute(self) };
                    
                    let result2 = result
                        .alt_fn(af)
                        .output_type(PushPull)
                        .output_speed(LowSpeed);
                    $Gpiox::set_pin_mode::<AltFn<AFN, PushPull, LowSpeed>>($i);
                    result2
                }
            }
            impl<PT: PullType, OT: OutputType, OS: OutputSpeed> $PXi<PT, Output<OT, OS>> {
                
                pub fn output_type<NOT: OutputType>(self, ot: NOT) -> $PXi<PT, Output<NOT, OS>> {
                    $Gpiox::set_output_type($i, ot);
                    unsafe { transmute(self) }
                }
                
                pub fn push_pull(self) -> $PXi<PT, Output<PushPull, OS>> {
                    self.output_type(PushPull)
                }
                
                pub fn open_drain(self) -> $PXi<PT, Output<OpenDrain, OS>> {
                    self.output_type(OpenDrain)
                }
                
                pub fn output_speed<NOS: OutputSpeed>(self, os: NOS) -> $PXi<PT, Output<OT, NOS>> {
                    $Gpiox::set_output_speed($i, os);
                    unsafe { transmute(self) }
                }
            }
            impl<PT: PullType, AFN: AltFnNum, OT: OutputType, OS: OutputSpeed> $PXi<PT, AltFn<AFN, OT, OS>> {
                
                pub fn output_type<NOT: OutputType>(self, ot: NOT) -> $PXi<PT, AltFn<AFN, NOT, OS>> {
                    $Gpiox::set_output_type($i, ot);
                    unsafe { transmute(self) }
                }
                
                pub fn output_speed<NOS: OutputSpeed>(self, os: NOS) -> $PXi<PT, AltFn<AFN, OT, NOS>> {
                    $Gpiox::set_output_speed($i, os);
                    unsafe { transmute(self) }
                }
                
                pub fn alt_fn<NAFN: AltFnNum>(self, af: NAFN) -> $PXi<PT, AltFn<NAFN, OT, OS>> {
                    let index = $i & (8 - 1);
                    let shift: usize = 0 + (index << 2);
                    let af_bits: u32 = af.alt_fn_num().into();
                    let afr = unsafe { &(*$GPIOX::ptr()).$AFR };
                    afr.modify(|r, w| unsafe {
                        w.bits((r.bits() & !(0b1111 << shift))
                               | (af_bits << shift))
                    });
                    unsafe { transmute(self) }
                }
            }
            #[allow(deprecated)]
            impl<PT: PullType, OT:OutputType, OS:OutputSpeed> OutputPin
                for $PXi<PT, Output<OT, OS>> {
                    fn set_high(&mut self) {
                        
                        unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << $i)) }
                    }
                    fn set_low(&mut self) {
                        
                        unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << (16 + $i))) }
                    }
                }
            #[allow(deprecated)]
            impl<PT: PullType, AN: AltFnNum, OT:OutputType, OS:OutputSpeed> OutputPin
                for $PXi<PT, AltFn<AN, OT, OS>> {
                    fn set_high(&mut self) {
                        
                        unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << $i)) }
                    }
                    fn set_low(&mut self) {
                        
                        unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << (16 + $i))) }
                    }
                }
            #[allow(deprecated)]
            impl<PT: PullType, OT:OutputType, OS:OutputSpeed> StatefulOutputPin
                for $PXi<PT, Output<OT, OS>> {
                    fn is_set_high(&self) -> bool {
                        !self.is_set_low()
                    }
                    fn is_set_low(&self) -> bool {
                        
                        unsafe { (*$GPIOX::ptr()).odr.read().bits() & (1 << $i) == 0 }
                    }
                }
            #[allow(deprecated)]
            impl<PT: PullType, OT:OutputType, OS:OutputSpeed> toggleable::Default
                for $PXi<PT, Output<OT, OS>> {}
            impl<PT: PullType, OT:OutputType, OS:OutputSpeed> v2::toggleable::Default
                for $PXi<PT, Output<OT, OS>> {}
            #[allow(deprecated)]
            impl<PT: PullType> InputPin for $PXi<PT, Input> {
                fn is_high(&self) -> bool {
                    !self.is_low()
                }
                fn is_low(&self) -> bool {
                    
                    unsafe { (*$GPIOX::ptr()).idr.read().bits() & (1 << $i) == 0 }
                }
            }
        )+
    }
}
pub enum Group {
    
    A,
    
    B,
    
    C,
    
    D,
    
    E,
    
    F,
    
    G,
}
gpio!(GPIOA, Gpioa, gpioa, iopaen, ioparst, A, PAx, [
    PA0: (pa0, 0, afrl),
    PA1: (pa1, 1, afrl),
    PA2: (pa2, 2, afrl),
    PA3: (pa3, 3, afrl),
    PA4: (pa4, 4, afrl),
    PA5: (pa5, 5, afrl),
    PA6: (pa6, 6, afrl),
    PA7: (pa7, 7, afrl),
    PA8: (pa8, 8, afrh),
    PA9: (pa9, 9, afrh),
    PA10: (pa10, 10, afrh),
    PA11: (pa11, 11, afrh),
    PA12: (pa12, 12, afrh),
    PA13: (pa13, 13, afrh),
    PA14: (pa14, 14, afrh),
    PA15: (pa15, 15, afrh),
]);
gpio!(GPIOB, Gpiob, gpiob, iopben, iopbrst, B, PBx, [
    PB0: (pb0, 0, afrl),
    PB1: (pb1, 1, afrl),
    PB2: (pb2, 2, afrl),
    PB3: (pb3, 3, afrl),
    PB4: (pb4, 4, afrl),
    PB5: (pb5, 5, afrl),
    PB6: (pb6, 6, afrl),
    PB7: (pb7, 7, afrl),
    PB8: (pb8, 8, afrh),
    PB9: (pb9, 9, afrh),
    PB10: (pb10, 10, afrh),
    PB11: (pb11, 11, afrh),
    PB12: (pb12, 12, afrh),
    PB13: (pb13, 13, afrh),
    PB14: (pb14, 14, afrh),
    PB15: (pb15, 15, afrh),
]);
gpio!(GPIOC, Gpioc, gpioc, iopcen, iopcrst, C, PCx, [
    PC0: (pc0, 0, afrl),
    PC1: (pc1, 1, afrl),
    PC2: (pc2, 2, afrl),
    PC3: (pc3, 3, afrl),
    PC4: (pc4, 4, afrl),
    PC5: (pc5, 5, afrl),
    PC6: (pc6, 6, afrl),
    PC7: (pc7, 7, afrl),
    PC8: (pc8, 8, afrh),
    PC9: (pc9, 9, afrh),
    PC10: (pc10, 10, afrh),
    PC11: (pc11, 11, afrh),
    PC12: (pc12, 12, afrh),
    PC13: (pc13, 13, afrh),
    PC14: (pc14, 14, afrh),
    PC15: (pc15, 15, afrh),
]);
gpio!(GPIOD, Gpiod, gpiod, iopden, iopdrst, D, PDx, [
    PD0: (pd0, 0, afrl),
    PD1: (pd1, 1, afrl),
    PD2: (pd2, 2, afrl),
    PD3: (pd3, 3, afrl),
    PD4: (pd4, 4, afrl),
    PD5: (pd5, 5, afrl),
    PD6: (pd6, 6, afrl),
    PD7: (pd7, 7, afrl),
    PD8: (pd8, 8, afrh),
    PD9: (pd9, 9, afrh),
    PD10: (pd10, 10, afrh),
    PD11: (pd11, 11, afrh),
    PD12: (pd12, 12, afrh),
    PD13: (pd13, 13, afrh),
    PD14: (pd14, 14, afrh),
    PD15: (pd15, 15, afrh),
]);
gpio!(GPIOE, Gpioe, gpioe, iopeen, ioperst, E, PEx, [
    PE0: (pe0, 0, afrl),
    PE1: (pe1, 1, afrl),
    PE2: (pe2, 2, afrl),
    PE3: (pe3, 3, afrl),
    PE4: (pe4, 4, afrl),
    PE5: (pe5, 5, afrl),
    PE6: (pe6, 6, afrl),
    PE7: (pe7, 7, afrl),
    PE8: (pe8, 8, afrh),
    PE9: (pe9, 9, afrh),
    PE10: (pe10, 10, afrh),
    PE11: (pe11, 11, afrh),
    PE12: (pe12, 12, afrh),
    PE13: (pe13, 13, afrh),
    PE14: (pe14, 14, afrh),
    PE15: (pe15, 15, afrh),
]);
gpio!(GPIOF, Gpiof, gpiof, iopfen, iopfrst, F, PFx, [
    PF0: (pf0, 0, afrl),
    PF1: (pf1, 1, afrl),
    PF2: (pf2, 2, afrl),
    PF4: (pf3, 4, afrl),
    PF6: (pf6, 6, afrl),
    PF9: (pf9, 9, afrh),
    PF10: (pf10, 10, afrh),
]);