//! Support for accessing glyph names.

use core::ops::Range;
use raw::{
    tables::{
        cff::Cff,
        post::Post,
        postscript::{Charset, CharsetIter, StringId as Sid},
    },
    types::GlyphId,
    FontRef, TableProvider,
};

/// "Names must be no longer than 63 characters; some older implementations
/// can assume a length limit of 31 characters."
/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/post#version-20>
const MAX_GLYPH_NAME_LEN: usize = 63;

/// Mapping from glyph identifiers to names.
///
/// This sources glyph names from the `post` and `CFF` tables in that order.
/// If glyph names are not available in either, then they are synthesized
/// as `gidDDD` where `DDD` is the glyph identifier in decimal. Use the
/// [`source`](Self::source) to determine which source was chosen.
#[derive(Clone)]
pub struct GlyphNames<'a> {
    inner: Inner<'a>,
}

#[derive(Clone)]
enum Inner<'a> {
    // Second field is num_glyphs
    Post(Post<'a>, u32),
    Cff(Cff<'a>, Charset<'a>),
    Synthesized(u32),
}

impl<'a> GlyphNames<'a> {
    /// Creates a new object for accessing glyph names from the given font.
    pub fn new(font: &FontRef<'a>) -> Self {
        let num_glyphs = font
            .maxp()
            .map(|maxp| maxp.num_glyphs() as u32)
            .unwrap_or_default();
        if let Ok(post) = font.post() {
            if post.num_names() != 0 {
                return Self {
                    inner: Inner::Post(post, num_glyphs),
                };
            }
        }
        if let Some((cff, charset)) = font
            .cff()
            .ok()
            .and_then(|cff| Some((cff.clone(), cff.charset(0).ok()??)))
        {
            return Self {
                inner: Inner::Cff(cff, charset),
            };
        }
        Self {
            inner: Inner::Synthesized(num_glyphs),
        }
    }

    /// Returns the chosen source for glyph names.
    pub fn source(&self) -> GlyphNameSource {
        match &self.inner {
            Inner::Post(..) => GlyphNameSource::Post,
            Inner::Cff(..) => GlyphNameSource::Cff,
            Inner::Synthesized(..) => GlyphNameSource::Synthesized,
        }
    }

    /// Returns the number of glyphs in the font.
    pub fn num_glyphs(&self) -> u32 {
        match &self.inner {
            Inner::Post(_, n) | Inner::Synthesized(n) => *n,
            Inner::Cff(_, charset) => charset.num_glyphs(),
        }
    }

    /// Returns the name for the given glyph identifier.
    pub fn get(&self, glyph_id: GlyphId) -> Option<GlyphName> {
        if glyph_id.to_u32() >= self.num_glyphs() {
            return None;
        }
        let name = match &self.inner {
            Inner::Post(post, _) => GlyphName::from_post(post, glyph_id),
            Inner::Cff(cff, charset) => charset
                .string_id(glyph_id)
                .ok()
                .and_then(|sid| GlyphName::from_cff_sid(cff, sid)),
            _ => None,
        };
        // If name is empty string, synthesize it
        if name.as_ref().is_none_or(|s| s.is_empty()) {
            return Some(GlyphName::synthesize(glyph_id));
        }
        Some(name.unwrap_or_else(|| GlyphName::synthesize(glyph_id)))
    }

    /// Returns an iterator yielding the identifier and name for all glyphs in
    /// the font.
    pub fn iter(&self) -> impl Iterator<Item = (GlyphId, GlyphName)> + 'a + Clone {
        match &self.inner {
            Inner::Post(post, n) => Iter::Post(0..*n, post.clone()),
            Inner::Cff(cff, charset) => Iter::Cff(cff.clone(), charset.iter()),
            Inner::Synthesized(n) => Iter::Synthesized(0..*n),
        }
    }
}

/// Specifies the chosen source for glyph names.
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum GlyphNameSource {
    /// Glyph names are sourced from the `post` table.
    Post,
    /// Glyph names are sourced from the `CFF` table.
    Cff,
    /// Glyph names are synthesized in the format `gidDDD` where `DDD` is
    /// the glyph identifier in decimal.
    Synthesized,
}

