#![no_std]
#![no_main]

#[rtic::app(
    device = rp_pico::hal::pac,
    dispatchers = [TIMER_IRQ_1]
    )]
mod app {
    extern crate alloc;
    use alloc::format;
    use core::fmt::Write;
    use cortex_m::delay::Delay;
    use defmt_rtt as _;
    use embedded_alloc::Heap;
    use embedded_hal::{
        digital::v2::{OutputPin, ToggleableOutputPin},
        spi::MODE_0,
    };
    use hd44780::{bus::FourBitBus, HD44780};
    use panic_probe as _;

    use rp2040_monotonic::{fugit::Duration, Rp2040Monotonic};

    use rp_pico::{
        hal::{
            clocks::{init_clocks_and_plls, Clock},
            fugit::RateExtU32,
            gpio::{
                self,
                bank0::{
                    Gpio0, Gpio1, Gpio14, Gpio16, Gpio17, Gpio18, Gpio19, Gpio2, Gpio20, Gpio21,
                    Gpio25, Gpio3, Gpio4, Gpio5,
                },
                FunctionSio, FunctionSpi, FunctionUart, Pin, PullDown, SioOutput,
            },
            sio::Sio,
            spi::{self, Spi},
            uart::{self, UartConfig, UartPeripheral},
            watchdog::Watchdog,
        },
        pac::{SPI0, UART0},
        Pins,
    };

    use hd44780_driver as hd44780;
    use max31855::{Max31855, Unit};

    #[global_allocator]
    static HEAP: Heap = Heap::empty();

    const MONO_NUM: u32 = 1;
    const MONO_DENOM: u32 = 1000000;
    const ONE_SEC_TICKS: u64 = 1000000;

    type Uart = UartPeripheral<
        uart::Enabled,
        UART0,
        (
            Pin<Gpio0, FunctionUart, PullDown>,
            Pin<Gpio1, FunctionUart, PullDown>,
        ),
    >;

    type Led = Pin<Gpio25, FunctionSio<SioOutput>, PullDown>;

    type ExternalLed = Pin<Gpio14, FunctionSio<SioOutput>, PullDown>;

    type ThermocoupleSpi = Spi<
        spi::Enabled,
        SPI0,
        (
            Pin<Gpio3, FunctionSpi, PullDown>,
            Pin<Gpio4, FunctionSpi, PullDown>,
            Pin<Gpio2, FunctionSpi, PullDown>,
        ),
    >;

    type Lcd = HD44780<
        FourBitBus<
            Pin<Gpio16, FunctionSio<SioOutput>, PullDown>,
            Pin<Gpio17, FunctionSio<SioOutput>, PullDown>,
            Pin<Gpio18, FunctionSio<SioOutput>, PullDown>,
            Pin<Gpio19, FunctionSio<SioOutput>, PullDown>,
            Pin<Gpio20, FunctionSio<SioOutput>, PullDown>,
            Pin<Gpio21, FunctionSio<SioOutput>, PullDown>,
        >,
    >;

    #[monotonic(binds = TIMER_IRQ_0, default = true)]
    type Rp2040Mono = Rp2040Monotonic;

    struct Thermocouple {
        cs: Pin<Gpio5, FunctionSio<SioOutput>, PullDown>,
        spi: ThermocoupleSpi,
    }

    impl Thermocouple {
        fn read_temp(
            &mut self,
        ) -> Result<f32, max31855::Error<core::convert::Infallible, core::convert::Infallible>>
        {
            self.spi.read_thermocouple(&mut self.cs, Unit::Celsius)
        }
    }

    fn write_lcd(lcd: &mut Lcd, delay: &mut Delay, string: &str) {
        lcd.reset(delay).unwrap();
        lcd.clear(delay).unwrap();
        lcd.write_str(string, delay).unwrap();
    }

    #[shared]
    struct Shared {
        delay: Delay,
    }

    #[local]
    struct Local {
        led: Led,
        thermocouple: Thermocouple,
        lcd: Lcd,
        external_led: ExternalLed,
    }

