use embedded_hal::digital::v2::OutputPin;
use mkl25z4::{GPIOA, GPIOB, GPIOC, GPIOD, GPIOE, SIM, SPI0, SPI1};
use mkl25z4_hal::clocks::Clocks;
use mkl25z4_hal::gpio::{self, gpioa, gpiob, gpioc, gpiod, gpioe, GpioExt};
use mkl25z4_hal::spi::{self, Phase, Polarity, Spi};
use mkl25z4_hal::time::U32Ext;

pub type DisplayBusy = gpioc::PC4<gpio::Input<gpio::PullUp>>;
pub type DisplayCs = gpioc::PC8<gpio::Output<gpio::PushPull>>;
pub type DisplayDc = gpioc::PC9<gpio::Output<gpio::PushPull>>;
pub type DisplayRst = gpioc::PC10<gpio::Output<gpio::PushPull>>;
pub type DisplayPwr = gpioc::PC11<gpio::Output<gpio::PushPull>>;

pub struct DisplayPins {
    /// Display busy (active low).
    pub busy: gpioc::PC4<gpio::Input<gpio::PullUp>>,
    /// Display chip select (active low).
    pub cs: gpioc::PC8<gpio::Output<gpio::PushPull>>,
    /// Display data/command.
    pub dc: gpioc::PC9<gpio::Output<gpio::PushPull>>,
    /// Display reset (active low).
    pub rst: gpioc::PC10<gpio::Output<gpio::PushPull>>,
    /// Display supply voltage enable (active low).
    pub pwr: gpioc::PC11<gpio::Output<gpio::PushPull>>,
}

pub struct BME280Pins {
    /// BME280 supply voltage enable (active low).
    pub pwr: gpiob::PB18<gpio::Output<gpio::PushPull>>,
    /// BME280 chip select (active low).
    pub cs: gpioc::PC2<gpio::Output<gpio::PushPull>>,
}

pub type DisplayBmeSpi = Spi<
    SPI0,
    gpioc::PC7<gpio::Alternate5>,
    gpioc::PC6<gpio::Alternate5>,
    gpioc::PC5<gpio::Alternate2>,
>;

pub type RadioIrq = gpiob::PB0<gpio::Input<gpio::PullUp>>;
pub type RadioCs = gpiob::PB1<gpio::Output<gpio::PushPull>>;
pub type RadioCe = gpiob::PB2<gpio::Output<gpio::PushPull>>;
pub type RadioPwr = gpiob::PB3<gpio::Output<gpio::PushPull>>;
pub type RadioSpi = Spi<
    SPI1,
    gpiob::PB16<gpio::Alternate2>,
    gpiob::PB17<gpio::Alternate2>,
    gpiod::PD5<gpio::Alternate2>,
>;

pub struct RadioPins {
    /// NRF IRQ line (active low).
    pub irq: RadioIrq,
    /// NRF chip select (active low).
    pub cs: RadioCs,
    /// NRF chip enable.
    pub ce: RadioCe,
    /// NRF supply voltage enable (active low).
    pub pwr: RadioPwr,
    /// SPI for the NRF module.
    pub spi: RadioSpi,
}

pub struct Pins {
    /// 5V supply for the LED (active high).
    pub enable_5v: gpioa::PA1<gpio::Output<gpio::PushPull>>,
    /// Red display frontlight LEDs (active high).
    pub red: gpioe::PE31<gpio::Output<gpio::PushPull>>,
    /// Green display frontlight LEDs (active high).
    pub green: gpioe::PE30<gpio::Output<gpio::PushPull>>,
    /// Blue display frontlight LEDs (active high).
    pub blue: gpioe::PE29<gpio::Output<gpio::PushPull>>,

    /// Top switch (active low).
    pub top_switch: gpioc::PC1<gpio::Input<gpio::Floating>>,
    /// Left switches (top to bottom, active low).
    pub left_switches: (
        gpiod::PD1<gpio::Input<gpio::PullUp>>,
        gpiod::PD2<gpio::Input<gpio::PullUp>>,
        gpiod::PD3<gpio::Input<gpio::PullUp>>,
        gpiod::PD4<gpio::Input<gpio::PullUp>>,
    ),
    /// Right switches (top to bottom, active low).
    pub right_switches: (
        gpioa::PA13<gpio::Input<gpio::PullUp>>,
        gpioa::PA12<gpio::Input<gpio::PullUp>>,
        gpioa::PA5<gpio::Input<gpio::PullUp>>,
        gpioa::PA4<gpio::Input<gpio::PullUp>>,
    ),

    /// Ambient light sensor enable (active high).
    pub als_en: gpioe::PE20<gpio::Output<gpio::PushPull>>,
    // TODO: ADC for the ambient light sensor.
    /// Ambient light sensor.
    pub als: gpioe::PE21<gpio::Input<gpio::Floating>>,

    // TODO: PWM for the buzzer.
    /// Buzzer (active high).
    pub buzzer: gpioc::PC3<gpio::Output<gpio::PushPull>>,

