#![allow(missing_docs)]
#![no_std]
#![no_main]

use core::convert::Infallible;

use embassy_executor::Spawner;
use esp_backtrace as _;
use log::info;

use device_envoy_esp::{
    button::PressedTo,
    button_watch,
    clock_sync::{h12_m_s, ClockSync, ClockSyncEsp, ClockSyncStaticEsp, ONE_SECOND},
    flash_block::FlashBlockEsp,
    init_and_start,
    wifi_auto::{
        fields::{TimezoneField, TimezoneFieldStatic},
        WifiAuto as _, WifiAutoEsp, WifiAutoEvent,
    },
    Error, Result,
};

esp_bootloader_esp_idf::esp_app_desc!();

const CAPTIVE_PORTAL_SSID: &str = "DeviceEnvoyClock";

button_watch! {
    ForcePortalButtonWatch {
        pin: GPIO6,
    }
}

async fn log_clock_ticks(clock_sync: &impl ClockSync) -> ! {
    loop {
        let clock_sync_tick = clock_sync.wait_for_tick().await;
        let (hours, minutes, seconds) = h12_m_s(&clock_sync_tick.local_time);
        info!(
            "Time {:02}:{:02}:{:02}, since sync {}s",
            hours,
            minutes,
            seconds,
            clock_sync_tick.since_last_sync.as_secs()
        );
    }
}

#[esp_rtos::main]
async fn main(spawner: Spawner) -> ! {
    match inner_main(spawner).await {
        Ok(infallible) => match infallible {},
        Err(error) => panic!("{error:?}"),
    }
}

async fn inner_main(spawner: Spawner) -> Result<Infallible> {
    init_and_start!(p);
    esp_println::logger::init_logger(log::LevelFilter::Info);

    let [wifi_auto_flash_block, timezone_flash_block] = FlashBlockEsp::new_array::<2>(p.FLASH)?;

    static TIMEZONE_FIELD_STATIC: TimezoneFieldStatic = TimezoneField::new_static();
    let timezone_field = TimezoneField::new(&TIMEZONE_FIELD_STATIC, timezone_flash_block);
    let button_watch6 = ForcePortalButtonWatch::new(p.GPIO6, PressedTo::Ground, spawner).await?;

    let wifi_auto = WifiAutoEsp::new(
        p.WIFI,
        wifi_auto_flash_block,
        CAPTIVE_PORTAL_SSID,
        [timezone_field],
        spawner,
    )?;

    let stack = wifi_auto
        .connect(&mut *button_watch6, |wifi_auto_event| async move {
            match wifi_auto_event {
                WifiAutoEvent::CaptivePortalReady => info!("WifiAuto: setup mode ready"),
                WifiAutoEvent::Connecting { .. } => info!("WifiAuto: connecting"),
                WifiAutoEvent::ConnectionFailed => info!("WifiAuto: connection failed"),
            }
            Ok(())
        })
        .await?;

    let offset_minutes = timezone_field
        .offset_minutes()?
        .ok_or(Error::MissingCustomWifiAutoField)?;

    static CLOCK_SYNC_STATIC: ClockSyncStaticEsp = ClockSyncEsp::new_static();
    let clock_sync = ClockSyncEsp::new(
        &CLOCK_SYNC_STATIC,
        stack,
        offset_minutes,
        Some(ONE_SECOND),
        spawner,
    );

    log_clock_ticks(&clock_sync).await
}
