use embedded_hal::blocking::delay::DelayUs;
use embedded_hal::digital::v2::OutputPin;
use epd_waveshare::epd4in2::EPD4in2;
use epd_waveshare::prelude::{Color, DisplayStream, QuickRefresh, RefreshLUT, WaveshareDisplay};
use mkl25z4_hal::time::CopyableMonoTimer;
use tinygfx::color::BlackWhite;
use tinygfx::{Frame, Renderer};

use super::pins::{
    DisplayBmeSpi, DisplayBusy, DisplayCs, DisplayDc, DisplayPins, DisplayPwr, DisplayRst,
};
use super::ui::Ui;

const FULL_REFRESH_INTERVAL: usize = 5;

pub struct Display {
    pwr: DisplayPwr,
    epd: EPD4in2<DisplayBmeSpi, DisplayCs, DisplayBusy, DisplayDc, DisplayRst>,
    refresh_counter: usize,
    current_ui: Option<Ui>,
}

impl Display {
    pub fn init(
        mut pins: DisplayPins,
        spi: &mut DisplayBmeSpi,
        mut time: CopyableMonoTimer,
    ) -> Display {
        // Enable power.
        pins.pwr.set_low().ok();
        // Wait a bit for power to stabilize.
        time.delay_us(100u8);

        let epd = EPD4in2::new(spi, pins.cs, pins.busy, pins.dc, pins.rst, &mut time).unwrap(); // TODO: Error handling?

        // Disable power again.
        pins.pwr.set_high().ok();

        Display {
            pwr: pins.pwr,
            epd,
            refresh_counter: 0,
            current_ui: None,
        }
    }

    pub fn update(&mut self, ui: Ui, spi: &mut DisplayBmeSpi, mut time: CopyableMonoTimer) {
        // Enable power.
        self.pwr.set_low().ok();
        // Wait a bit for power to stabilize.
        time.delay_us(100u8);

        // TODO: Error handling.
        self.epd.wake_up(spi, &mut time).ok();

        // We perform a full refresh every X images or if we do not know the state of the display.
        let current_ui = self.current_ui.as_ref();
        if self.refresh_counter == 0 || current_ui.is_none() {
            self.refresh_counter = FULL_REFRESH_INTERVAL - 1;

            self.epd.set_background_color(Color::White);
            self.epd.set_lut(spi, Some(RefreshLUT::FULL)).unwrap();

            // Just draw the new UI.
            let mut frame = Frame::new(400, 300, |mut renderer| {
                ui.draw(&mut renderer);
            });
            frame.mirror_x(true);
            frame.mirror_y(true);
            let stream = TinyGfxStream::new(frame);
            self.epd.update_frame_stream(spi, stream).unwrap();
        } else {
            self.refresh_counter -= 1;
            self.epd.set_lut(spi, Some(RefreshLUT::QUICK)).unwrap();

            // Draw both old and new frames.
            let mut frame = Frame::new(400, 300, |mut renderer| {
                current_ui.unwrap().draw(&mut renderer);
            });
            frame.mirror_x(true);
            frame.mirror_y(true);
            let stream = TinyGfxStream::new(frame);
            self.epd.stream_old_frame(spi, stream).unwrap();

            let mut frame = Frame::new(400, 300, |mut renderer| {
                ui.draw(&mut renderer);
            });
            frame.mirror_x(true);
            frame.mirror_y(true);
            let stream = TinyGfxStream::new(frame);
            self.epd.stream_new_frame(spi, stream).unwrap();
        }
        self.current_ui = Some(ui);

        self.epd.display_frame(spi).unwrap();

        // Disable power again.
        self.pwr.set_high().ok();
    }
}

struct TinyGfxStream<Draw> {
    frame: Frame<Draw, BlackWhite>,
    buffer: [u8; 400 / 8],
    row: u32,
}

impl<Draw> TinyGfxStream<Draw>
where
    Draw: Fn(Renderer<BlackWhite>),
{
    fn new(frame: Frame<Draw, BlackWhite>) -> Self {
        Self {
            frame,
            buffer: [0u8; 400 / 8],
            row: 0,
        }
    }
}

impl<Draw> DisplayStream for TinyGfxStream<Draw>
where
    Draw: Fn(Renderer<BlackWhite>),
{
    fn next<'a>(&'a mut self) -> Option<&'a [u8]> {
        if self.row == self.frame.height() {
            None
        } else {
            self.frame.draw_row(self.row, &mut self.buffer);
            self.row += 1;
            Some(&self.buffer)
        }
    }
}
