#![no_std]
#![no_main]
#![feature(impl_trait_in_assoc_type)]

use alloc::string::String as AllocString;
use core::convert::Infallible;
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};

use defmt::info;
use embassy_executor::Spawner;
use embassy_time::{Delay, Instant, Timer};
use embedded_graphics::{
    prelude::{DrawTarget, OriginDimensions, Pixel, Point, Size},
    primitives::Rectangle,
};
use embedded_hal::delay::DelayNs;
use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice};
use embedded_storage::{ReadStorage, Storage};
use esp_backtrace as _;
use esp_bootloader_esp_idf::partitions::{read_partition_table, PARTITION_TABLE_MAX_LEN};
use esp_hal::gpio::{Flex, Input, InputConfig, Level, Output, OutputConfig, Pull};
use esp_hal::i2c::master::{Config as I2cConfig, I2c};
use esp_hal::interrupt::software::SoftwareInterruptControl;
use esp_hal::timer::timg::TimerGroup;
use esp_hal::{clock::CpuClock, ram, spi::master::Spi, time::Rate};
use esp_hal::{spi, Blocking};
use esp_println as _;
use esp_radio::wifi::{
    scan::ScanConfig, sta::StationConfig, AuthenticationMethod, Config as WifiConfig,
    ControllerConfig, WifiController, WifiError as RadioWifiError,
};
use esp_storage::FlashStorage;
use kinda::{
    App, AppCommand, InputEvent, RefreshHint, WifiAccessPoint, WifiCredentials, WifiEvent,
    WifiFailure, WifiNetworkList, WifiPassword, WifiSecurity, WifiSsid, MAX_NETWORKS,
    WIFI_CREDENTIAL_RECORD_BYTES,
};
use lm3630a::{
    Address, Bank, Brightness, Configuration, ControlConfig, FullScaleCurrent, Led2Bank, Lm3630a,
    Mapping,
};
// use panic_rtt_target as _;
use ssd1677::{
    Framebuffer, Region, Ssd1677, ThresholdFramebuffer, BYTES_PER_ROW, FRAME_BYTES, HEIGHT, WIDTH,
};
use static_cell::StaticCell;

extern crate alloc;

esp_bootloader_esp_idf::esp_app_desc!();

static DISPLAY_BUFFER: StaticCell<[u8; FRAME_BYTES]> = StaticCell::new();
static PREVIOUS_DISPLAY_BUFFER: StaticCell<[u8; FRAME_BYTES]> = StaticCell::new();
static DISPLAY_BUSY_STATS: BusyStats = BusyStats::new();

struct BusyStats {
    reads: AtomicU32,
    high_reads: AtomicU32,
    rising_edges: AtomicU32,
    falling_edges: AtomicU32,
    last_high: AtomicBool,
    has_last: AtomicBool,
}

impl BusyStats {
    const fn new() -> Self {
        Self {
            reads: AtomicU32::new(0),
            high_reads: AtomicU32::new(0),
            rising_edges: AtomicU32::new(0),
            falling_edges: AtomicU32::new(0),
            last_high: AtomicBool::new(false),
            has_last: AtomicBool::new(false),
        }
    }

    fn reset(&self) {
        self.reads.store(0, Ordering::Relaxed);
        self.high_reads.store(0, Ordering::Relaxed);
        self.rising_edges.store(0, Ordering::Relaxed);
        self.falling_edges.store(0, Ordering::Relaxed);
        self.last_high.store(false, Ordering::Relaxed);
        self.has_last.store(false, Ordering::Relaxed);
    }

    fn record(&self, high: bool) {
        self.reads.fetch_add(1, Ordering::Relaxed);
        if high {
            self.high_reads.fetch_add(1, Ordering::Relaxed);
        }

        let had_last = self.has_last.swap(true, Ordering::Relaxed);
        let previous = self.last_high.swap(high, Ordering::Relaxed);
        if had_last && previous != high {
            if high {
                self.rising_edges.fetch_add(1, Ordering::Relaxed);
            } else {
                self.falling_edges.fetch_add(1, Ordering::Relaxed);
            }
        }
    }

    fn log(&self, label: &str, elapsed_ms: u64) {
        let reads = self.reads.load(Ordering::Relaxed);
        let high_reads = self.high_reads.load(Ordering::Relaxed);
        let rising_edges = self.rising_edges.load(Ordering::Relaxed);
        let falling_edges = self.falling_edges.load(Ordering::Relaxed);
        info!(
            "Display BUSY during {}: elapsed={}ms reads={} high_reads={} rising_edges={} falling_edges={}",
            label, elapsed_ms, reads, high_reads, rising_edges, falling_edges
        );

        if high_reads == 0 {
            defmt::warn!(
                "Display BUSY never read high during {}; BUSY may be open/stuck low or the display may not be accepting commands",
                label
            );
        }
    }
}

struct DiagnosticBusyPin {
    pin: Flex<'static>,
}

impl DiagnosticBusyPin {
    fn new(mut pin: Flex<'static>) -> Self {
        pin.set_output_enable(false);
        pin.set_input_enable(true);
        pin.apply_input_config(&InputConfig::default());
        Self { pin }
    }
}

impl embedded_hal::digital::ErrorType for DiagnosticBusyPin {
    type Error = Infallible;
}

impl embedded_hal::digital::InputPin for DiagnosticBusyPin {
    fn is_high(&mut self) -> Result<bool, Self::Error> {
        let high = self.pin.is_high();
        DISPLAY_BUSY_STATS.record(high);
        Ok(high)
    }