/// The name of a glyph.
#[derive(Clone)]
pub struct GlyphName {
    name: [u8; MAX_GLYPH_NAME_LEN],
    len: u8,
    is_synthesized: bool,
}

impl GlyphName {
    /// Returns the underlying name as a string.
    pub fn as_str(&self) -> &str {
        let bytes = &self.name[..self.len as usize];
        core::str::from_utf8(bytes).unwrap_or_default()
    }

    /// Returns true if the glyph name was synthesized, i.e. not found in any
    /// source.
    pub fn is_synthesized(&self) -> bool {
        self.is_synthesized
    }

    fn from_bytes(bytes: &[u8]) -> Self {
        let mut name = Self::default();
        name.append(bytes);
        name
    }

    fn from_post(post: &Post, glyph_id: GlyphId) -> Option<Self> {
        glyph_id
            .try_into()
            .ok()
            .and_then(|id| post.glyph_name(id))
            .map(|s| s.as_bytes())
            .map(Self::from_bytes)
    }

    fn from_cff_sid(cff: &Cff, sid: Sid) -> Option<Self> {
        cff.string(sid)
            .and_then(|s| core::str::from_utf8(s.bytes()).ok())
            .map(|s| s.as_bytes())
            .map(Self::from_bytes)
    }

    fn synthesize(glyph_id: GlyphId) -> Self {
        use core::fmt::Write;
        let mut name = Self {
            is_synthesized: true,
            ..Self::default()
        };
        let _ = write!(GlyphNameWrite(&mut name), "gid{}", glyph_id.to_u32());
        name
    }

    /// Appends the given bytes to `self` while keeping the maximum length
    /// at 63 bytes.
    ///
    /// This exists primarily to support the [`core::fmt::Write`] impl
    /// (which is used for generating synthesized glyph names) because
    /// we have no guarantee of how many times `write_str` might be called
    /// for a given format.
    fn append(&mut self, bytes: &[u8]) {
        // We simply truncate when length exceeds the max since glyph names
        // are expected to be <= 63 chars
        let start = self.len as usize;
        let available = MAX_GLYPH_NAME_LEN - start;
        let copy_len = available.min(bytes.len());
        self.name[start..start + copy_len].copy_from_slice(&bytes[..copy_len]);
        self.len = (start + copy_len) as u8;
    }
}

impl Default for GlyphName {
    fn default() -> Self {
        Self {
            name: [0; MAX_GLYPH_NAME_LEN],
            len: 0,
            is_synthesized: false,
        }
    }
}

impl core::fmt::Debug for GlyphName {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("GlyphName")
            .field("name", &self.as_str())
            .field("is_synthesized", &self.is_synthesized)
            .finish()
    }
}

impl core::fmt::Display for GlyphName {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl core::ops::Deref for GlyphName {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        self.as_str()
    }
}

impl PartialEq<&str> for GlyphName {
    fn eq(&self, other: &&str) -> bool {
        self.as_str() == *other
    }
}

struct GlyphNameWrite<'a>(&'a mut GlyphName);

impl core::fmt::Write for GlyphNameWrite<'_> {
    fn write_str(&mut self, s: &str) -> core::fmt::Result {
        self.0.append(s.as_bytes());
        Ok(())
    }
}

#[derive(Clone)]
enum Iter<'a> {
    Post(Range<u32>, Post<'a>),
    Cff(Cff<'a>, CharsetIter<'a>),
    Synthesized(Range<u32>),
}

impl Iter<'_> {
    fn next_name(&mut self) -> Option<Result<(GlyphId, GlyphName), GlyphId>> {
        match self {
            Self::Post(range, post) => {
                let gid = GlyphId::new(range.next()?);
                Some(
                    GlyphName::from_post(post, gid)
                        .map(|name| (gid, name))
                        .ok_or(gid),
                )
            }
            Self::Cff(cff, iter) => {
                let (gid, sid) = iter.next()?;
                Some(
                    GlyphName::from_cff_sid(cff, sid)
                        .map(|name| (gid, name))
                        .ok_or(gid),
                )
            }
            Self::Synthesized(range) => {
                let gid = GlyphId::new(range.next()?);
                Some(Ok((gid, GlyphName::synthesize(gid))))
            }
        }
    }
}

