// 320 * 240 * 2 bytes/pixel = 150KiB of RAM

use core::convert::Infallible;

use embedded_graphics::{
    draw_target::DrawTarget,
    geometry::{Dimensions, Point, Size},
    pixelcolor::{raw::ToBytes, Rgb565},
    primitives::Rectangle,
    Pixel,
};

use crate::{
    app::{HEIGHT, WIDTH},
    drivers::st7789::St7789,
};

pub struct BufferedDisplay {
    target: St7789,
    cur: Option<&'static mut [u8; WIDTH * HEIGHT * 2]>,
}

impl BufferedDisplay {
    pub fn new(target: St7789, cur: &'static mut [u8; WIDTH * HEIGHT * 2]) -> Self {
        Self {
            target,
            cur: Some(cur),
        }
    }

    pub fn flush(&mut self) {
        let buf = self.target.write_framebuffer(self.cur.take().unwrap());
        self.cur = Some(buf);
    }
}

impl Dimensions for BufferedDisplay {
    fn bounding_box(&self) -> Rectangle {
        Rectangle {
            top_left: Point::zero(),
            size: Size::new(WIDTH.try_into().unwrap(), HEIGHT.try_into().unwrap()),
        }
    }
}

impl DrawTarget for BufferedDisplay {
    type Color = Rgb565;
    type Error = Infallible;

    fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
    where
        I: IntoIterator<Item = embedded_graphics::prelude::Pixel<Self::Color>>,
    {
        for Pixel(p, c) in pixels {
            if let Some(idx) = index(p.x, p.y) {
                let bytes = c.to_be_bytes();
                self.cur.as_mut().unwrap()[idx] = bytes[0];
                self.cur.as_mut().unwrap()[idx + 1] = bytes[1];
            }
        }

        Ok(())
    }
}

#[inline(always)]
fn index<X: TryInto<usize>, Y: TryInto<usize>>(x: X, y: Y) -> Option<usize> {
    let x: usize = x.try_into().ok()?;
    let y: usize = y.try_into().ok()?;

    if y >= HEIGHT || x >= WIDTH {
        return None;
    }

    Some((x * HEIGHT + y) * 2)
}