    fn is_low(&mut self) -> Result<bool, Self::Error> {
        let low = self.pin.is_low();
        DISPLAY_BUSY_STATS.record(!low);
        Ok(low)
    }
}

struct DisplayPins {
    cs: Flex<'static>,
    dc: Flex<'static>,
    rst: Flex<'static>,
    busy: Flex<'static>,
    sck: Flex<'static>,
    mosi: Flex<'static>,
}

#[derive(Clone, Copy)]
enum DisplayOutputSignal {
    Cs,
    Dc,
    Rst,
    Sck,
    Mosi,
}

impl DisplayPins {
    async fn run_diagnostics(&mut self) {
        info!("Display GPIO diagnostics start");
        self.configure_all_inputs(Pull::Down);
        Timer::after_millis(1).await;
        self.log_levels("Display GPIO pulldown sample");

        self.configure_all_inputs(Pull::Up);
        Timer::after_millis(1).await;
        self.log_levels("Display GPIO pullup sample");

        self.run_busy_pull_test().await;
        self.run_reset_busy_test().await;
        self.run_bitbang_controller_test().await;
        self.run_short_scan().await;

        self.configure_all_inputs(Pull::Down);
        info!("Display GPIO diagnostics end");
    }

    fn configure_all_inputs(&mut self, pull: Pull) {
        let config = InputConfig::default().with_pull(pull);
        self.configure_input(DisplayOutputSignal::Cs, &config);
        self.configure_input(DisplayOutputSignal::Dc, &config);
        self.configure_input(DisplayOutputSignal::Rst, &config);
        self.busy.set_output_enable(false);
        self.busy.set_input_enable(true);
        self.busy.apply_input_config(&config);
        self.configure_input(DisplayOutputSignal::Sck, &config);
        self.configure_input(DisplayOutputSignal::Mosi, &config);
    }

    fn configure_input(&mut self, signal: DisplayOutputSignal, config: &InputConfig) {
        let pin = self.pin_mut(signal);
        pin.set_output_enable(false);
        pin.set_input_enable(true);
        pin.apply_input_config(config);
    }

    async fn run_busy_pull_test(&mut self) {
        self.busy.set_output_enable(false);
        self.busy.set_input_enable(true);

        self.busy
            .apply_input_config(&InputConfig::default().with_pull(Pull::Down));
        Timer::after_millis(1).await;
        let pulldown = self.busy.is_high();

        self.busy
            .apply_input_config(&InputConfig::default().with_pull(Pull::Up));
        Timer::after_millis(1).await;
        let pullup = self.busy.is_high();

        info!(
            "Display BUSY pull test: pulldown_high={} pullup_high={}",
            pulldown, pullup
        );

        if !pulldown && pullup {
            defmt::warn!(
                "Display BUSY follows internal pulls before init; compare against the good board for an open BUSY/FPC fault"
            );
        }
    }

    async fn run_reset_busy_test(&mut self) {
        self.configure_all_inputs(Pull::Down);
        self.busy.apply_input_config(&InputConfig::default());

        self.drive_output(DisplayOutputSignal::Rst, Level::Low);
        Timer::after_millis(5).await;
        let reset_low_busy_high = self.busy.is_high();

        self.drive_output(DisplayOutputSignal::Rst, Level::High);
        Timer::after_millis(5).await;
        let reset_high_5ms_busy_high = self.busy.is_high();
        Timer::after_millis(50).await;
        let reset_high_55ms_busy_high = self.busy.is_high();

        info!(
            "Display reset/BUSY test: reset_low_busy_high={} reset_high_5ms_busy_high={} reset_high_55ms_busy_high={}",
            reset_low_busy_high, reset_high_5ms_busy_high, reset_high_55ms_busy_high
        );
    }

    async fn run_bitbang_controller_test(&mut self) {
        self.configure_all_inputs(Pull::Down);
        self.prepare_bitbang_bus();
        self.bitbang_write_command(0x12);
        self.log_busy_after_bitbang_command("bitbang SW reset");

        let status_none = self.bitbang_read_status(Pull::None);
        let status_pulldown = self.bitbang_read_status(Pull::Down);
        let status_pullup = self.bitbang_read_status(Pull::Up);
        info!(
            "Display bitbang status read: none={} pulldown={} pullup={} chip_id_none={} chip_id_pulldown={} chip_id_pullup={}",
            status_none,
            status_pulldown,
            status_pullup,
            status_none & 0x03,
            status_pulldown & 0x03,
            status_pullup & 0x03
        );

        if (status_pulldown & 0x03) != 0x01 || status_pulldown != status_pullup {
            defmt::warn!(
                "Display bitbang status did not look like a driven SSD1677 status byte; SPI/SDA/control path may be open"
            );
        }
    }

    fn prepare_bitbang_bus(&mut self) {
        self.drive_output(DisplayOutputSignal::Rst, Level::High);
        self.drive_output(DisplayOutputSignal::Cs, Level::High);
        self.drive_output(DisplayOutputSignal::Dc, Level::High);
        self.drive_output(DisplayOutputSignal::Sck, Level::Low);
        self.drive_output(DisplayOutputSignal::Mosi, Level::Low);
        self.busy.set_output_enable(false);
        self.busy.set_input_enable(true);
        self.busy.apply_input_config(&InputConfig::default());
    }

    fn bitbang_write_command(&mut self, command: u8) {
        let mut delay = Delay;
        self.prepare_bitbang_bus();
        self.cs.set_low();
        self.dc.set_low();
        delay.delay_us(2);
        self.bitbang_write_byte(command, &mut delay);
        delay.delay_us(2);
        self.cs.set_high();
        self.dc.set_high();
        self.sck.set_low();
    }

