use crate::error::Result;
use crate::font::GlyphMapping;
use std::collections::BTreeSet;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum FontSubsetMode {
    Subset,
    FullFontFallback,
}

#[derive(Debug, Clone, PartialEq)]
pub struct EmbeddedGlyph {
    pub unicode: char,
    pub original_gid: u16,
    pub embedded_cid: u16,
    pub width: u16,
}

#[derive(Debug, Clone, PartialEq)]
pub struct FontEmbedding {
    pub family: String,
    pub mode: FontSubsetMode,
    pub font_data: Vec<u8>,
    pub glyphs: Vec<EmbeddedGlyph>,
}

fn full_font_fallback(family: &str, font_data: Vec<u8>, glyphs: &BTreeSet<GlyphMapping>) -> FontEmbedding {
    let face = ttf_parser::Face::parse(&font_data, 0).ok();
    let units_per_em = face.as_ref().map_or(1000.0, |f| f.units_per_em() as f32);

    let embedded_glyphs = glyphs.iter().map(|glyph| {
        let width = face.as_ref()
            .and_then(|f| {
                let gid = f.glyph_index(glyph.unicode)?;
                f.glyph_hor_advance(gid)
            })
            .map(|adv| ((adv as f32) * 1000.0 / units_per_em).round() as u16)
            .unwrap_or(1000);

        EmbeddedGlyph {
            unicode: glyph.unicode,
            original_gid: glyph.glyph_id,
            embedded_cid: glyph.glyph_id,
            width,
        }
    }).collect();

    FontEmbedding {
        family: family.to_string(),
        mode: FontSubsetMode::FullFontFallback,
        font_data,
        glyphs: embedded_glyphs,
    }
}

impl FontEmbedding {
    pub fn prepare(family: &str, font_data: &[u8], glyphs: &BTreeSet<GlyphMapping>) -> Result<Self> {
        let chars: BTreeSet<char> = glyphs.iter().map(|g| g.unicode).collect();
        
        match font_subset::Font::opentype(font_data) {
            Ok(face) => {
                match face.subset(&chars) {
                    Ok(subset) => {
                        let subset_bytes = subset.to_opentype();
                        if subset_bytes.len() < font_data.len() {
                            let subset_face = match ttf_parser::Face::parse(&subset_bytes, 0) {
                                Ok(f) => f,
                                Err(_) => return Ok(full_font_fallback(family, font_data.to_vec(), glyphs)),
                            };
                            let units_per_em = subset_face.units_per_em() as f32;
                            
                            let embedded_glyphs: Vec<EmbeddedGlyph> = glyphs.iter().filter_map(|glyph| {
                                let subset_gid = subset_face.glyph_index(glyph.unicode)?;
                                let width = ((subset_face.glyph_hor_advance(subset_gid)? as f32) * 1000.0 / units_per_em).round() as u16;
                                Some(EmbeddedGlyph {
                                    unicode: glyph.unicode,
                                    original_gid: glyph.glyph_id,
                                    embedded_cid: subset_gid.0,
                                    width,
                                })
                            }).collect();
                            
                            if embedded_glyphs.len() != glyphs.len() {
                                return Ok(full_font_fallback(family, font_data.to_vec(), glyphs));
                            }
                            
                            return Ok(Self {
                                family: family.to_string(),
                                mode: FontSubsetMode::Subset,
                                font_data: subset_bytes,
                                glyphs: embedded_glyphs,
                            });
                        } else {
                            return Ok(full_font_fallback(family, font_data.to_vec(), glyphs));
                        }
                    }
                    Err(_) => return Ok(full_font_fallback(family, font_data.to_vec(), glyphs)),
                }
            }
            Err(_) => return Ok(full_font_fallback(family, font_data.to_vec(), glyphs)),
        }
    }

    pub fn cid_for_char(&self, ch: char) -> Option<u16> {
        self.glyphs.iter().find(|glyph| glyph.unicode == ch).map(|glyph| glyph.embedded_cid)
    }
}
