use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use std::thread;
use std::time::Duration;

use embedded_hal::blocking::i2c::Operation;
use embedded_hal::digital::v2::OutputPin;

use esp_idf_sys::esp_sleep_pd_domain_t_ESP_PD_DOMAIN_VDDSDIO;
use esp_idf_sys::EspError;

use esp_idf_hal::gpio::{GpioPin, Unknown};
use esp_idf_hal::prelude::*;
use esp_idf_hal::spi;
use esp_idf_hal::{delay, gpio};

use anyhow::Result;
use embedded_graphics::mono_font::iso_8859_15::FONT_10X20;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::Point;
use embedded_graphics::text::Text;
use embedded_graphics::{mono_font, Drawable};

use pervasive_epd::graphics::{Display, VarDisplay};
use pervasive_epd::prelude::*;
use pervasive_epd::E2266CS0Cx;

fn main() -> Result<()> {
    // Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once,
    // or else some patches to the runtime implemented by esp-idf-sys might not link properly.
    esp_idf_sys::link_patches();

    let peripherals = Peripherals::take().unwrap();
    let pins = peripherals.pins;

    // NFC related pins
    let nfc_cs = pins.gpio4.into_output()?;
    let nfc_irq = pins.gpio15.into_input()?;

    // EPD related pins
    let epd_cs = pins.gpio38.into_output()?;
    let epd_busy = pins.gpio41.into_input()?;
    let epd_dc = pins.gpio39.into_output()?;
    let epd_rst = pins.gpio40.into_output()?;
    let mut epd_bs = pins.gpio42.into_output()?;
    epd_bs.set_low()?;

    // generic outputs
    let mut buzzer = pins.gpio16.into_output()?;

    // ui switches inputs
    let mut sw1 = pins.gpio12.into_input();
    let mut sw2 = pins.gpio13.into_input();
    let mut sw3 = pins.gpio14.into_input();
    let mut sw4 = pins.gpio21.into_input();

    // SPI Bus
    let sclk = pins.gpio5.into_output()?;
    let sdo = pins.gpio6.into_output()?;
    let sdi = pins.gpio7.into_input()?;
    let spi = peripherals.spi3;
    let config = <spi::config::Config as Default>::default().baudrate(10.MHz().into());
    let mut spi_master = spi::Master::<spi::SPI3, _, _, _, _>::new(
        spi,
        spi::Pins {
            sclk,
            sdo,
            sdi: Some(sdi),
            cs: Option::<gpio::Gpio2<gpio::Output>>::None,
        },
        config,
    )?;

    // Setup EPD
    let mut epd = E2266CS0Cx::E2266CS0Cx::new(
        &mut spi_master,
        epd_cs,
        epd_busy,
        epd_dc,
        epd_rst,
        &mut delay::Ets,
    )
    .unwrap();
    // Use display graphics from embedded-graphics
    let mut buffer = vec![
        E2266CS0Cx::DEFAULT_BACKGROUND_COLOR.get_byte_value();
        E2266CS0Cx::WIDTH as usize / 8 * E2266CS0Cx::HEIGHT as usize
    ];
    let mut display = VarDisplay::new(E2266CS0Cx::WIDTH, E2266CS0Cx::HEIGHT, &mut buffer);

    let style = MonoTextStyle::new(&FONT_10X20, BinaryColor::On);

    // Create a text at position (20, 30) and draw it using the previously defined style
    Text::new("FabReader2!", Point::new(20, 30), style).draw(&mut display)?;

    // Display updated frame
    epd.update_and_display_frame(&mut spi_master, &display.buffer(), &mut delay::Ets)?;

    println!("Hello, world!");

    loop {
        buzzer.set_high()?;
        // we are using thread::sleep here to make sure the watchdog isn't triggered
        thread::sleep(Duration::from_secs(1));

        buzzer.set_low()?;
        thread::sleep(Duration::from_secs(1));
    }

    #[allow(unreachable_code)]
    Ok(())
}

fn deep_sleep(wakeup_pin_num: u8) -> Result<()> {
    let bitmask: u64 = 0b01 << wakeup_pin_num - 1;

    // Configure RTC for wakeup if the given pin goes high
    EspError::convert(unsafe {
        esp_idf_sys::esp_sleep_enable_ext1_wakeup(
            bitmask,
            esp_idf_sys::esp_sleep_ext1_wakeup_mode_t_ESP_EXT1_WAKEUP_ANY_HIGH,
        )
    })?;
    // Power down flash
    EspError::convert(unsafe {
        esp_idf_sys::esp_sleep_pd_config(
            esp_sleep_pd_domain_t_ESP_PD_DOMAIN_VDDSDIO,
            esp_idf_sys::esp_sleep_pd_option_t_ESP_PD_OPTION_OFF,
        )
    })?;
    //Go into deep sleep
    unsafe {
        esp_idf_sys::esp_deep_sleep_start();
    }
    Ok(())
}