    pub nrf: RadioPins,

    pub display: DisplayPins,

    pub bme: BME280Pins,

    /// SPI for both display and BME280.
    pub display_bme_spi: DisplayBmeSpi,
}

impl Pins {
    pub fn configure(
        sim: &mut SIM,
        clocks: Clocks,
        gpioa: GPIOA,
        gpiob: GPIOB,
        gpioc: GPIOC,
        gpiod: GPIOD,
        gpioe: GPIOE,
        spi0: SPI0,
        spi1: SPI1,
    ) -> Pins {
        let mut gpioa = gpioa.split(sim);
        let mut gpiob = gpiob.split(sim);
        let mut gpioc = gpioc.split(sim);
        let mut gpiod = gpiod.split(sim);
        let mut gpioe = gpioe.split(sim);

        // SPI0 for the display/BME module.
        let mosi = gpioc.pc7.into_alternate(&mut gpioc.pddr);
        let miso = gpioc.pc6.into_alternate(&mut gpioc.pddr);
        let sck = gpioc.pc5.into_alternate(&mut gpioc.pddr);
        let mode = spi::Mode {
            polarity: Polarity::IdleLow,
            phase: Phase::CaptureOnFirstTransition,
        };
        let spi0 = Spi::spi0(spi0, mosi, miso, sck, mode, 500000u32.hz(), clocks, sim);

        // SPI1 for the NRF module.
        let mosi = gpiob.pb16.into_alternate(&mut gpiob.pddr);
        let miso = gpiob.pb17.into_alternate(&mut gpiob.pddr);
        let sck = gpiod.pd5.into_alternate(&mut gpiod.pddr);
        let mode = spi::Mode {
            polarity: Polarity::IdleLow,
            phase: Phase::CaptureOnFirstTransition,
        };
        let spi1 = Spi::spi1(spi1, mosi, miso, sck, mode, 500000u32.hz(), clocks, sim);

        let mut pins = Pins {
            enable_5v: gpioa.pa1.into_push_pull_output(&mut gpioa.pddr),
            red: gpioe.pe31.into_push_pull_output(&mut gpioe.pddr),
            green: gpioe.pe30.into_push_pull_output(&mut gpioe.pddr),
            blue: gpioe.pe29.into_push_pull_output(&mut gpioe.pddr),
            top_switch: gpioc.pc1.into_floating_input(&mut gpioc.pddr),
            left_switches: (
                gpiod.pd1.into_pull_up_input(&mut gpiod.pddr),
                gpiod.pd2.into_pull_up_input(&mut gpiod.pddr),
                gpiod.pd3.into_pull_up_input(&mut gpiod.pddr),
                gpiod.pd4.into_pull_up_input(&mut gpiod.pddr),
            ),
            right_switches: (
                gpioa.pa13.into_pull_up_input(&mut gpioa.pddr),
                gpioa.pa12.into_pull_up_input(&mut gpioa.pddr),
                gpioa.pa5.into_pull_up_input(&mut gpioa.pddr),
                gpioa.pa4.into_pull_up_input(&mut gpioa.pddr),
            ),
            als_en: gpioe.pe20.into_push_pull_output(&mut gpioe.pddr),
            als: gpioe.pe21.into_floating_input(&mut gpioe.pddr),
            buzzer: gpioc.pc3.into_push_pull_output(&mut gpioc.pddr),
            nrf: RadioPins {
                irq: gpiob.pb0.into_pull_up_input(&mut gpiob.pddr),
                cs: gpiob.pb1.into_push_pull_output(&mut gpiob.pddr),
                ce: gpiob.pb2.into_push_pull_output(&mut gpiob.pddr),
                pwr: gpiob.pb3.into_push_pull_output(&mut gpiob.pddr),
                spi: spi1,
            },

            display: DisplayPins {
                busy: gpioc.pc4.into_pull_up_input(&mut gpioc.pddr),
                cs: gpioc.pc8.into_push_pull_output(&mut gpioc.pddr),
                dc: gpioc.pc9.into_push_pull_output(&mut gpioc.pddr),
                rst: gpioc.pc10.into_push_pull_output(&mut gpioc.pddr),
                pwr: gpioc.pc11.into_push_pull_output(&mut gpioc.pddr),
            },

            bme: BME280Pins {
                pwr: gpiob.pb18.into_push_pull_output(&mut gpiob.pddr),
                cs: gpioc.pc2.into_push_pull_output(&mut gpioc.pddr),
            },

            display_bme_spi: spi0,
        };

        // Disable all power, set all chip-select lines high.
        pins.enable_5v.set_low().ok();
        pins.als_en.set_low().ok();
        pins.buzzer.set_low().ok();
        pins.nrf.cs.set_high().ok();
        pins.nrf.pwr.set_high().ok();
        pins.display.cs.set_high().ok();
        pins.display.pwr.set_high().ok();
        pins.bme.pwr.set_high().ok();
        pins.bme.cs.set_high().ok();

        pins
    }
}
