#![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, Level, Output};
use esp_hal::timer::systimer::SystemTimer;
use esp_hal::{clock::CpuClock, spi::master::Spi};
use esp_hal::{spi, Blocking};
use esp_println as _;
// use panic_rtt_target as _;
use ssd1677::display::{Dimensions, DisplayUpdateMode, Rotation};
use ssd1677::graphics::GraphicsDisplayBlackAndWhite;

extern crate alloc;

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_hal_embassy::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 timer0 = SystemTimer::new(peripherals.SYSTIMER);
    esp_hal_embassy::init(timer0.alarm0);

    info!("Embassy initialized!");

    let _ = spawner;

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

    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, 24), 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;
    }
}
