use crate::{
    channel::{Channel0, Channel1},
    fan_ctrl::FanPin,
    hw_rev::{HWRev, HWSettings},
    leds::Leds,
};
use eeprom24x::{self, Eeprom24x};
use stm32_eth::EthPins;
use stm32f4xx_hal::{
    adc::Adc,
    gpio::{
        gpioa::*, gpiob::*, gpioc::*, gpioe::*, gpiof::*, gpiog::*, Alternate, AlternateOD, Analog,
        Floating, GpioExt, Input, Output, PushPull, AF5,
    },
    hal::{self, blocking::spi::Transfer, digital::v2::OutputPin},
    i2c::I2c,
    otg_fs::USB,
    pac::{
        ADC1, GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, GPIOF, GPIOG, I2C1, OTG_FS_DEVICE, OTG_FS_GLOBAL,
        OTG_FS_PWRCLK, SPI2, SPI4, SPI5, TIM1, TIM3, TIM8,
    },
    pwm::{self, PwmChannels},
    rcc::Clocks,
    spi::{NoMiso, Spi, TransferModeNormal},
    time::U32Ext,
    timer::Timer,
};

pub type Eeprom = Eeprom24x<
    I2c<
        I2C1,
        (
            PB8<AlternateOD<{ stm32f4xx_hal::gpio::AF4 }>>,
            PB9<AlternateOD<{ stm32f4xx_hal::gpio::AF4 }>>,
        ),
    >,
    eeprom24x::page_size::B8,
    eeprom24x::addr_size::OneByte,
>;

pub type EthernetPins = EthPins<
    PA1<Input<Floating>>,
    PA7<Input<Floating>>,
    PB11<Input<Floating>>,
    PG13<Input<Floating>>,
    PB13<Input<Floating>>,
    PC4<Input<Floating>>,
    PC5<Input<Floating>>,
>;

pub trait ChannelPins {
    type DacSpi: Transfer<u8>;
    type DacSync: OutputPin;
    type Shdn: OutputPin;
    type VRefPin;
    type ITecPin;
    type DacFeedbackPin;
    type TecUMeasPin;
}

pub enum Channel0VRef {
    Analog(PA0<Analog>),
    Disabled(PA0<Input<Floating>>),
}

impl ChannelPins for Channel0 {
    type DacSpi = Dac0Spi;
    type DacSync = PE4<Output<PushPull>>;
    type Shdn = PE10<Output<PushPull>>;
    type VRefPin = Channel0VRef;
    type ITecPin = PA6<Analog>;
    type DacFeedbackPin = PA4<Analog>;
    type TecUMeasPin = PC2<Analog>;
}

pub enum Channel1VRef {
    Analog(PA3<Analog>),
    Disabled(PA3<Input<Floating>>),
}

impl ChannelPins for Channel1 {
    type DacSpi = Dac1Spi;
    type DacSync = PF6<Output<PushPull>>;
    type Shdn = PE15<Output<PushPull>>;
    type VRefPin = Channel1VRef;
    type ITecPin = PB0<Analog>;
    type DacFeedbackPin = PA5<Analog>;
    type TecUMeasPin = PC3<Analog>;
}

/// SPI peripheral used for communication with the ADC
pub type AdcSpi = Spi<
    SPI2,
    (
        PB10<Alternate<AF5>>,
        PB14<Alternate<AF5>>,
        PB15<Alternate<AF5>>,
    ),
    TransferModeNormal,
>;
pub type AdcNss = PB12<Output<PushPull>>;
type Dac0Spi = Spi<SPI4, (PE2<Alternate<AF5>>, NoMiso, PE6<Alternate<AF5>>), TransferModeNormal>;
type Dac1Spi = Spi<SPI5, (PF7<Alternate<AF5>>, NoMiso, PF9<Alternate<AF5>>), TransferModeNormal>;
pub type PinsAdc = Adc<ADC1>;

pub struct ChannelPinSet<C: ChannelPins> {
    pub dac_spi: C::DacSpi,
    pub dac_sync: C::DacSync,
    pub shdn: C::Shdn,
    pub vref_pin: C::VRefPin,
    pub itec_pin: C::ITecPin,
    pub dac_feedback_pin: C::DacFeedbackPin,
    pub tec_u_meas_pin: C::TecUMeasPin,
}

pub struct HWRevPins {
    pub hwrev0: stm32f4xx_hal::gpio::gpiod::PD0<Input<Floating>>,
    pub hwrev1: stm32f4xx_hal::gpio::gpiod::PD1<Input<Floating>>,
    pub hwrev2: stm32f4xx_hal::gpio::gpiod::PD2<Input<Floating>>,
    pub hwrev3: stm32f4xx_hal::gpio::gpiod::PD3<Input<Floating>>,
}

pub struct Pins {
    pub adc_spi: AdcSpi,
    pub adc_nss: AdcNss,
    pub pins_adc: PinsAdc,
    pub pwm: PwmPins,
    pub channel0: ChannelPinSet<Channel0>,
    pub channel1: ChannelPinSet<Channel1>,
}

