#![no_main]
#![no_std]

mod display;
mod information;
mod pins;
mod radio;
mod sensors;

// TODO: Enable warnings again.
#[allow(unused)]
mod assets {
    include!(concat!(env!("OUT_DIR"), "/assets.rs"));
}

use mkl25z4_hal::clocks::ClockConfiguration;
use mkl25z4_hal::time::{CopyableMonoTimer, NonCopyableMonoTimer, U32Ext};
use panic_semihosting as _;

use display::{Display, DisplayState};
use information::{Information, TemperatureHumidity};
use pins::{BME280Pins, DisplayBmeSpi};
use radio::Radio;

#[rtfm::app(device = mkl25z4_hal::mkl25z4, peripherals = true)]
const APP: () = {
    struct Resources {
        info: Information,
        display_state: DisplayState,
        display: Display,
        display_bme_spi: Option<DisplayBmeSpi>,
        bme_pins: Option<BME280Pins>,
        radio: Option<Radio>,
        time: CopyableMonoTimer,
    }

    #[init(spawn = [fetch_update])]
    fn init(ctx: init::Context) -> init::LateResources {
        let mut sim = ctx.device.SIM;
        mkl25z4_hal::watchdog::disable(&mut sim);
        let clocks = ClockConfiguration::new()
            .use_irc()
            .core_clock(24.mhz())
            .bus_clock(24.mhz())
            .apply(&mut sim, ctx.device.OSC0, ctx.device.MCG);

        let mut pins = pins::Pins::configure(
            &mut sim,
            clocks,
            ctx.device.GPIOA,
            ctx.device.GPIOB,
            ctx.device.GPIOC,
            ctx.device.GPIOD,
            ctx.device.GPIOE,
            ctx.device.SPI0,
            ctx.device.SPI1,
        );

        let time =
            CopyableMonoTimer::new(NonCopyableMonoTimer::new(ctx.device.PIT, clocks, &mut sim));

        let display = Display::init(pins.display, &mut pins.display_bme_spi, time);

        let radio = Radio::init(pins.nrf, time);

        // Fetch the first update.
        ctx.spawn.fetch_update().ok();

        init::LateResources {
            info: Information {
                time: 0,
                valid: false,
                inside: None,
                outside: None,
                bathroom: None,
                pressure: None,
            },
            display_state: DisplayState{
                // TODO
            },
            display,
            display_bme_spi: Some(pins.display_bme_spi),
            bme_pins: Some(pins.bme),
            radio: Some(radio),
            time,
        }
    }

    #[task(binds = LPTMR0, priority = 3, spawn = [fetch_update])]
    fn lptmr0_interrupt(ctx: lptmr0_interrupt::Context) {
        // Restart the timer.
        // TODO

        // Trigger an update of the displayed values.
        ctx.spawn.fetch_update().ok();
    }

    #[task(binds = LLWU, priority = 3)]
    fn llwu_interrupt(_: llwu_interrupt::Context) {
        // TODO
    }

    #[task(priority = 1, spawn = [update_display], resources = [info, bme_pins, display_bme_spi, &time, radio])]
    fn fetch_update(mut ctx: fetch_update::Context) {
        // Fetch local updates.
        let time = ctx.resources.time.clone();
        let pins = ctx.resources.bme_pins.take().unwrap();
        let (pins, values) = ctx.resources.display_bme_spi.lock(|display_bme_spi| {
            let spi = display_bme_spi.take().unwrap();
            let (pins, spi, values) = sensors::bme280(pins, spi, time);
            *display_bme_spi = Some(spi);
            (pins, values)
        });
        *ctx.resources.bme_pins = Some(pins);

        // Send local measurements to the base station. This needs to be done before fetching data
        // from the base station as the base station might return the data to us.
        let mut radio = ctx.resources.radio.as_mut().unwrap().enable();
        radio.send_sensor_values(values);
        // TODO

        // Fetch updates via radio.
        // TODO
        ctx.resources.info.lock(|info| {
            info.inside = Some(TemperatureHumidity {
                temperature: values.temperature,
                humidity: values.humidity,
            });
            info.pressure = Some(values.pressure);
        });

        // If the update failed, simply increment the time and place a hint that
        // the information might be invalid.
        // TODO

        // Update the display to show the new values.
        ctx.spawn.update_display().ok();
    }

    #[task(priority = 2, resources = [info, display_state, display, display_bme_spi, &time])]
    fn update_display(ctx: update_display::Context) {
        ctx.resources.display.update(
            &ctx.resources.display_state,
            &ctx.resources.info,
            ctx.resources.display_bme_spi.as_mut().unwrap(),
            ctx.resources.time.clone(),
        );
    }

    #[idle]
    fn idle(_cx: idle::Context) -> ! {
        // Sleep
        loop {
            // wfi needs to be disabled for GDB
            /*// add some side effect to prevent this from turning into a UDF instruction
            // see rust-lang/rust#28728 for details
            atomic::compiler_fence(Ordering::SeqCst);*/
            cortex_m::asm::wfi();
        }
    }

    extern "C" {
        fn I2C0();
        fn I2C1();
    }
};
