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

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::i2c::I2c as EmbeddedI2c;
use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice};
use esp_backtrace as _;
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 kinda::{App, AppCommand, InputEvent, RefreshHint};
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;
const LOGICAL_WIDTH: u16 = HEIGHT;
const LOGICAL_HEIGHT: u16 = WIDTH;
const TOUCH_RAW_LEFT: u16 = 453;
const TOUCH_RAW_RIGHT: u16 = 32;
const TOUCH_RAW_TOP: u16 = 733;
const TOUCH_RAW_BOTTOM: u16 = 69;

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

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

impl<D> DrawTarget for RotatedMinus90<'_, 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 logical_size = self.size();
        let logical_width = logical_size.width as i32;
        let logical_height = logical_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 >= logical_width
                    || point.y >= logical_height
                {
                    return None;
                }

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

    fn fill_solid(&mut self, area: &Rectangle, color: Self::Color) -> Result<(), Self::Error> {
        let logical_size = self.size();
        let bounds = Rectangle::new(Point::new(0, 0), logical_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(
                logical_size.height as i32
                    - drawable_area.top_left.y
                    - drawable_area.size.height as i32,
                drawable_area.top_left.x,
            ),
            Size::new(drawable_area.size.height, drawable_area.size.width),
        );
        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 RotatedMinus90<'_, D>
where
    D: OriginDimensions,
{
    fn size(&self) -> Size {
        let native_size = self.target.size();
        Size::new(native_size.height, native_size.width)
    }
}

struct TouchController<I2C> {
    i2c: I2C,
    was_touching: bool,
    read_failed: bool,
}

impl<I2C> TouchController<I2C>
where
    I2C: EmbeddedI2c,
{
    const ADDRESS: u8 = 0x38;
    const CHIP_ID: u8 = 0x64;
    const REG_ID_G_CIPHER: u8 = 0xA3;
    const REG_TOUCH1_XH: u8 = 0x03;
    const REG_TD_STATUS: u8 = 0x02;
    const TOUCH_EVENT_MASK: u8 = 0xC0;
    const TOUCH_EVENT_DOWN: u8 = 0x00;
    const TOUCH_EVENT_CONTACT: u8 = 0x80;
    const LM3630A_ADDRESS: u8 = 0x36;
    const LM3630A_CONTROL: u8 = 0x00;
    const LM3630A_CONFIGURATION: u8 = 0x01;
    const LM3630A_BRIGHTNESS_A: u8 = 0x03;
    const LM3630A_BRIGHTNESS_B: u8 = 0x04;
    const LM3630A_CURRENT_A: u8 = 0x05;
    const LM3630A_CURRENT_B: u8 = 0x06;
    const LM3630A_CONFIGURATION_DEFAULT: u8 = 0x18;
    const LM3630A_CONTROL_ON: u8 = 0x1E;
    const LM3630A_CONTROL_SLEEP: u8 = 0x80;
    const LM3630A_CURRENT_CODE: u8 = 6;
    const LM3630A_BRIGHTNESS_ON: u8 = 100;

    fn new(i2c: I2C) -> Self {
        Self {
            i2c,
            was_touching: false,
            read_failed: false,
        }
    }

    fn init(&mut self) {
        let mut chip_id = [0u8; 1];
        match self
            .i2c
            .write_read(Self::ADDRESS, &[Self::REG_ID_G_CIPHER], &mut chip_id)
        {
            Ok(()) => {
                if chip_id[0] == Self::CHIP_ID {
                    info!("FT6336U touch detected at I2C 0x38");
                } else {
                    defmt::warn!(
                        "Touch controller at I2C 0x38 returned chip id {}; expected {}",
                        chip_id[0],
                        Self::CHIP_ID
                    );
                }
            }
            Err(_) => {
                self.read_failed = true;
                defmt::warn!("FT6336U touch not detected at I2C 0x38");
            }
        }
    }

    fn poll(&mut self) -> Option<InputEvent> {
        let mut points = [0u8; 1];
        if self
            .i2c
            .write_read(Self::ADDRESS, &[Self::REG_TD_STATUS], &mut points)
            .is_err()
        {
            if !self.read_failed {
                defmt::warn!("FT6336U touch read failed");
            }
            self.read_failed = true;
            self.was_touching = false;
            return None;
        }

        self.read_failed = false;
        let points = points[0] & 0x0F;
        if points == 0 {
            self.was_touching = false;
            return None;
        }

        let mut data = [0u8; 4];
        if self
            .i2c
            .write_read(Self::ADDRESS, &[Self::REG_TOUCH1_XH], &mut data)
            .is_err()
        {
            if !self.read_failed {
                defmt::warn!("FT6336U touch coordinate read failed");
            }
            self.read_failed = true;
            self.was_touching = false;
            return None;
        }

        let event = data[0] & Self::TOUCH_EVENT_MASK;
        if event != Self::TOUCH_EVENT_DOWN && event != Self::TOUCH_EVENT_CONTACT {
            self.was_touching = false;
            return None;
        }

        if self.was_touching {
            return None;
        }
        self.was_touching = true;

        let raw_x = (u16::from(data[0] & 0x0F) << 8) | u16::from(data[1]);
        let raw_y = (u16::from(data[2] & 0x0F) << 8) | u16::from(data[3]);
        let (x, y) = Self::logical_touch(raw_x, raw_y);
        info!(
            "FT6336U touch raw=({}, {}) logical=({}, {})",
            raw_x, raw_y, x, y
        );

        Some(InputEvent::Touch { x, y })
    }

    fn set_front_light(&mut self, on: bool) -> Result<(), I2C::Error> {
        if on {
            self.write_lm3630a(
                Self::LM3630A_CONFIGURATION,
                Self::LM3630A_CONFIGURATION_DEFAULT,
            )?;
            self.write_lm3630a(Self::LM3630A_CURRENT_A, Self::LM3630A_CURRENT_CODE)?;
            self.write_lm3630a(Self::LM3630A_CURRENT_B, Self::LM3630A_CURRENT_CODE)?;
            self.write_lm3630a(Self::LM3630A_BRIGHTNESS_A, Self::LM3630A_BRIGHTNESS_ON)?;
            self.write_lm3630a(Self::LM3630A_BRIGHTNESS_B, Self::LM3630A_BRIGHTNESS_ON)?;
            self.write_lm3630a(Self::LM3630A_CONTROL, Self::LM3630A_CONTROL_ON)
        } else {
            self.write_lm3630a(Self::LM3630A_BRIGHTNESS_A, 0)?;
            self.write_lm3630a(Self::LM3630A_BRIGHTNESS_B, 0)?;
            self.write_lm3630a(Self::LM3630A_CONTROL, Self::LM3630A_CONTROL_SLEEP)
        }
    }

    fn write_lm3630a(&mut self, register: u8, value: u8) -> Result<(), I2C::Error> {
        self.i2c.write(Self::LM3630A_ADDRESS, &[register, value])
    }

    fn logical_touch(raw_x: u16, raw_y: u16) -> (u16, u16) {
        let x = Self::map_inverted_axis(raw_x, TOUCH_RAW_LEFT, TOUCH_RAW_RIGHT, LOGICAL_WIDTH - 1);
        let y = Self::map_inverted_axis(raw_y, TOUCH_RAW_TOP, TOUCH_RAW_BOTTOM, LOGICAL_HEIGHT - 1);
        (x, y)
    }

    fn map_inverted_axis(raw: u16, raw_at_zero: u16, raw_at_max: u16, logical_max: u16) -> u16 {
        if raw >= raw_at_zero {
            return 0;
        }

        if raw <= raw_at_max {
            return logical_max;
        }

        let span = u32::from(raw_at_zero - raw_at_max);
        let offset = u32::from(raw_at_zero - raw);
        ((offset * u32::from(logical_max) + (span / 2)) / span) as u16
    }
}

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
    }
}

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 _ = spawner;

    let button_bottom = Input::new(
        peripherals.GPIO4,
        InputConfig::default().with_pull(Pull::Up),
    );
    let button_left = Input::new(
        peripherals.GPIO5,
        InputConfig::default().with_pull(Pull::Up),
    );
    let button_right_up = Input::new(
        peripherals.GPIO6,
        InputConfig::default().with_pull(Pull::Up),
    );
    let button_right_down = Input::new(
        peripherals.GPIO7,
        InputConfig::default().with_pull(Pull::Up),
    );

    let mut display_pins = DisplayPins {
        cs: Flex::new(peripherals.GPIO20),
        dc: Flex::new(peripherals.GPIO21),
        rst: Flex::new(peripherals.GPIO22),
        busy: Flex::new(peripherals.GPIO23),
        sck: Flex::new(peripherals.GPIO19),
        mosi: Flex::new(peripherals.GPIO18),
    };
    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.GPIO14, 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.GPIO3)
    .with_scl(peripherals.GPIO2);

    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 i2c = backlight.release();
    let mut touch = TouchController::new(i2c);
    touch.init();

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

    {
        let mut threshold_screen = ThresholdFramebuffer::new(&mut screen);
        let mut display_target = RotatedMinus90::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;

    loop {
        if let Some(event) = buttons.poll().or_else(|| touch.poll()) {
            let update = app.update(event);
            if let Some(command) = update.command {
                match command {
                    AppCommand::SetFrontLight { on } => {
                        if on {
                            backlight_enable.set_high();
                            Timer::after_millis(10).await;
                            if touch.set_front_light(true).is_ok() {
                                info!("Front light on");
                            } else {
                                defmt::error!("Failed to turn front light on");
                            }
                        } else {
                            if touch.set_front_light(false).is_ok() {
                                info!("Front light off");
                            } else {
                                defmt::error!("Failed to turn front light off");
                            }
                            backlight_enable.set_low();
                        }
                    }
                }
            }

            if update.dirty {
                {
                    let mut threshold_screen = ThresholdFramebuffer::new(&mut screen);
                    let mut display_target = RotatedMinus90::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,
                );
            }
        }

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