impl Pins {
    /// Setup GPIO pins and configure MCU peripherals
    pub fn setup(
        clocks: Clocks,
        tim1: TIM1,
        tim3: TIM3,
        tim8: TIM8,
        gpioa: GPIOA,
        gpiob: GPIOB,
        gpioc: GPIOC,
        gpiod: GPIOD,
        gpioe: GPIOE,
        gpiof: GPIOF,
        gpiog: GPIOG,
        i2c1: I2C1,
        spi2: SPI2,
        spi4: SPI4,
        spi5: SPI5,
        adc1: ADC1,
        otg_fs_global: OTG_FS_GLOBAL,
        otg_fs_device: OTG_FS_DEVICE,
        otg_fs_pwrclk: OTG_FS_PWRCLK,
    ) -> (
        Self,
        Leds,
        Eeprom,
        EthernetPins,
        USB,
        Option<FanPin>,
        HWRev,
        HWSettings,
    ) {
        let gpioa = gpioa.split();
        let gpiob = gpiob.split();
        let gpioc = gpioc.split();
        let gpiod = gpiod.split();
        let gpioe = gpioe.split();
        let gpiof = gpiof.split();
        let gpiog = gpiog.split();

        let adc_spi = Self::setup_spi_adc(clocks, spi2, gpiob.pb10, gpiob.pb14, gpiob.pb15);
        let adc_nss = gpiob.pb12.into_push_pull_output();

        let pins_adc = Adc::adc1(adc1, true, Default::default());

        let pwm = PwmPins::setup(
            clocks, tim1, tim3, gpioc.pc6, gpioc.pc7, gpioe.pe9, gpioe.pe11, gpioe.pe13, gpioe.pe14,
        );

        let hwrev = HWRev::detect_hw_rev(&HWRevPins {
            hwrev0: gpiod.pd0,
            hwrev1: gpiod.pd1,
            hwrev2: gpiod.pd2,
            hwrev3: gpiod.pd3,
        });
        let hw_settings = hwrev.settings();

        let (dac0_spi, dac0_sync) = Self::setup_dac0(clocks, spi4, gpioe.pe2, gpioe.pe4, gpioe.pe6);
        let mut shdn0 = gpioe.pe10.into_push_pull_output();
        let _ = shdn0.set_low();
        let vref0_pin = if hwrev.major > 2 {
            Channel0VRef::Analog(gpioa.pa0.into_analog())
        } else {
            Channel0VRef::Disabled(gpioa.pa0)
        };
        let itec0_pin = gpioa.pa6.into_analog();
        let dac_feedback0_pin = gpioa.pa4.into_analog();
        let tec_u_meas0_pin = gpioc.pc2.into_analog();
        let channel0 = ChannelPinSet {
            dac_spi: dac0_spi,
            dac_sync: dac0_sync,
            shdn: shdn0,
            vref_pin: vref0_pin,
            itec_pin: itec0_pin,
            dac_feedback_pin: dac_feedback0_pin,
            tec_u_meas_pin: tec_u_meas0_pin,
        };

        let (dac1_spi, dac1_sync) = Self::setup_dac1(clocks, spi5, gpiof.pf7, gpiof.pf6, gpiof.pf9);
        let mut shdn1 = gpioe.pe15.into_push_pull_output();
        let _ = shdn1.set_low();
        let vref1_pin = if hwrev.major > 2 {
            Channel1VRef::Analog(gpioa.pa3.into_analog())
        } else {
            Channel1VRef::Disabled(gpioa.pa3)
        };
        let itec1_pin = gpiob.pb0.into_analog();
        let dac_feedback1_pin = gpioa.pa5.into_analog();
        let tec_u_meas1_pin = gpioc.pc3.into_analog();
        let channel1 = ChannelPinSet {
            dac_spi: dac1_spi,
            dac_sync: dac1_sync,
            shdn: shdn1,
            vref_pin: vref1_pin,
            itec_pin: itec1_pin,
            dac_feedback_pin: dac_feedback1_pin,
            tec_u_meas_pin: tec_u_meas1_pin,
        };

        let pins = Pins {
            adc_spi,
            adc_nss,
            pins_adc,
            pwm,
            channel0,
            channel1,
        };

        let leds = Leds::new(
            gpiod.pd9,
            gpiod.pd10.into_push_pull_output(),
            gpiod.pd11.into_push_pull_output(),
        );

        let eeprom_scl = gpiob.pb8.into_alternate().set_open_drain();
        let eeprom_sda = gpiob.pb9.into_alternate().set_open_drain();
        let eeprom_i2c = I2c::new(i2c1, (eeprom_scl, eeprom_sda), 400.khz(), clocks);
        let eeprom = Eeprom24x::new_24x02(eeprom_i2c, eeprom24x::SlaveAddr::default());

        let eth_pins = EthPins {
            ref_clk: gpioa.pa1,
            crs: gpioa.pa7,
            tx_en: gpiob.pb11,
            tx_d0: gpiog.pg13,
            tx_d1: gpiob.pb13,
            rx_d0: gpioc.pc4,
            rx_d1: gpioc.pc5,
        };

        let usb = USB {
            usb_global: otg_fs_global,
            usb_device: otg_fs_device,
            usb_pwrclk: otg_fs_pwrclk,
            pin_dm: gpioa.pa11.into_alternate(),
            pin_dp: gpioa.pa12.into_alternate(),
            hclk: clocks.hclk(),
        };

        let fan = if hw_settings.fan_available {
            Some(
                Timer::new(tim8, &clocks)
                    .pwm(gpioc.pc9.into_alternate(), hw_settings.fan_pwm_freq_hz.hz()),
            )
        } else {
            None
        };

        (pins, leds, eeprom, eth_pins, usb, fan, hwrev, hw_settings)
    }