    fn bitbang_read_status(&mut self, read_pull: Pull) -> u8 {
        let mut delay = Delay;
        self.prepare_bitbang_bus();
        self.cs.set_low();
        self.dc.set_low();
        delay.delay_us(2);
        self.bitbang_write_byte(0x2F, &mut delay);

        self.mosi.set_output_enable(false);
        self.mosi.set_input_enable(true);
        self.mosi
            .apply_input_config(&InputConfig::default().with_pull(read_pull));
        self.dc.set_high();
        delay.delay_us(2);
        let value = self.bitbang_read_byte(&mut delay);

        self.cs.set_high();
        self.dc.set_high();
        self.sck.set_low();
        self.mosi.set_output_enable(false);
        value
    }

    fn bitbang_write_byte(&mut self, byte: u8, delay: &mut Delay) {
        for bit_index in (0..8).rev() {
            if (byte & (1u8 << bit_index)) == 0 {
                self.mosi.set_low();
            } else {
                self.mosi.set_high();
            }
            delay.delay_us(2);
            self.sck.set_high();
            delay.delay_us(2);
            self.sck.set_low();
            delay.delay_us(2);
        }
    }

    fn bitbang_read_byte(&mut self, delay: &mut Delay) -> u8 {
        let mut value = 0u8;
        for bit_index in (0..8).rev() {
            self.sck.set_high();
            delay.delay_us(2);
            self.sck.set_low();
            delay.delay_us(2);
            if self.mosi.is_high() {
                value |= 1u8 << bit_index;
            }
            delay.delay_us(2);
        }
        value
    }

    fn log_busy_after_bitbang_command(&mut self, label: &str) {
        let mut delay = Delay;
        let mut reads = 0u16;
        let mut high_reads = 0u16;
        let mut rising_edges = 0u16;
        let mut falling_edges = 0u16;
        let mut has_last = false;
        let mut last_high = false;

        for _ in 0..120 {
            let high = self.busy.is_high();
            reads = reads.saturating_add(1);
            if high {
                high_reads = high_reads.saturating_add(1);
            }
            if has_last && high != last_high {
                if high {
                    rising_edges = rising_edges.saturating_add(1);
                } else {
                    falling_edges = falling_edges.saturating_add(1);
                }
            }
            has_last = true;
            last_high = high;

            if !high && high_reads > 0 {
                break;
            }
            delay.delay_ms(1);
        }

        info!(
            "Display BUSY after {}: reads={} high_reads={} rising_edges={} falling_edges={}",
            label, reads, high_reads, rising_edges, falling_edges
        );
    }

    async fn run_short_scan(&mut self) {
        self.check_drive_high(DisplayOutputSignal::Cs).await;
        self.check_drive_high(DisplayOutputSignal::Dc).await;
        self.check_drive_high(DisplayOutputSignal::Rst).await;
        self.check_drive_high(DisplayOutputSignal::Sck).await;
        self.check_drive_high(DisplayOutputSignal::Mosi).await;
    }

    async fn check_drive_high(&mut self, signal: DisplayOutputSignal) {
        self.configure_all_inputs(Pull::Down);
        self.drive_output(signal, Level::High);

        Timer::after_millis(1).await;
        let driven_high = self.pin_level(signal);
        let other_high_mask = self.other_high_mask(signal);
        let busy_high = self.busy.is_high();
        info!(
            "Display GPIO drive-high {}: driven_high={} other_high_mask={} busy_high={}",
            signal.name(),
            driven_high,
            other_high_mask,
            busy_high
        );

        if !driven_high {
            defmt::warn!(
                "Display GPIO {} did not read high while driven high; possible short to GND or heavy load",
                signal.name()
            );
        }

        if other_high_mask != 0 {
            defmt::warn!(
                "Display GPIO {} high also raised other display pins; possible solder bridge mask={}",
                signal.name(), other_high_mask
            );
        }
    }

    fn prepare_control_pins(dc: &mut Flex<'static>, rst: &mut Flex<'static>) {
        let output_config = OutputConfig::default();
        dc.apply_output_config(&output_config);
        dc.set_high();
        dc.set_output_enable(true);
        dc.set_input_enable(false);

        rst.apply_output_config(&output_config);
        rst.set_high();
        rst.set_output_enable(true);
        rst.set_input_enable(false);
    }

    fn drive_output(&mut self, signal: DisplayOutputSignal, level: Level) {
        let output_config = OutputConfig::default();
        let pin = self.pin_mut(signal);
        pin.apply_output_config(&output_config);
        pin.set_level(level);
        pin.set_output_enable(true);
        pin.set_input_enable(true);
    }

