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

use defmt::info;
use embassy_executor::Spawner;
use embassy_time::{Delay, Timer};
use embedded_graphics::mono_font::ascii::FONT_10X20;
use embedded_graphics::mono_font::MonoTextStyleBuilder;
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::Point;
use embedded_graphics::text::Text;
use embedded_hal::delay::DelayNs;
use embedded_hal::spi::{ErrorType, Operation, SpiBus, SpiDevice};
use esp_backtrace as _;
use esp_hal::gpio::{Input, InputConfig, Level, Output, 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, spi::master::Spi, time::Rate};
use esp_hal::{spi, Blocking};
use esp_println as _;
use lm3630a::{
    Address, Bank, Brightness, Configuration, ControlConfig, FullScaleCurrent, Led2Bank, Lm3630a,
    Mapping,
};
// use panic_rtt_target as _;
use ssd1677::display::{Dimensions, DisplayUpdateMode, Rotation};
use ssd1677::graphics::GraphicsDisplayBlackAndWhite;

extern crate alloc;

esp_bootloader_esp_idf::esp_app_desc!();

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

#[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!(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.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 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(peripherals.GPIO2)
        .with_sck(peripherals.GPIO6)
        .with_mosi(peripherals.GPIO7);

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

    info!("Init controller");
    let config = ssd1677::config::Builder::new()
        .auto_update(false)
        .dimensions(Dimensions {
            rows: 480,
            cols: 800,
        })
        .rotation(Rotation::Rotate270)
        .build();

    let interface = ssd1677::interface::Interface4Pin::new(
        spi_driver,
        Output::new(peripherals.GPIO3, Level::High, Default::default()),
        Output::new(peripherals.GPIO4, Level::High, Default::default()),
        Input::new(peripherals.GPIO5, Default::default()),
    );

    let mut display = ssd1677::display::Display::new(interface, config.unwrap());
    Timer::after_millis(10).await;
    info!("Reset display");
    display.reset(&mut Delay).expect("failed to reset");
    info!("Init display");
    Timer::after_millis(10).await;
    display.init().expect("failed to init");

    let mut buf: [u8; 800 * 480 / 8] = [0; (800 * 480) / 8];
    let mut screen = GraphicsDisplayBlackAndWhite::new(display, &mut buf);

    let mut is_on = true;

    screen
        .clear(BinaryColor::Off)
        .expect("did not clear screen");
    screen.update(DisplayUpdateMode::Slow).unwrap();

    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");
    }

    loop {
        info!("Turning {}", if is_on { "on" } else { "off" });

        use embedded_graphics::Drawable;

        let text = if is_on {
            "does it work?"
        } else {
            "hell yea it works "
        };

        screen
            .clear(BinaryColor::Off)
            .expect("did not clear screen");

        let style = MonoTextStyleBuilder::new()
            .font(&FONT_10X20)
            .text_color(BinaryColor::On)
            .background_color(BinaryColor::Off)
            .build();

        Text::new(text, Point::new(16, 28), style)
            .draw(&mut screen)
            .expect("did not draw text");

        screen
            .update(ssd1677::display::DisplayUpdateMode::Fast)
            .expect("did not update screen");

        Timer::after_secs(3).await;

        is_on = !is_on;
    }
}
