use std::{fs, path::Path, sync::Arc};

use crate::{error::Error, CONFIG};

#[derive(Clone)]
pub struct Fonts {
    pub regular: Arc<TrueTypeFont<'static>>,
    pub italic: Arc<TrueTypeFont<'static>>,
    pub bold: Arc<TrueTypeFont<'static>>,
    pub bold_italic: Arc<TrueTypeFont<'static>>,
}

pub trait Font {
    fn layout(&self) -> &FontLayout;
    fn map_glyph<F: FnMut(usize, usize, f32)>(&self, ch: char, mapper: F);
}

pub struct TrueTypeFont<'a> {
    inner: rusttype::Font<'a>,
    scale: rusttype::Scale,
    layout: FontLayout,
    ascent: f32,
}

impl TrueTypeFont<'static> {
    pub fn load<P: AsRef<Path>>(path: P, pixel_height: usize) -> Result<Self, Error> {
        let font_data = fs::read(path)?;
        let inner = rusttype::Font::try_from_vec(font_data).ok_or(Error::FontLoadError)?;
        let unscaled_v_metrics = inner.v_metrics(rusttype::Scale::uniform(1.0f32));
        let scale =
            (pixel_height as f32) / (unscaled_v_metrics.ascent - unscaled_v_metrics.descent);
        let scale = rusttype::Scale::uniform(scale);
        let v_metrics = inner.v_metrics(scale);
        let a_h_metrics = inner.glyph('a').scaled(scale).h_metrics();
        let layout = FontLayout {
            width: a_h_metrics.advance_width.ceil() as usize,
            height: (v_metrics.ascent - v_metrics.descent).ceil() as usize,
        };
        let ascent = v_metrics.ascent;
        Ok(Self {
            inner,
            scale,
            layout,
            ascent,
        })
    }
}

impl Font for TrueTypeFont<'_> {
    fn layout(&self) -> &FontLayout {
        &self.layout
    }

    fn map_glyph<F: FnMut(usize, usize, f32)>(&self, ch: char, mut mapper: F) {
        let glyph = self
            .inner
            .glyph(ch)
            .scaled(self.scale)
            .positioned(rusttype::Point {
                x: 0.0,
                y: self.ascent,
            });

        if let Some(bb) = glyph.pixel_bounding_box() {
            glyph.draw(|x, y, v| {
                let x = (x as i32 + bb.min.x).clamp(0, self.layout.width as i32);
                let y = (y as i32 + bb.min.y).clamp(0, self.layout.height as i32);
                mapper(x as usize, y as usize, v);
            });
        }
    }
}

#[derive(Debug)]
pub struct FontLayout {
    pub width: usize,
    pub height: usize,
}

impl Fonts {
    pub fn from_config() -> Result<Self, Error> {
        let config = &*CONFIG;
        let regular = Arc::new(TrueTypeFont::load(
            &config.fonts.regular,
            config.fonts.size,
        )?);
        let italic = TrueTypeFont::load(&config.fonts.italic, config.fonts.size)
            .map(Arc::new)
            .inspect_err(|error| log::error!("{}: {error}", config.fonts.italic.display()))
            .unwrap_or_else(|_| regular.clone());
        let bold = TrueTypeFont::load(&config.fonts.bold, config.fonts.size)
            .map(Arc::new)
            .inspect_err(|error| log::error!("{}: {error}", config.fonts.bold.display()))
            .unwrap_or_else(|_| regular.clone());
        let bold_italic = TrueTypeFont::load(&config.fonts.bold_italic, config.fonts.size)
            .map(Arc::new)
            .inspect_err(|error| log::error!("{}: {error}", config.fonts.bold_italic.display()))
            .unwrap_or_else(|_| italic.clone());
        Ok(Self {
            regular,
            italic,
            bold,
            bold_italic,
        })
    }
}
