use std::mem::size_of;

use bytemuck::{Pod, Zeroable};

struct Align<T, D: ?Sized> {
    _align: T,
    data: D,
}

static FONT_DATA: &Align<u32, [u8]> = &Align {
    _align: 0,
    data: *include_bytes!("../../../etc/fonts/regular.psfu"),
};

#[repr(C)]
#[derive(Pod, Zeroable, Clone, Copy)]
struct PsfHeader {
    magic: u32,
    version: u32,
    header_size: u32,
    flags: u32,
    num_glyph: u32,
    bytes_per_glyph: u32,
    height: u32,
    width: u32,
}

/// Represents a PSF-format font object
#[derive(Clone, Copy)]
pub struct PcScreenFont<'a> {
    header: &'a PsfHeader,
    glyph_data: &'a [u8],
}

impl Default for PcScreenFont<'static> {
    fn default() -> Self {
        Self::from_bytes(&FONT_DATA.data)
    }
}

impl<'a> PcScreenFont<'a> {
    /// Constructs an instance of [PcScreenFont] from its byte representation
    pub fn from_bytes(bytes: &'a [u8]) -> Self {
        let header: &PsfHeader = bytemuck::from_bytes(&bytes[..size_of::<PsfHeader>()]);
        let glyph_data = &bytes[header.header_size as usize..];

        Self { header, glyph_data }
    }

    /// Returns the character width of the font
    #[inline]
    pub const fn width(&self) -> u32 {
        self.header.width
    }

    /// Returns the character height of the font
    #[inline]
    pub const fn height(&self) -> u32 {
        self.header.height
    }

    /// Returns the count of glyphs present in the font
    #[allow(clippy::len_without_is_empty)]
    #[inline]
    pub const fn len(&self) -> u32 {
        self.header.num_glyph
    }

    /// Returns the data slice of a single glyph within the font
    #[inline]
    pub fn raw_glyph_data(&self, index: u32) -> &[u8] {
        &self.glyph_data[(index * self.header.bytes_per_glyph) as usize..]
    }

    pub fn map_glyph_pixels<F: FnMut(usize, usize, bool)>(&self, mut c: u32, mut put_pixel: F) {
        let bytes_per_line = (self.width() as usize + 7) / 8;
        // Draw character
        if c >= self.len() {
            c = b'?' as u32;
        }
        let mut glyph = self.raw_glyph_data(c);
        let mut y = 0;

        while y < self.height() as usize {
            let mut mask = 1 << (self.width() - 1);
            let mut x = 0;
            // let row_off = (cy + y) * self.width;

            while x < self.width() as usize {
                put_pixel(x, y, glyph[0] & mask != 0);
                mask >>= 1;
                x += 1;
            }
            glyph = &glyph[bytes_per_line..];
            y += 1;
        }
    }
}