impl Iterator for Iter<'_> {
    type Item = (GlyphId, GlyphName);

    fn next(&mut self) -> Option<Self::Item> {
        match self.next_name()? {
            Ok((gid, name)) if name.is_empty() => Some((gid, GlyphName::synthesize(gid))),
            Ok(gid_name) => Some(gid_name),
            Err(gid) => Some((gid, GlyphName::synthesize(gid))),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use raw::{FontData, FontRead};

    #[test]
    fn synthesized_glyph_names() {
        let count = 58;
        let names = GlyphNames {
            inner: Inner::Synthesized(58),
        };
        let names_buf = (0..count).map(|i| format!("gid{i}")).collect::<Vec<_>>();
        let expected_names = names_buf.iter().map(|s| s.as_str()).collect::<Vec<_>>();
        for (_, name) in names.iter() {
            assert!(name.is_synthesized())
        }
        check_names(&names, &expected_names, GlyphNameSource::Synthesized);
    }

    #[test]
    fn synthesize_for_empty_names() {
        let mut post_data = font_test_data::post::SIMPLE.to_vec();
        // last name in this post data is "hola" so pop 5 bytes and then
        // push a 0 to simulate an empty name
        post_data.truncate(post_data.len() - 5);
        post_data.push(0);
        let post = Post::read(FontData::new(&post_data)).unwrap();
        let gid = GlyphId::new(9);
        assert!(post.glyph_name(gid.try_into().unwrap()).unwrap().is_empty());
        let names = GlyphNames {
            inner: Inner::Post(post, 10),
        };
        assert_eq!(names.get(gid).unwrap(), "gid9");
        assert_eq!(names.iter().last().unwrap().1, "gid9");
    }

    #[test]
    fn cff_glyph_names() {
        let font = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED).unwrap();
        let names = GlyphNames::new(&font);
        assert_eq!(names.source(), GlyphNameSource::Cff);
        let expected_names = [".notdef", "i", "j", "k", "l"];
        check_names(&names, &expected_names, GlyphNameSource::Cff);
    }

    #[test]
    fn post_glyph_names() {
        let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
        let names = GlyphNames::new(&font);
        let expected_names = [
            ".notdef",
            "space",
            "A",
            "I",
            "T",
            "Aacute",
            "Agrave",
            "Iacute",
            "Igrave",
            "Amacron",
            "Imacron",
            "acutecomb",
            "gravecomb",
            "macroncomb",
            "A.001",
            "A.002",
            "A.003",
            "A.004",
            "A.005",
            "A.006",
            "A.007",
            "A.008",
            "A.009",
            "A.010",
        ];
        check_names(&names, &expected_names, GlyphNameSource::Post);
    }

    #[test]
    fn post_glyph_names_partial() {
        let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
        let mut names = GlyphNames::new(&font);
        let Inner::Post(_, len) = &mut names.inner else {
            panic!("it's a post table!");
        };
        // Increase count by 4 so we synthesize the remaining names
        *len += 4;
        let expected_names = [
            ".notdef",
            "space",
            "A",
            "I",
            "T",
            "Aacute",
            "Agrave",
            "Iacute",
            "Igrave",
            "Amacron",
            "Imacron",
            "acutecomb",
            "gravecomb",
            "macroncomb",
            "A.001",
            "A.002",
            "A.003",
            "A.004",
            "A.005",
            "A.006",
            "A.007",
            "A.008",
            "A.009",
            "A.010",
            // synthesized names...
            "gid24",
            "gid25",
            "gid26",
            "gid27",
        ];
        check_names(&names, &expected_names, GlyphNameSource::Post);
    }

    fn check_names(names: &GlyphNames, expected_names: &[&str], expected_source: GlyphNameSource) {
        assert_eq!(names.source(), expected_source);
        let iter_names = names.iter().collect::<Vec<_>>();
        assert_eq!(iter_names.len(), expected_names.len());
        for (i, expected) in expected_names.iter().enumerate() {
            let gid = GlyphId::new(i as u32);
            let name = names.get(gid).unwrap();
            assert_eq!(name, expected);
            assert_eq!(iter_names[i].0, gid);
            assert_eq!(iter_names[i].1, expected);
        }
    }
}