    #[init]
    fn init(mut ctx: init::Context) -> (Shared, Local, init::Monotonics) {
        let mut watchdog = Watchdog::new(ctx.device.WATCHDOG);
        let sio = Sio::new(ctx.device.SIO);

        // External high-speed crystal on the pico board is 12Mhz
        let external_xtal_freq_hz = 12_000_000u32;
        let clocks = init_clocks_and_plls(
            external_xtal_freq_hz,
            ctx.device.XOSC,
            ctx.device.CLOCKS,
            ctx.device.PLL_SYS,
            ctx.device.PLL_USB,
            &mut ctx.device.RESETS,
            &mut watchdog,
        )
        .ok()
        .unwrap();

        //initalize the heap
        {
            use core::mem::MaybeUninit;
            const HEAP_SIZE: usize = 1024;
            static mut HEAP_MEM: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
            unsafe { HEAP.init(HEAP_MEM.as_ptr() as usize, HEAP_SIZE) }
        }

        let mut delay =
            cortex_m::delay::Delay::new(ctx.core.SYST, clocks.system_clock.freq().to_Hz());

        let pins = Pins::new(
            ctx.device.IO_BANK0,
            ctx.device.PADS_BANK0,
            sio.gpio_bank0,
            &mut ctx.device.RESETS,
        );

        let mut uart: Uart = UartPeripheral::new(
            ctx.device.UART0,
            (pins.gpio0.into_function(), pins.gpio1.into_function()),
            &mut ctx.device.RESETS,
        )
        .enable(
            UartConfig::new(
                9600u32.Hz(),
                rp_pico::hal::uart::DataBits::Eight,
                None,
                rp_pico::hal::uart::StopBits::One,
            ),
            clocks.peripheral_clock.freq(),
        )
        .unwrap();

        defmt::info!("Program start");
        writeln!(uart, "Program start\r",).unwrap();

        let mut led_pin: Led = pins.led.into_push_pull_output();
        let mut external_led_pin: ExternalLed = pins.gpio14.into_push_pull_output();

        //cs
        let thermometer_spi_device = ctx.device.SPI0;
        let thermocouple = Thermocouple {
            cs: pins
                .gpio5
                .into_push_pull_output_in_state(gpio::PinState::Low),

            spi: Spi::<_, _, _, 8>::new(
                thermometer_spi_device,
                (
                    //mosi
                    pins.gpio3.into_function::<gpio::FunctionSpi>(),
                    //miso/do
                    pins.gpio4.into_function::<gpio::FunctionSpi>(),
                    //clk
                    pins.gpio2.into_function::<gpio::FunctionSpi>(),
                ),
            )
            .init(
                &mut ctx.device.RESETS,
                125_000_000u32.Hz(),
                4u32.MHz(),
                MODE_0,
            ),
        };

        let mut lcd: Lcd = hd44780::HD44780::new_4bit(
            pins.gpio16.into_push_pull_output(), //rs
            pins.gpio17.into_push_pull_output(), //enable
            pins.gpio18.into_push_pull_output(), //d4
            pins.gpio19.into_push_pull_output(), //d5
            pins.gpio20.into_push_pull_output(), //d6
            pins.gpio21.into_push_pull_output(), //d6
            &mut delay,
        )
        .unwrap();

        let mono = Rp2040Mono::new(ctx.device.TIMER);

        write_lcd(&mut lcd, &mut delay, "Starting");

        heartbeat::spawn().unwrap();
        second_heartbeat::spawn().unwrap();

        (
            Shared { delay },
            Local {
                led: led_pin,
                thermocouple,
                lcd,
                external_led: external_led_pin,
            },
            init::Monotonics(mono),
        )

        // loop {
        //     led_pin.set_high().unwrap();
        //     external_led_pin.set_low().unwrap();
        //     delay.delay_ms(500);
        //     led_pin.set_low().unwrap();
        //     external_led_pin.set_high().unwrap();
        //     delay.delay_ms(500);
        //     match thermocouple.read_temp() {
        //         Ok(v) => {
        //             writeln!(uart, "Current: {} \r", v).unwrap();
        //             write_lcd(&mut lcd, &mut delay, &format!("{:02.2} C", v))
        //         }
        //         Err(e) => defmt::error!("error reading temp {}", defmt::Debug2Format(&e)),
        //     };
        // }
    }
    #[task(local = [led])]
    fn heartbeat(ctx: heartbeat::Context) {
        // Flicker the built-in LED
        ctx.local.led.toggle().unwrap();

        // Re-spawn this task after 1 second
        let one_second = Duration::<u64, MONO_NUM, MONO_DENOM>::from_ticks(ONE_SEC_TICKS);
        heartbeat::spawn_after(one_second).unwrap();
    }
    #[task(local = [external_led])]
    fn second_heartbeat(ctx: second_heartbeat::Context) {
        // Flicker the built-in LED
        ctx.local.external_led.toggle().unwrap();

        // Re-spawn this task after 1 second
        let one_and_one_half_second =
            Duration::<u64, MONO_NUM, MONO_DENOM>::from_ticks((ONE_SEC_TICKS as f64*1.14)as u64);
        second_heartbeat::spawn_after(one_and_one_half_second).unwrap();
    }
}
