use core::marker::PhantomData;
use crate::rcc::APB2;
pub trait GpioExt {
    
    type Parts;
    
    fn split(self, apb2: &mut APB2) -> Self::Parts;
}
pub struct Input<MODE> {
    _mode: PhantomData<MODE>,
}
pub struct Floating;
pub struct PullDown;
pub struct PullUp;
pub struct Output<MODE> {
    _mode: PhantomData<MODE>,
}
pub struct PushPull;
pub struct OpenDrain;
pub struct Analog;
pub struct Alternate<MODE> {
    _mode: PhantomData<MODE>,
}
macro_rules! gpio {
    ($GPIOX:ident, $gpiox:ident, $gpioy:ident, $iopxenr:ident, $iopxrst:ident, $PXx:ident, [
        $($PXi:ident: ($pxi:ident, $i:expr, $MODE:ty, $CR:ident),)+
    ]) => {
        
        pub mod $gpiox {
            use core::marker::PhantomData;
            use crate::hal::digital::{InputPin, OutputPin, StatefulOutputPin, toggleable};
            use crate::device::{$gpioy, $GPIOX};
            use crate::rcc::APB2;
            use super::{
                Alternate, Floating, GpioExt, Input,
                OpenDrain,
                Output,
                PullDown, 
                PullUp,
                PushPull,
                Analog,
            };
            
            pub struct Parts {
                
                pub crl: CRL,
                
                pub crh: CRH,
                $(
                    
                    pub $pxi: $PXi<$MODE>,
                )+
            }
            impl GpioExt for $GPIOX {
                type Parts = Parts;
                fn split(self, apb2: &mut APB2) -> Parts {
                    apb2.enr().modify(|_, w| w.$iopxenr().enabled());
                    apb2.rstr().modify(|_, w| w.$iopxrst().set_bit());
                    apb2.rstr().modify(|_, w| w.$iopxrst().clear_bit());
                    Parts {
                        crl: CRL { _0: () },
                        crh: CRH { _0: () },
                        $(
                            $pxi: $PXi { _mode: PhantomData },
                        )+
                    }
                }
            }
            
            pub struct CRL {
                _0: (),
            }
            impl CRL {
                
                #[allow(dead_code)]
                pub(crate) fn cr(&mut self) -> &$gpioy::CRL {
                    unsafe { &(*$GPIOX::ptr()).crl }
                }
            }
            
            pub struct CRH {
                _0: (),
            }
            impl CRH {
                pub(crate) fn cr(&mut self) -> &$gpioy::CRH {
                    unsafe { &(*$GPIOX::ptr()).crh }
                }
            }
            
            pub struct $PXx<MODE> {
                i: u8,
                _mode: PhantomData<MODE>,
            }
            impl<MODE> OutputPin for $PXx<Output<MODE>> {
                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))) }
                }
            }
            impl<MODE> InputPin for $PXx<Input<MODE>> {
                fn is_high(&self) -> bool {
                    !self.is_low()
                }
                fn is_low(&self) -> bool {
                    
                    unsafe { (*$GPIOX::ptr()).idr.read().bits() & (1 << self.i) == 0 }
                }
            }
            impl <MODE> StatefulOutputPin for $PXx<Output<MODE>> {
                fn is_set_high(&self) -> bool {
                    !self.is_set_low()
                }
                fn is_set_low(&self) -> bool {
                    
                    unsafe { (*$GPIOX::ptr()).odr.read().bits() & (1 << self.i) == 0 }
                }
            }
            impl <MODE> toggleable::Default for $PXx<Output<MODE>> {}
            impl InputPin for $PXx<Output<OpenDrain>> {
                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<MODE> {
                    _mode: PhantomData<MODE>,
                }
                impl<MODE> $PXi<MODE> {
                    
                    pub fn into_alternate_push_pull(
                        self,
                        cr: &mut $CR,
                    ) -> $PXi<Alternate<PushPull>> {
                        let offset = (4 * $i) % 32;
                        
                        let cnf = 0b10;
                        
                        let mode = 0b11;
                        let bits = (cnf << 2) | mode;
                        
                        cr
                            .cr()
                            .modify(|r, w| unsafe {
                                w.bits((r.bits() & !(0b1111 << offset)) | (bits << offset))
                            });
                        $PXi { _mode: PhantomData }
                    }
                    pub fn into_alternate_open_drain(
                        self,
                        cr: &mut $CR,
                    ) -> $PXi<Alternate<OpenDrain>> {
                        let offset = (4 * $i) % 32;
                        
                        let cnf = 0b11;
                        
                        let mode = 0b11;
                        let bits = (cnf << 2) | mode;
                        
                        cr
                            .cr()
                            .modify(|r, w| unsafe {
                                w.bits((r.bits() & !(0b1111 << offset)) | (bits << offset))
                            });
                        $PXi { _mode: PhantomData }
                    }
                    
                    pub fn into_floating_input(
                        self,
                        cr: &mut $CR,
                    ) -> $PXi<Input<Floating>> {
                        let offset = (4 * $i) % 32;
                        
                        let cnf = 0b01;
                        
                        let mode = 0b00;
                        let bits = (cnf << 2) | mode;
                        
                        cr
                            .cr()
                            .modify(|r, w| unsafe {
                                w.bits((r.bits() & !(0b1111 << offset)) | (bits << offset))
                            });
                        $PXi { _mode: PhantomData }
                    }
                    
                    pub fn into_pull_down_input(
                        self,
                        cr: &mut $CR,
                    ) -> $PXi<Input<PullDown>> {
                        let offset = (4 * $i) % 32;
                        
                        let cnf = 0b10;
                        
                        let mode = 0b00;
                        let bits = (cnf << 2) | mode;
                        
                        
                        unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << (16 + $i))) }
                        
                        cr
                            .cr()
                            .modify(|r, w| unsafe {
                                w.bits((r.bits() & !(0b1111 << offset)) | (bits << offset))
                            });
                        $PXi { _mode: PhantomData }
                    }
                    
                    pub fn into_pull_up_input(
                        self,
                        cr: &mut $CR,
                    ) -> $PXi<Input<PullUp>> {
                        let offset = (4 * $i) % 32;
                        
                        let cnf = 0b10;
                        
                        let mode = 0b00;
                        let bits = (cnf << 2) | mode;
                        
                        
                        unsafe { (*$GPIOX::ptr()).bsrr.write(|w| w.bits(1 << $i)) }
                        
                        cr
                            .cr()
                            .modify(|r, w| unsafe {
                                w.bits((r.bits() & !(0b1111 << offset)) | (bits << offset))
                            });
                        $PXi { _mode: PhantomData }
                    }
                    
                    pub fn into_open_drain_output(
                        self,
                        cr: &mut $CR,
                    ) -> $PXi<Output<OpenDrain>> {
                        let offset = (4 * $i) % 32;
                        
                        let cnf = 0b01;
                        
                        let mode = 0b11;
                        let bits = (cnf << 2) | mode;
                        cr
                            .cr()
                            .modify(|r, w| unsafe {
                                w.bits((r.bits() & !(0b1111 << offset)) | (bits << offset))
                            });
                        $PXi { _mode: PhantomData }
                    }
                    
                    pub fn into_push_pull_output(
                        self,
                        cr: &mut $CR,
                    ) -> $PXi<Output<PushPull>> {
                        let offset = (4 * $i) % 32;
                        
                        let cnf = 0b00;
                        
                        let mode = 0b11;
                        let bits = (cnf << 2) | mode;
                        cr
                            .cr()
                            .modify(|r, w| unsafe {
                                w.bits((r.bits() & !(0b1111 << offset)) | (bits << offset))
                            });
                        $PXi { _mode: PhantomData }
                    }
                    
                    
                    pub fn into_analog(self, cr: &mut $CR) -> $PXi<Analog> {
                        let offset = (4 * $i) % 32;
                        
                        let cnf = 0b00;
                        
                        let mode = 0b00;
                        let bits = (cnf << 2) | mode;
                        
                        cr
                            .cr()
                            .modify(|r, w| unsafe {
                                w.bits((r.bits() & !(0b1111 << offset)) | (bits << offset))
                            });
                        $PXi { _mode: PhantomData }
                    }
                }
                impl<MODE> $PXi<MODE> {
                    
                    
                    
                    
                    pub fn downgrade(self) -> $PXx<MODE> {
                        $PXx {
                            i: $i,
                            _mode: self._mode,
                        }
                    }
                }
                impl<MODE> OutputPin for $PXi<Output<MODE>> {
                    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))) }
                    }
                }
                impl<MODE> StatefulOutputPin for $PXi<Output<MODE>> {
                    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 }
                    }
                }
                impl<MODE> toggleable::Default for $PXi<Output<MODE>> {}
                impl<MODE> OutputPin for $PXi<Alternate<MODE>> {
                    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))) }
                    }
                }
                impl<MODE> StatefulOutputPin for $PXi<Alternate<MODE>> {
                    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 }
                    }
                }
                impl<MODE> InputPin for $PXi<Input<MODE>> {
                    fn is_high(&self) -> bool {
                        !self.is_low()
                    }
                    fn is_low(&self) -> bool {
                        
                        unsafe { (*$GPIOX::ptr()).idr.read().bits() & (1 << $i) == 0 }
                    }
                }
                impl InputPin for $PXi<Output<OpenDrain>> {
                    fn is_high(&self) -> bool {
                        !self.is_low()
                    }
                    fn is_low(&self) -> bool {
                        
                        unsafe { (*$GPIOX::ptr()).idr.read().bits() & (1 << $i) == 0 }
                    }
                }
            )+
        }
    }
}
gpio!(GPIOA, gpioa, gpioa, iopaen, ioparst, PAx, [
    PA0: (pa0, 0, Input<Floating>, CRL),
    PA1: (pa1, 1, Input<Floating>, CRL),
    PA2: (pa2, 2, Input<Floating>, CRL),
    PA3: (pa3, 3, Input<Floating>, CRL),
    PA4: (pa4, 4, Input<Floating>, CRL),
    PA5: (pa5, 5, Input<Floating>, CRL),
    PA6: (pa6, 6, Input<Floating>, CRL),
    PA7: (pa7, 7, Input<Floating>, CRL),
    PA8: (pa8, 8, Input<Floating>, CRH),
    PA9: (pa9, 9, Input<Floating>, CRH),
    PA10: (pa10, 10, Input<Floating>, CRH),
    PA11: (pa11, 11, Input<Floating>, CRH),
    PA12: (pa12, 12, Input<Floating>, CRH),
    PA13: (pa13, 13, Input<Floating>, CRH),
    PA14: (pa14, 14, Input<Floating>, CRH),
    PA15: (pa15, 15, Input<Floating>, CRH),
]);
gpio!(GPIOB, gpiob, gpioa, iopben, iopbrst, PBx, [
    PB0: (pb0, 0, Input<Floating>, CRL),
    PB1: (pb1, 1, Input<Floating>, CRL),
    PB2: (pb2, 2, Input<Floating>, CRL),
    PB3: (pb3, 3, Input<Floating>, CRL),
    PB4: (pb4, 4, Input<Floating>, CRL),
    PB5: (pb5, 5, Input<Floating>, CRL),
    PB6: (pb6, 6, Input<Floating>, CRL),
    PB7: (pb7, 7, Input<Floating>, CRL),
    PB8: (pb8, 8, Input<Floating>, CRH),
    PB9: (pb9, 9, Input<Floating>, CRH),
    PB10: (pb10, 10, Input<Floating>, CRH),
    PB11: (pb11, 11, Input<Floating>, CRH),
    PB12: (pb12, 12, Input<Floating>, CRH),
    PB13: (pb13, 13, Input<Floating>, CRH),
    PB14: (pb14, 14, Input<Floating>, CRH),
    PB15: (pb15, 15, Input<Floating>, CRH),
]);
gpio!(GPIOC, gpioc, gpioa, iopcen, iopcrst, PCx, [
    PC13: (pc13, 13, Input<Floating>, CRH),
    PC14: (pc14, 14, Input<Floating>, CRH),
    PC15: (pc15, 15, Input<Floating>, CRH),
]);