///|
/// Parsed font face used by higher-level layout and rendering APIs.
struct FontFace {
  font : Font
} derive(Debug)

///|
pub extend FontFace with Debug::{to_repr}

///|
/// Wrap an already parsed font as a face.
fn FontFace::from_font(font : Font) -> FontFace {
  { font, }
}

///|
/// Parse TTF bytes into a checked font face.
pub fn FontFace::from_bytes(data : Bytes) -> Result[FontFace, FontParseError] {
  match parse_font_result(data) {
    Ok(font) => Ok(FontFace::from_font(font))
    Err(error) => Err(error)
  }
}

///|
/// Parse a raw font-data container into a checked font face.
pub fn FontFace::from_data(data : FontData) -> Result[FontFace, FontParseError] {
  guard data.get_font_index() is 0 else {
    return Err(UnsupportedFontIndex(data.get_font_index()))
  }
  FontFace::from_bytes(data.get_data())
}

///|
/// Parse this raw font-data container into a checked font face.
pub fn FontData::parse(self : FontData) -> Result[FontFace, FontParseError] {
  FontFace::from_data(self)
}

///|
/// Small checked font-face cache for GUI/resource code that repeatedly asks for
/// the same parsed face.
struct FontFaceCache {
  mut faces : Map[String, FontFace]
  mut access_order : Array[String]
  capacity : Int
  is_bounded : Bool
} derive(Debug)

///|
pub extend FontFaceCache with Debug::{to_repr}

///|
/// Result of reading a checked font face from cache or parsing it on miss.
struct FontFaceCacheParseResult {
  face : FontFace
  did_parse : Bool
} derive(Debug)

///|
pub extend FontFaceCacheParseResult with Debug::{to_repr}

///|
/// Create an empty font-face cache.
pub fn FontFaceCache::new() -> FontFaceCache {
  { faces: Map([]), access_order: [], capacity: 0, is_bounded: false, }
}

///|
/// Create an empty font-face cache with least-recently-used eviction.
pub fn FontFaceCache::with_capacity(capacity : Int) -> FontFaceCache {
  {
    faces: Map([]),
    access_order: [],
    capacity: capacity.max(1),
    is_bounded: true,
  }
}

///|
/// Get the resulting checked font face.
pub fn FontFaceCacheParseResult::get_face(
  self : FontFaceCacheParseResult,
) -> FontFace {
  self.face
}

///|
/// Return whether this lookup parsed bytes instead of hitting cache.
pub fn FontFaceCacheParseResult::did_parse(
  self : FontFaceCacheParseResult,
) -> Bool {
  self.did_parse
}

///|
/// Return whether this lookup reused a checked face from cache.
pub fn FontFaceCacheParseResult::did_hit_cache(
  self : FontFaceCacheParseResult,
) -> Bool {
  !self.did_parse
}

///|
/// Return the number of cached font faces.
pub fn FontFaceCache::len(self : FontFaceCache) -> Int {
  self.faces.length()
}

///|
/// Return whether the cache currently has no parsed faces.
pub fn FontFaceCache::is_empty(self : FontFaceCache) -> Bool {
  self.len() is 0
}

///|
/// Return the maximum number of cached font faces, or 0 for an unbounded cache.
pub fn FontFaceCache::capacity(self : FontFaceCache) -> Int {
  self.capacity
}

///|
/// Return whether the cache has a configured entry limit.
pub fn FontFaceCache::is_capacity_bounded(self : FontFaceCache) -> Bool {
  self.is_bounded
}

///|
/// Return whether the cache has a parsed face for this key without refreshing LRU.
pub fn FontFaceCache::contains(self : FontFaceCache, key : String) -> Bool {
  self.faces.contains(key)
}

///|
/// Look up a parsed face by caller-provided key.
pub fn FontFaceCache::get(self : FontFaceCache, key : String) -> FontFace? {
  match self.faces.get(key) {
    Some(face) => {
      self.touch_key(key)
      Some(face)
    }
    None => None
  }
}

///|
/// Insert or replace a parsed face under a caller-provided key.
pub fn FontFaceCache::insert(
  self : FontFaceCache,
  key : String,
  face : FontFace,
) -> FontFace {
  self.faces[key] = face
  self.touch_key(key)
  self.evict_over_capacity()
  face
}

///|
/// Parse bytes on first use and return the cached checked face afterwards.
pub fn FontFaceCache::get_or_parse(
  self : FontFaceCache,
  key : String,
  data : Bytes,
) -> Result[FontFace, FontParseError] {
  match self.get_or_parse_result(key, data) {
    Ok(result) => Ok(result.get_face())
    Err(error) => Err(error)
  }
}

///|
/// Parse bytes on first use and report whether cache missed.
pub fn FontFaceCache::get_or_parse_result(
  self : FontFaceCache,
  key : String,
  data : Bytes,
) -> Result[FontFaceCacheParseResult, FontParseError] {
  match self.get(key) {
    Some(face) => Ok({ face, did_parse: false, })
    None =>
      match FontFace::from_bytes(data) {
        Ok(face) => Ok({ face: self.insert(key, face), did_parse: true, })
        Err(error) => Err(error)
      }
  }
}

///|
/// Remove a cached face by key.
pub fn FontFaceCache::remove(self : FontFaceCache, key : String) -> Unit {
  self.faces.remove(key)
  self.remove_access_key(key)
}

///|
/// Drop all cached faces.
pub fn FontFaceCache::clear(self : FontFaceCache) -> Unit {
  self.faces = Map([])
  self.access_order = []
}

///|
fn FontFaceCache::touch_key(self : FontFaceCache, key : String) -> Unit {
  self.remove_access_key(key)
  self.access_order.push(key)
}

///|
fn FontFaceCache::remove_access_key(self : FontFaceCache, key : String) -> Unit {
  let mut index = 0
  while index < self.access_order.length() {
    if self.access_order[index] == key {
      ignore(self.access_order.remove(index))
    } else {
      index += 1
    }
  }
}

///|
fn FontFaceCache::evict_over_capacity(self : FontFaceCache) -> Unit {
  guard self.is_bounded && self.capacity > 0 else { return }
  while self.faces.length() > self.capacity && self.access_order.length() > 0 {
    let key = self.access_order.remove(0)
    self.faces.remove(key)
  }
}

///|
/// Get the parsed font backing this face.
fn FontFace::get_font(self : FontFace) -> Font {
  self.font
}

///|
/// Get face-wide metrics in font units.
pub fn FontFace::get_metrics(self : FontFace) -> FontMetrics {
  let (ascent, descent, line_gap) = self.font.get_metrics()
  FontMetrics::new(
    units_per_em=self.font.get_units_per_em(),
    ascent~,
    descent~,
    line_gap~,
  )
}

///|
/// Get units per em for the face.
pub fn FontFace::get_units_per_em(self : FontFace) -> Int {
  self.font.get_units_per_em()
}

///|
/// Map a Unicode codepoint to a glyph id.
pub fn FontFace::char_to_glyph(self : FontFace, char_code : Int) -> Int {
  self.font.char_to_glyph(char_code)
}

///|
/// Return whether this face maps a Unicode codepoint to a drawable glyph.
pub fn FontFace::has_glyph_for_char(self : FontFace, char_code : Int) -> Bool {
  self.font.has_glyph_for_char(char_code)
}

///|
/// Get resolved glyph data for a glyph id.
fn FontFace::get_glyph(self : FontFace, glyph_id : Int) -> GlyphData {
  self.font.get_glyph(glyph_id)
}