use limine::request::FramebufferRequest;

#[used]
#[unsafe(link_section = ".requests")]
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();

use super::colour::Colour;
use core::panic;

use limine::framebuffer::Framebuffer;
use spin::{Lazy, Mutex};

pub static FRAMEBUFFER_WRITER: Lazy<Mutex<Option<FramebufferWriter>>> =
    Lazy::new(|| {
        Mutex::new(FRAMEBUFFER_REQUEST.get_response().map_or_else(
            || {
                panic!("Framebuffer request failed");
            },
            |framebuffer_response| {
                let framebuffer =
                    framebuffer_response.framebuffers().next().unwrap();
                Some(FramebufferWriter::new(framebuffer))
            },
        ))
    });

/// The updated writer stores necessary fields from the [Framebuffer].
/// This ensures that the contained types are Send, as Framebuffer was
/// not marked as Send.
///
/// It also avoids the requirement for lifetimes.
///
/// Note this does not implement Writer as these functions only handle drawing
/// pixels.
pub struct FramebufferWriter {
    pitch: u64,
    bpp: u16,
    addr: *mut u8,
    width: u64,
    height: u64,
}

unsafe impl Send for FramebufferWriter {}
unsafe impl Sync for FramebufferWriter {}

impl FramebufferWriter {
    pub fn new(framebuffer: Framebuffer) -> Self {
        Self {
            pitch: framebuffer.pitch(),
            bpp: framebuffer.bpp(),
            addr: framebuffer.addr(),
            width: framebuffer.width(),
            height: framebuffer.height(),
        }
    }

    pub fn write_pixel(&self, x: usize, y: usize, color: Colour) {
        let pitch = self.pitch as usize;
        let bpp = (self.bpp / 8) as usize;
        let pixel_offset = y * pitch + x * bpp;

        unsafe {
            *(self.addr.add(pixel_offset) as *mut u32) = color.into();
        }
    }

    pub fn render_frame(&self, buffer: &[&[Colour]]) {
        // TODO: this should return errors
        for (y, &row) in buffer.iter().enumerate() {
            if y >= self.height() as usize {
                break;
            }
            for (x, pixel) in row.iter().enumerate() {
                if x >= self.width() as usize {
                    break;
                }
                self.write_pixel(x, y, *pixel);
            }
        }
    }

    pub const fn width(&self) -> u32 {
        self.width as u32
    }

    pub const fn height(&self) -> u32 {
        self.height as u32
    }

    pub fn clear(&self) {
        let width = self.width as usize;
        let height = self.height as usize;

        for y in 0..height {
            for x in 0..width {
                self.write_pixel(x, y, Colour::Black);
            }
        }
    }
}

pub fn screensize_px() -> (u32, u32) {
    FRAMEBUFFER_WRITER
        .lock()
        .as_mut()
        .map_or_else(|| (0, 0), |writer| (writer.width(), writer.height()))
}