    fn pin_mut(&mut self, signal: DisplayOutputSignal) -> &mut Flex<'static> {
        match signal {
            DisplayOutputSignal::Cs => &mut self.cs,
            DisplayOutputSignal::Dc => &mut self.dc,
            DisplayOutputSignal::Rst => &mut self.rst,
            DisplayOutputSignal::Sck => &mut self.sck,
            DisplayOutputSignal::Mosi => &mut self.mosi,
        }
    }

    fn pin_level(&self, signal: DisplayOutputSignal) -> bool {
        match signal {
            DisplayOutputSignal::Cs => self.cs.is_high(),
            DisplayOutputSignal::Dc => self.dc.is_high(),
            DisplayOutputSignal::Rst => self.rst.is_high(),
            DisplayOutputSignal::Sck => self.sck.is_high(),
            DisplayOutputSignal::Mosi => self.mosi.is_high(),
        }
    }

    fn other_high_mask(&self, driven: DisplayOutputSignal) -> u8 {
        let mut mask = 0u8;
        if !matches!(driven, DisplayOutputSignal::Cs) && self.cs.is_high() {
            mask |= 1;
        }
        if !matches!(driven, DisplayOutputSignal::Dc) && self.dc.is_high() {
            mask |= 2;
        }
        if !matches!(driven, DisplayOutputSignal::Rst) && self.rst.is_high() {
            mask |= 4;
        }
        if !matches!(driven, DisplayOutputSignal::Sck) && self.sck.is_high() {
            mask |= 8;
        }
        if !matches!(driven, DisplayOutputSignal::Mosi) && self.mosi.is_high() {
            mask |= 16;
        }
        mask
    }

    fn log_levels(&self, label: &str) {
        info!(
            "{}: CS={} DC={} RST={} BUSY={} SCK={} MOSI={}",
            label,
            self.cs.is_high(),
            self.dc.is_high(),
            self.rst.is_high(),
            self.busy.is_high(),
            self.sck.is_high(),
            self.mosi.is_high()
        );
    }
}

impl DisplayOutputSignal {
    fn name(self) -> &'static str {
        match self {
            DisplayOutputSignal::Cs => "CS",
            DisplayOutputSignal::Dc => "DC",
            DisplayOutputSignal::Rst => "RST",
            DisplayOutputSignal::Sck => "SCK",
            DisplayOutputSignal::Mosi => "MOSI",
        }
    }
}

struct SpiDriver {
    inner: Spi<'static, Blocking>,
    delay: Delay,
}

impl ErrorType for SpiDriver {
    type Error = spi::Error;
}

impl SpiDevice for SpiDriver {
    fn transaction(
        &mut self,
        operations: &mut [embedded_hal::spi::Operation<'_, u8>],
    ) -> Result<(), Self::Error> {
        for op in operations {
            match op {
                Operation::DelayNs(delay) => self.delay.delay_ns(*delay),
                Operation::Read(words) => self.inner.read(words)?,
                Operation::Transfer(_in_bytes, _out_bytes) => {
                    todo!()
                }
                Operation::TransferInPlace(words) => self.inner.transfer_in_place(words)?,
                Operation::Write(words) => self.inner.write(words)?,
            }
        }

        Ok(())
    }
}

const LONG_PRESS_TICKS: u16 = 32;
const MAX_FAST_DISPLAY_UPDATES: u8 = 12;
const PARTIAL_UPDATE_X_PADDING: u16 = 16;
const PARTIAL_UPDATE_Y_PADDING: u16 = 12;

struct Rotated180<'a, D> {
    target: &'a mut D,
}

impl<'a, D> Rotated180<'a, D> {
    fn new(target: &'a mut D) -> Self {
        Self { target }
    }
}

impl<D> DrawTarget for Rotated180<'_, D>
where
    D: DrawTarget + OriginDimensions,
{
    type Color = D::Color;
    type Error = D::Error;

    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = Pixel<Self::Color>>,
    {
        let size = self.target.size();
        let width = size.width as i32;
        let height = size.height as i32;

        self.target
            .draw_iter(pixels.into_iter().filter_map(|Pixel(point, color)| {
                if point.x < 0 || point.y < 0 || point.x >= width || point.y >= height {
                    return None;
                }

                Some(Pixel(
                    Point::new(width - 1 - point.x, height - 1 - point.y),
                    color,
                ))
            }))
    }

    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
        let size = self.target.size();
        let bounds = Rectangle::new(Point::new(0, 0), size);
        let drawable_area = area.intersection(&bounds);

        if drawable_area.size.width == 0 || drawable_area.size.height == 0 {
            return Ok(());
        }

        let rotated_area = Rectangle::new(
            Point::new(
                size.width as i32 - drawable_area.top_left.x - drawable_area.size.width as i32,
                size.height as i32 - drawable_area.top_left.y - drawable_area.size.height as i32,
            ),
            drawable_area.size,
        );
        self.target.fill_solid(&rotated_area, color)
    }

    fn clear(&mut self, color: Self::Color) -> Result<(), Self::Error> {
        self.target.clear(color)
    }
}

impl<D> OriginDimensions for Rotated180<'_, D>
where
    D: OriginDimensions,
{
    fn size(&self) -> Size {
        self.target.size()
    }
}

struct Buttons {
    bottom: Input<'static>,
    left: Input<'static>,
    right_up: Input<'static>,
    right_down: Input<'static>,
    bottom_state: ButtonState,
    left_state: ButtonState,
    right_up_state: ButtonState,
    right_down_state: ButtonState,
}

impl Buttons {
    fn new(
        bottom: Input<'static>,
        left: Input<'static>,
        right_up: Input<'static>,
        right_down: Input<'static>,
    ) -> Self {
        Self {
            bottom,
            left,
            right_up,
            right_down,
            bottom_state: ButtonState::new(),
            left_state: ButtonState::new(),
            right_up_state: ButtonState::new(),
            right_down_state: ButtonState::new(),
        }
    }

    fn poll(&mut self) -> Option<InputEvent> {
        if let Some(event) =
            self.right_up_state
                .update(self.right_up.is_low(), InputEvent::Up, None)
        {
            return Some(event);
        }

        if let Some(event) =
            self.right_down_state
                .update(self.right_down.is_low(), InputEvent::Down, None)
        {
            return Some(event);
        }

        if let Some(event) = self.bottom_state.update(
            self.bottom.is_low(),
            InputEvent::Select,
            Some(InputEvent::SelectLong),
        ) {
            return Some(event);
        }

        if let Some(event) = self.left_state.update(
            self.left.is_low(),
            InputEvent::Back,
            Some(InputEvent::BackLong),
        ) {
            return Some(event);
        }

        None
    }
}

