use crate::{Aabb, SegmentCollector};

#[derive(Debug, Clone)]
pub enum ReadGlyphOutlineError<Inner: std::error::Error> {
    NotFound,
    ShouldRasterize,
    EmptyGlyph,
    Other(Inner),
}

impl<Inner: std::error::Error> std::fmt::Display for ReadGlyphOutlineError<Inner> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ReadGlyphOutlineError::NotFound => write!(f, "Requested glyph was not found in font."),
            ReadGlyphOutlineError::ShouldRasterize => {
                write!(
                    f,
                    "Requested glyph can not be rendered with signed distance field."
                )
            }
            ReadGlyphOutlineError::EmptyGlyph => write!(f, "Requested glyph is empty."),
            ReadGlyphOutlineError::Other(e) => std::fmt::Display::fmt(e, f),
        }
    }
}
impl<Inner: std::error::Error> std::error::Error for ReadGlyphOutlineError<Inner> {}

/// Represents font data sources.
pub trait OutlineProvider {
    /// Provider-specific error when font data can not be decoded.
    type ReadFontError: std::error::Error;

    /// Get the length in font unit corresponding to 1 em.
    fn length_per_em(&self) -> f32;

    /// Collect the outline of a glyph.
    ///
    /// # Parameters
    /// - `settings`: Outline settings.
    /// - `collector`: Outline segment collector. For each segment of the outline, call the
    ///   corresponding method of `collector`.
    ///
    /// # Returns
    /// The bounding box of the glyph in font units, if operation is successful.
    ///
    /// # Errors
    /// - Implementors may error if a glyph is not found, if a glyph doesn't have any outline and
    ///   should be rasterized instead, or any other error when reading font data.
    fn collect_outline<'collect>(
        &self,
        collector: &mut SegmentCollector<'collect>,
    ) -> Result<Aabb, ReadGlyphOutlineError<Self::ReadFontError>>;
}

#[cfg(feature = "skrifa")]
pub struct SkrifaOutlineProvider<'a> {
    font: skrifa::FontRef<'a>,
    glyph_id: skrifa::GlyphId,
}

#[cfg(feature = "skrifa")]
impl<'a> SkrifaOutlineProvider<'a> {
    pub fn new(font: skrifa::FontRef<'a>, glyph_id: skrifa::GlyphId) -> Self {
        Self { font, glyph_id }
    }
}

#[cfg(feature = "skrifa")]
impl<'a> OutlineProvider for SkrifaOutlineProvider<'a> {
    type ReadFontError = skrifa::outline::DrawError;

    fn collect_outline<'collect>(
        &self,
        collector: &mut SegmentCollector<'collect>,
    ) -> Result<Aabb, ReadGlyphOutlineError<Self::ReadFontError>> {
        use skrifa::{MetadataProvider, raw::TableProvider};

        let outlines = self.font.outline_glyphs();
        let font = &self.font;

        if let Ok(colr) = font.colr()
            && (colr.v0_base_glyph(self.glyph_id).is_ok_and(|x| x.is_some())
                || colr.v1_base_glyph(self.glyph_id).is_ok_and(|x| x.is_some()))
        {
            return Err(ReadGlyphOutlineError::ShouldRasterize);
        }
        if let Ok(svg) = font.svg()
            && svg.glyph_data(self.glyph_id).is_ok_and(|x| x.is_some())
        {
            return Err(ReadGlyphOutlineError::ShouldRasterize);
        }
        if font
            .bitmap_strikes()
            .glyph_for_size(skrifa::prelude::Size::unscaled(), self.glyph_id)
            .is_some()
        {
            return Err(ReadGlyphOutlineError::ShouldRasterize);
        }
        let (Some(glyph), Some(bound)) = (
            outlines.get(self.glyph_id),
            font.glyph_metrics(
                skrifa::prelude::Size::unscaled(),
                skrifa::prelude::LocationRef::default(),
            )
            .bounds(self.glyph_id),
        ) else {
            return Err(ReadGlyphOutlineError::NotFound);
        };

        let format = outlines.format();
        let reversed = matches!(
            format,
            Some(skrifa::outline::OutlineGlyphFormat::Cff)
                | Some(skrifa::outline::OutlineGlyphFormat::Cff2)
        );
        collector.set_reversed(reversed);
        glyph
            .draw(
                skrifa::outline::DrawSettings::unhinted(
                    skrifa::prelude::Size::unscaled(),
                    skrifa::prelude::LocationRef::default(),
                ),
                collector,
            )
            .map_err(ReadGlyphOutlineError::Other)?;

        Ok(Aabb::new(
            bound.x_min,
            bound.y_min,
            bound.x_max,
            bound.y_max,
        ))
    }

    fn length_per_em(&self) -> f32 {
        use skrifa::MetadataProvider;

        self.font
            .metrics(
                skrifa::prelude::Size::unscaled(),
                skrifa::prelude::LocationRef::default(),
            )
            .units_per_em as f32
    }
}

#[cfg(feature = "skrifa")]
impl skrifa::outline::OutlinePen for SegmentCollector<'_> {
    /// Emit a command to begin a new subpath at (x, y).
    fn move_to(&mut self, x: f32, y: f32) {
        self.move_to(x, y);
    }

    /// Emit a line segment from the current point to (x, y).
    fn line_to(&mut self, x: f32, y: f32) {
        self.line_to(x, y);
    }

    /// Emit a quadratic bezier segment from the current point with a control
    /// point at (cx0, cy0) and ending at (x, y).
    fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
        self.quad_to(cx0, cy0, x, y);
    }

    /// Emit a cubic bezier segment from the current point with control
    /// points at (cx0, cy0) and (cx1, cy1) and ending at (x, y).
    fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
        self.curve_to(cx0, cy0, cx1, cy1, x, y);
    }

    /// Emit a command to close the current subpath.
    fn close(&mut self) {
        self.close()
    }
}