    /// Configure the GPIO pins for SPI operation, and initialize SPI
    fn setup_spi_adc<M1, M2, M3>(
        clocks: Clocks,
        spi2: SPI2,
        sck: PB10<M1>,
        miso: PB14<M2>,
        mosi: PB15<M3>,
    ) -> AdcSpi {
        let sck = sck.into_alternate();
        let miso = miso.into_alternate();
        let mosi = mosi.into_alternate();
        Spi::new(
            spi2,
            (sck, miso, mosi),
            crate::ad7172::SPI_MODE,
            crate::ad7172::SPI_CLOCK,
            clocks,
        )
    }

    fn setup_dac0<M1, M2, M3>(
        clocks: Clocks,
        spi4: SPI4,
        sclk: PE2<M1>,
        sync: PE4<M2>,
        sdin: PE6<M3>,
    ) -> (Dac0Spi, <Channel0 as ChannelPins>::DacSync) {
        let sclk = sclk.into_alternate();
        let sdin = sdin.into_alternate();
        let spi = Spi::new(
            spi4,
            (sclk, NoMiso {}, sdin),
            crate::ad5680::SPI_MODE,
            crate::ad5680::SPI_CLOCK,
            clocks,
        );
        let sync = sync.into_push_pull_output();

        (spi, sync)
    }

    fn setup_dac1<M1, M2, M3>(
        clocks: Clocks,
        spi5: SPI5,
        sclk: PF7<M1>,
        sync: PF6<M2>,
        sdin: PF9<M3>,
    ) -> (Dac1Spi, <Channel1 as ChannelPins>::DacSync) {
        let sclk = sclk.into_alternate();
        let sdin = sdin.into_alternate();
        let spi = Spi::new(
            spi5,
            (sclk, NoMiso {}, sdin),
            crate::ad5680::SPI_MODE,
            crate::ad5680::SPI_CLOCK,
            clocks,
        );
        let sync = sync.into_push_pull_output();

        (spi, sync)
    }
}

pub struct PwmPins {
    pub max_v0: PwmChannels<TIM3, pwm::C1>,
    pub max_v1: PwmChannels<TIM3, pwm::C2>,
    pub max_i_pos0: PwmChannels<TIM1, pwm::C1>,
    pub max_i_pos1: PwmChannels<TIM1, pwm::C2>,
    pub max_i_neg0: PwmChannels<TIM1, pwm::C3>,
    pub max_i_neg1: PwmChannels<TIM1, pwm::C4>,
}

impl PwmPins {
    fn setup<M1, M2, M3, M4, M5, M6>(
        clocks: Clocks,
        tim1: TIM1,
        tim3: TIM3,
        max_v0: PC6<M1>,
        max_v1: PC7<M2>,
        max_i_pos0: PE9<M3>,
        max_i_pos1: PE11<M4>,
        max_i_neg0: PE13<M5>,
        max_i_neg1: PE14<M6>,
    ) -> PwmPins {
        let freq = 20u32.khz();

        fn init_pwm_pin<P: hal::PwmPin<Duty = u16>>(pin: &mut P) {
            pin.set_duty(0);
            pin.enable();
        }
        let channels = (max_v0.into_alternate(), max_v1.into_alternate());
        //let (mut max_v0, mut max_v1) = pwm::tim3(tim3, channels, clocks, freq);
        let (mut max_v0, mut max_v1) = Timer::new(tim3, &clocks).pwm(channels, freq);
        init_pwm_pin(&mut max_v0);
        init_pwm_pin(&mut max_v1);

        let channels = (
            max_i_pos0.into_alternate(),
            max_i_pos1.into_alternate(),
            max_i_neg0.into_alternate(),
            max_i_neg1.into_alternate(),
        );
        let (mut max_i_pos0, mut max_i_pos1, mut max_i_neg0, mut max_i_neg1) =
            Timer::new(tim1, &clocks).pwm(channels, freq);
        init_pwm_pin(&mut max_i_pos0);
        init_pwm_pin(&mut max_i_neg0);
        init_pwm_pin(&mut max_i_pos1);
        init_pwm_pin(&mut max_i_neg1);

        PwmPins {
            max_v0,
            max_v1,
            max_i_pos0,
            max_i_pos1,
            max_i_neg0,
            max_i_neg1,
        }
    }
}