struct ButtonState {
    was_pressed: bool,
    held_ticks: u16,
    long_sent: bool,
}

impl ButtonState {
    const fn new() -> Self {
        Self {
            was_pressed: false,
            held_ticks: 0,
            long_sent: false,
        }
    }

    fn update(
        &mut self,
        pressed: bool,
        short_event: InputEvent,
        long_event: Option<InputEvent>,
    ) -> Option<InputEvent> {
        if pressed {
            if self.was_pressed {
                self.held_ticks = self.held_ticks.saturating_add(1);
                if !self.long_sent && self.held_ticks >= LONG_PRESS_TICKS {
                    self.long_sent = true;
                    return long_event;
                }
            } else {
                self.was_pressed = true;
                self.held_ticks = 0;
                self.long_sent = false;
            }

            return None;
        }

        if self.was_pressed {
            self.was_pressed = false;
            self.held_ticks = 0;

            if !self.long_sent {
                return Some(short_event);
            }
        }

        self.long_sent = false;
        None
    }
}

struct WifiCredentialStore {
    flash: FlashStorage<'static>,
}

impl WifiCredentialStore {
    const PARTITION_LABEL: &'static str = "wifi";

    fn new(flash: esp_hal::peripherals::FLASH<'static>) -> Self {
        Self {
            flash: FlashStorage::new(flash),
        }
    }

    fn load(&mut self) -> Option<WifiCredentials> {
        let mut table_storage = [0u8; PARTITION_TABLE_MAX_LEN];
        let table = read_partition_table(&mut self.flash, &mut table_storage).ok()?;
        let partition = table
            .iter()
            .find(|partition| partition.label_as_str() == Self::PARTITION_LABEL)?;
        let mut region = partition.as_embedded_storage(&mut self.flash);

        let mut record = [0u8; WIFI_CREDENTIAL_RECORD_BYTES];
        region.read(0, &mut record).ok()?;
        WifiCredentials::decode(&record)
    }

    fn save(&mut self, credentials: &WifiCredentials) -> Result<(), ()> {
        let mut table_storage = [0u8; PARTITION_TABLE_MAX_LEN];
        let table = read_partition_table(&mut self.flash, &mut table_storage).map_err(|_| ())?;
        let partition = table
            .iter()
            .find(|partition| partition.label_as_str() == Self::PARTITION_LABEL)
            .ok_or(())?;
        let mut region = partition.as_embedded_storage(&mut self.flash);

        let record = credentials.encode();
        region.write(0, &record).map_err(|_| ())
    }
}

struct WifiService {
    controller: WifiController<'static>,
}

impl WifiService {
    fn new(device: esp_hal::peripherals::WIFI<'static>) -> Result<Self, RadioWifiError> {
        let controller = WifiController::new(device, ControllerConfig::default())?;
        Ok(Self { controller })
    }

    async fn handle(&mut self, command: AppCommand) -> WifiEvent {
        match command {
            AppCommand::ScanWifi => self.scan().await,
            AppCommand::JoinWifi {
                access_point,
                password,
            } => self.join_access_point(access_point, password).await,
            AppCommand::JoinSavedWifi { credentials } => self.join_saved(credentials).await,
        }
    }

    fn unavailable(command: AppCommand) -> WifiEvent {
        match command {
            AppCommand::ScanWifi => WifiEvent::ScanFinished(Err(WifiFailure::Unsupported)),
            AppCommand::JoinWifi { .. } | AppCommand::JoinSavedWifi { .. } => {
                WifiEvent::JoinFinished(Err(WifiFailure::Unsupported))
            }
        }
    }

    async fn scan(&mut self) -> WifiEvent {
        let scan_config = ScanConfig::default().with_max(MAX_NETWORKS);
        match self.controller.scan_async(&scan_config).await {
            Ok(access_points) => {
                let mut networks = WifiNetworkList::new();
                for access_point in access_points {
                    if access_point.ssid.is_empty() {
                        continue;
                    }

                    let mut ssid = WifiSsid::new();
                    if ssid.push_str(access_point.ssid.as_str()).is_err() {
                        continue;
                    }

                    let network = WifiAccessPoint {
                        ssid,
                        bssid: access_point.bssid,
                        channel: access_point.channel,
                        signal_strength: access_point.signal_strength,
                        security: Self::from_auth_method(access_point.auth_method),
                    };

                    if networks.push(network).is_err() {
                        break;
                    }
                }

                WifiEvent::ScanFinished(Ok(networks))
            }
            Err(error) => {
                defmt::error!("WiFi scan failed: {:?}", error);
                WifiEvent::ScanFinished(Err(Self::scan_failure_from_error(error)))
            }
        }
    }

    async fn join_access_point(
        &mut self,
        access_point: WifiAccessPoint,
        password: WifiPassword,
    ) -> WifiEvent {
        let ssid = access_point.ssid.clone();
        let station_config = StationConfig::default()
            .with_ssid(access_point.ssid.as_str())
            .with_bssid(access_point.bssid)
            .with_channel(access_point.channel)
            .with_auth_method(Self::to_auth_method(access_point.security))
            .with_password(AllocString::from(password.as_str()));

        self.connect(ssid, station_config).await
    }

    async fn join_saved(&mut self, credentials: WifiCredentials) -> WifiEvent {
        let ssid = credentials.ssid.clone();
        let station_config = StationConfig::default()
            .with_ssid(credentials.ssid.as_str())
            .with_auth_method(Self::to_auth_method(credentials.security))
            .with_password(AllocString::from(credentials.password.as_str()));

        self.connect(ssid, station_config).await
    }

    async fn connect(&mut self, ssid: WifiSsid, station_config: StationConfig) -> WifiEvent {
        let _ = self.controller.disconnect_async().await;

        let station_config = WifiConfig::Station(station_config);
        if let Err(error) = self.controller.set_config(&station_config) {
            defmt::error!("WiFi config failed: {:?}", error);
            return WifiEvent::JoinFinished(Err(Self::join_failure_from_error(error)));
        }

        match self.controller.connect_async().await {
            Ok(_) => WifiEvent::JoinFinished(Ok(ssid)),
            Err(error) => {
                defmt::error!("WiFi join failed: {:?}", error);
                WifiEvent::JoinFinished(Err(Self::join_failure_from_error(error)))
            }
        }
    }

    fn from_auth_method(auth_method: Option<AuthenticationMethod>) -> WifiSecurity {
        match auth_method {
            None | Some(AuthenticationMethod::None) => WifiSecurity::Open,
            Some(AuthenticationMethod::Wep) => WifiSecurity::Wep,
            Some(AuthenticationMethod::Wpa) => WifiSecurity::Wpa,
            Some(AuthenticationMethod::Wpa2Personal) => WifiSecurity::Wpa2Personal,
            Some(AuthenticationMethod::WpaWpa2Personal) => WifiSecurity::WpaWpa2Personal,
            Some(AuthenticationMethod::Wpa2Wpa3Personal) => WifiSecurity::Wpa2Wpa3Personal,
            Some(AuthenticationMethod::Wpa3Personal) => WifiSecurity::Wpa3Personal,
            _ => WifiSecurity::Other,
        }
    }

    fn to_auth_method(security: WifiSecurity) -> AuthenticationMethod {
        match security {
            WifiSecurity::Open => AuthenticationMethod::None,
            WifiSecurity::Wep => AuthenticationMethod::Wep,
            WifiSecurity::Wpa => AuthenticationMethod::Wpa,
            WifiSecurity::Wpa2Personal => AuthenticationMethod::Wpa2Personal,
            WifiSecurity::WpaWpa2Personal => AuthenticationMethod::WpaWpa2Personal,
            WifiSecurity::Wpa2Wpa3Personal => AuthenticationMethod::Wpa2Wpa3Personal,
            WifiSecurity::Wpa3Personal => AuthenticationMethod::Wpa3Personal,
            WifiSecurity::Other => AuthenticationMethod::Wpa2Personal,
        }
    }

    fn scan_failure_from_error(error: RadioWifiError) -> WifiFailure {
        match error {
            RadioWifiError::OutOfMemory => WifiFailure::OutOfMemory,
            RadioWifiError::Unsupported => WifiFailure::Unsupported,
            _ => WifiFailure::ScanFailed,
        }
    }

    fn join_failure_from_error(error: RadioWifiError) -> WifiFailure {
        match error {
            RadioWifiError::InvalidSsid
            | RadioWifiError::InvalidPassword
            | RadioWifiError::InvalidArguments => WifiFailure::InvalidCredentials,
            RadioWifiError::OutOfMemory => WifiFailure::OutOfMemory,
            RadioWifiError::Unsupported => WifiFailure::Unsupported,
            RadioWifiError::NotConnected => WifiFailure::NotConnected,
            _ => WifiFailure::JoinFailed,
        }
    }
}

fn present_display_update<SPI, DC, RST, BUSY, PinError>(
    display: &mut Ssd1677<SPI, DC, RST, BUSY>,
    screen: &Framebuffer<'_>,
    previous_buf: &mut [u8],
    refresh: RefreshHint,
    fast_update_count: &mut u8,
) where
    SPI: SpiDevice,
    SPI::Error: core::fmt::Debug,
    DC: embedded_hal::digital::OutputPin<Error = PinError>,
    RST: embedded_hal::digital::OutputPin<Error = PinError>,
    BUSY: embedded_hal::digital::InputPin<Error = PinError>,
    PinError: core::fmt::Debug,
{
    match refresh {
        RefreshHint::Full => {
            display
                .update_frame_quality(screen.as_bytes(), &mut Delay)
                .expect("did not update screen");
            screen
                .copy_to(previous_buf)
                .expect("did not sync framebuffer");
            *fast_update_count = 0;
        }
        RefreshHint::Fast => {
            if *fast_update_count >= MAX_FAST_DISPLAY_UPDATES {
                display
                    .update_frame_quality(screen.as_bytes(), &mut Delay)
                    .expect("did not update screen");
                screen
                    .copy_to(previous_buf)
                    .expect("did not sync framebuffer");
                *fast_update_count = 0;
            } else if update_changed_partial_padded(display, screen.as_bytes(), previous_buf) {
                *fast_update_count = (*fast_update_count).saturating_add(1);
            }
        }
        RefreshHint::None => {}
    }
}

fn update_changed_partial_padded<SPI, DC, RST, BUSY, PinError>(
    display: &mut Ssd1677<SPI, DC, RST, BUSY>,
    frame: &[u8],
    previous_frame: &mut [u8],
) -> bool
where
    SPI: SpiDevice,
    SPI::Error: core::fmt::Debug,
    DC: embedded_hal::digital::OutputPin<Error = PinError>,
    RST: embedded_hal::digital::OutputPin<Error = PinError>,
    BUSY: embedded_hal::digital::InputPin<Error = PinError>,
    PinError: core::fmt::Debug,
{
    let Some(region) = display
        .changed_region(frame, previous_frame)
        .expect("did not calculate changed display region")
    else {
        return false;
    };

    let region = pad_partial_update_region(region);
    display
        .write_region_from_frame(region, frame)
        .expect("did not write partial display region");
    display
        .refresh_partial(region, &mut Delay)
        .expect("did not refresh partial display region");
    display
        .write_region_from_frame(region, frame)
        .expect("did not resync current display region");
    display
        .write_previous_region_from_frame(region, frame)
        .expect("did not resync previous display region");
    sync_previous_frame_region(previous_frame, frame, region);

    true
}

fn pad_partial_update_region(region: Region) -> Region {
    let x_start = region.x.saturating_sub(PARTIAL_UPDATE_X_PADDING);
    let x_start = x_start - (x_start % 8);
    let x_end =
        (u32::from(region.x) + u32::from(region.width) + u32::from(PARTIAL_UPDATE_X_PADDING))
            .min(u32::from(WIDTH)) as u16;
    let x_end = align_up_to_byte_boundary(x_end).min(WIDTH);

    let y_start = region.y.saturating_sub(PARTIAL_UPDATE_Y_PADDING);
    let y_end =
        (u32::from(region.y) + u32::from(region.height) + u32::from(PARTIAL_UPDATE_Y_PADDING))
            .min(u32::from(HEIGHT)) as u16;

    Region::new(x_start, y_start, x_end - x_start, y_end - y_start)
}

fn align_up_to_byte_boundary(value: u16) -> u16 {
    let remainder = value % 8;
    if remainder == 0 {
        value
    } else {
        value + (8 - remainder)
    }
}

fn sync_previous_frame_region(previous_frame: &mut [u8], frame: &[u8], region: Region) {
    let x_byte_start = region.x as usize / 8;
    let row_bytes = region.width as usize / 8;
    let y_start = region.y as usize;
    let y_end = y_start + region.height as usize;

    for y in y_start..y_end {
        let start = y * BYTES_PER_ROW + x_byte_start;
        previous_frame[start..start + row_bytes].copy_from_slice(&frame[start..start + row_bytes]);
    }
}

#[esp_rtos::main]
async fn main(spawner: Spawner) {
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);

    esp_alloc::heap_allocator!(#[ram(reclaimed)] size: 64 * 1024);
    esp_alloc::heap_allocator!(size: 48 * 1024);

    let software_interrupt = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
    let timg0 = TimerGroup::new(peripherals.TIMG0);
    esp_rtos::start(timg0.timer0, software_interrupt.software_interrupt0);

    info!("Embassy initialized!");

    let mut wifi = match WifiService::new(peripherals.WIFI) {
        Ok(service) => Some(service),
        Err(error) => {
            defmt::error!("WiFi init failed: {:?}", error);
            None
        }
    };

    let mut credential_store = WifiCredentialStore::new(peripherals.FLASH);
    let saved_credentials = credential_store.load();
    if saved_credentials.is_some() {
        info!("Loaded saved WiFi credentials");
    }

    let _ = spawner;

    let button_bottom = Input::new(
        peripherals.GPIO14,
        InputConfig::default().with_pull(Pull::Up),
    );
    let button_left = Input::new(
        peripherals.GPIO19,
        InputConfig::default().with_pull(Pull::Up),
    );
    let button_right_up = Input::new(
        peripherals.GPIO20,
        InputConfig::default().with_pull(Pull::Up),
    );
    let button_right_down = Input::new(
        peripherals.GPIO21,
        InputConfig::default().with_pull(Pull::Up),
    );

    let mut display_pins = DisplayPins {
        cs: Flex::new(peripherals.GPIO2),
        dc: Flex::new(peripherals.GPIO3),
        rst: Flex::new(peripherals.GPIO4),
        busy: Flex::new(peripherals.GPIO5),
        sck: Flex::new(peripherals.GPIO6),
        mosi: Flex::new(peripherals.GPIO7),
    };
    display_pins.run_diagnostics().await;

    let DisplayPins {
        cs,
        mut dc,
        mut rst,
        busy,
        sck,
        mosi,
    } = display_pins;
    DisplayPins::prepare_control_pins(&mut dc, &mut rst);
    let busy = DiagnosticBusyPin::new(busy);

    let spi_config = spi::master::Config::default().with_mode(spi::Mode::_0);
    let spi = Spi::new(peripherals.SPI2, spi_config)
        .expect("did not get SPI")
        .with_cs(cs)
        .with_sck(sck)
        .with_mosi(mosi);

    let spi_driver = SpiDriver {
        inner: spi,
        delay: Delay,
    };

    info!("Init display");
    let mut display = Ssd1677::new(spi_driver, dc, rst, busy);
    Timer::after_millis(10).await;
    DISPLAY_BUSY_STATS.reset();
    let display_init_started = Instant::now();
    display.init(&mut Delay).expect("failed to init display");
    DISPLAY_BUSY_STATS.log("display init", display_init_started.elapsed().as_millis());

    let buf = DISPLAY_BUFFER.init([0; FRAME_BYTES]);
    let previous_buf = PREVIOUS_DISPLAY_BUFFER.init([0; FRAME_BYTES]);
    let mut screen = Framebuffer::new(buf).expect("invalid framebuffer");

    let mut backlight_enable = Output::new(peripherals.GPIO18, Level::High, Default::default());
    backlight_enable.set_high();
    Timer::after_millis(10).await;

    let mut i2c = I2c::new(
        peripherals.I2C0,
        I2cConfig::default().with_frequency(Rate::from_khz(10)),
    )
    .expect("did not get I2C")
    .with_sda(peripherals.GPIO22)
    .with_scl(peripherals.GPIO23);

    info!("Scanning I2C bus");
    let mut devices_found = 0u8;
    for address in 0x08..=0x77 {
        if i2c.write(address, &[]).is_ok() {
            devices_found += 1;
            info!("I2C device ACK at {}", address);
        }
    }
    info!("I2C scan complete, found {} devices", devices_found);

    let mut backlight = Lm3630a::new(i2c, Address::SelLow);
    backlight.wait_until_ready(&mut Delay);

    let current = FullScaleCurrent::from_code(6).expect("invalid backlight current code");
    let backlight_init: Result<(), esp_hal::i2c::master::Error> = (|| {
        let revision = backlight.revision().map_err(|error| {
            defmt::error!("LM3630A did not respond at 0x36: {:?}", error);
            error
        })?;
        info!("LM3630A revision {}", revision);

        backlight
            .set_configuration(Configuration::default())
            .map_err(|error| {
                defmt::error!("LM3630A configuration write failed: {:?}", error);
                error
            })?;
        backlight
            .set_full_scale_current(Bank::A, current)
            .map_err(|error| {
                defmt::error!("LM3630A bank A current write failed: {:?}", error);
                error
            })?;
        backlight
            .set_full_scale_current(Bank::B, current)
            .map_err(|error| {
                defmt::error!("LM3630A bank B current write failed: {:?}", error);
                error
            })?;
        backlight
            .set_brightness(Bank::A, Brightness::from_raw(128))
            .map_err(|error| {
                defmt::error!("LM3630A bank A brightness write failed: {:?}", error);
                error
            })?;
        backlight
            .set_brightness(Bank::B, Brightness::from_raw(128))
            .map_err(|error| {
                defmt::error!("LM3630A bank B brightness write failed: {:?}", error);
                error
            })?;
        backlight
            .set_control(ControlConfig {
                sleep: false,
                bank_a_mapping: Mapping::Linear,
                bank_b_mapping: Mapping::Linear,
                bank_a_enabled: true,
                bank_b_enabled: true,
                led2_bank: Led2Bank::B,
            })
            .map_err(|error| {
                defmt::error!("LM3630A control write failed: {:?}", error);
                error
            })?;

        Ok(())
    })();

    if backlight_init.is_ok() {
        info!("LM3630A backlight enabled");
        if let Err(error) = backlight.set_brightness(Bank::A, Brightness::from_raw(100)) {
            defmt::error!("LM3630A brightness update failed: {:?}", error);
        }
    } else {
        defmt::error!("Skipping LM3630A brightness update because init failed");
    }

    let mut buttons = Buttons::new(
        button_bottom,
        button_left,
        button_right_up,
        button_right_down,
    );
    let mut app = App::new_with_saved_wifi(saved_credentials);

    {
        let mut threshold_screen = ThresholdFramebuffer::new(&mut screen);
        let mut display_target = Rotated180::new(&mut threshold_screen);
        app.render(&mut display_target).expect("did not render app");
    }
    DISPLAY_BUSY_STATS.reset();
    let initial_update_started = Instant::now();
    display
        .update_frame_quality(screen.as_bytes(), &mut Delay)
        .expect("did not update screen");
    DISPLAY_BUSY_STATS.log(
        "initial full update",
        initial_update_started.elapsed().as_millis(),
    );
    screen
        .copy_to(previous_buf)
        .expect("did not sync framebuffer");
    let mut fast_update_count = 0u8;
    let mut pending_command = app.take_pending_command();

    loop {
        if pending_command.is_none() {
            if let Some(event) = buttons.poll() {
                let update = app.update(event);
                if update.dirty {
                    {
                        let mut threshold_screen = ThresholdFramebuffer::new(&mut screen);
                        let mut display_target = Rotated180::new(&mut threshold_screen);
                        app.render(&mut display_target).expect("did not render app");
                    }
                    present_display_update(
                        &mut display,
                        &screen,
                        previous_buf,
                        update.refresh,
                        &mut fast_update_count,
                    );
                }
                pending_command = update.command;
            }
        }

        if let Some(command) = pending_command.take() {
            let command_for_storage = command.clone();
            let wifi_event = match wifi.as_mut() {
                Some(wifi) => wifi.handle(command).await,
                None => WifiService::unavailable(command),
            };

            if let AppCommand::JoinWifi {
                access_point,
                password,
            } = &command_for_storage
            {
                if matches!(&wifi_event, WifiEvent::JoinFinished(Ok(_))) {
                    let credentials =
                        WifiCredentials::from_access_point(access_point, password.clone());
                    if credential_store.save(&credentials).is_ok() {
                        info!("Saved WiFi credentials");
                    } else {
                        defmt::error!("Failed to save WiFi credentials");
                    }
                }
            }

            let update = app.handle_wifi_event(wifi_event);
            if update.dirty {
                {
                    let mut threshold_screen = ThresholdFramebuffer::new(&mut screen);
                    let mut display_target = Rotated180::new(&mut threshold_screen);
                    app.render(&mut display_target).expect("did not render app");
                }
                present_display_update(
                    &mut display,
                    &screen,
                    previous_buf,
                    update.refresh,
                    &mut fast_update_count,
                );
            }
            pending_command = update.command;
        }

        Timer::after_millis(25).await;
    }
}
