///|
// Memoized platform text results for one renderer instance. A renderer holds
// one and shares it with its glyph atlas; nothing here depends on a concrete
// rendering backend.
pub struct NativeTextCache {
  priv engine : NativeTextEngine
  priv measure_cache : Map[String, @core.TextLayoutResult]
  priv layout_cache : Map[String, NativeTextLayout]
}

///|
pub fn NativeTextCache::new() -> NativeTextCache {
  NativeTextCache::new_with_engine(native_noop_text_engine())
}

///|
pub fn NativeTextCache::new_with_engine(
  engine : NativeTextEngine,
) -> NativeTextCache {
  { engine, measure_cache: Map([]), layout_cache: Map([]), }
}

///|
pub fn NativeTextCache::engine(self : NativeTextCache) -> NativeTextEngine {
  self.engine
}

///|
pub fn NativeTextCache::measure(
  self : NativeTextCache,
  input : @core.TextLayoutInput,
) -> @core.TextLayoutResult {
  let key = input.text +
    "|" +
    input.font.to_json().stringify() +
    "|" +
    Json(input.max_width).stringify()
  match self.measure_cache.get(key) {
    Some(result) => result
    None => {
      let result = self.engine.measure(input)
      self.measure_cache[key] = result
      result
    }
  }
}

///|
pub fn NativeTextCache::layout_run(
  self : NativeTextCache,
  run : @core.TextRun,
  scale_factor : Double,
) -> NativeTextLayout? {
  let key = run.text +
    "|" +
    run.font.to_json().stringify() +
    "|" +
    run.frame.size.width.to_string() +
    "|" +
    scale_factor.to_string()
  match self.layout_cache.get(key) {
    Some(layout) => Some(layout)
    None =>
      match self.engine.layout_run(run, scale_factor) {
        Some(layout) => {
          self.layout_cache[key] = layout
          Some(layout)
        }
        None => None
      }
  }
}

///|
// Retry a run against the engine's explicit fallback provider. Uncached by
// design: the caller only reaches it after the primary layout failed to fill a
// glyph atlas.
pub fn NativeTextCache::fallback_layout_run(
  self : NativeTextCache,
  run : @core.TextRun,
  scale_factor : Double,
) -> NativeTextLayout? {
  self.engine.fallback_layout_run(run, scale_factor)
}

///|
pub fn NativeTextCache::raster_glyph(
  self : NativeTextCache,
  key : NativeGlyphKey,
) -> NativeRasterGlyph? {
  self.engine.raster_glyph(key)
}

///|
pub fn NativeTextCache::register_font_data(
  self : NativeTextCache,
  family : @core.FontFamily,
  data : Bytes,
) -> Unit {
  self.engine.register_font_data(family, data)
}

///|
fn NativeTextEngine::measure(
  self : NativeTextEngine,
  input : @core.TextLayoutInput,
) -> @core.TextLayoutResult {
  match (self.provider.measure)(input) {
    Some(layout) =>
      if layout.is_valid_for_measure(input.text) {
        layout.to_text_layout_result()
      } else {
        self.measure_with_fallback(input)
      }
    None => self.measure_with_fallback(input)
  }
}

///|
fn NativeTextEngine::measure_with_fallback(
  self : NativeTextEngine,
  input : @core.TextLayoutInput,
) -> @core.TextLayoutResult {
  match self.fallback {
    Some(fallback) =>
      match (fallback.measure)(input) {
        Some(layout) =>
          if layout.is_valid_for_measure(input.text) {
            layout.to_text_layout_result()
          } else {
            @core.TextSystem::fallback().measure_text(input)
          }
        None => @core.TextSystem::fallback().measure_text(input)
      }
    None => @core.TextSystem::fallback().measure_text(input)
  }
}

///|
fn NativeTextEngine::layout_run(
  self : NativeTextEngine,
  run : @core.TextRun,
  scale_factor : Double,
) -> NativeTextLayout? {
  match (self.provider.layout_run)(run, scale_factor) {
    Some(layout) if layout.is_valid_for_run(run.text) => Some(layout)
    _ => self.fallback_layout_run(run, scale_factor)
  }
}

///|
fn NativeTextEngine::fallback_layout_run(
  self : NativeTextEngine,
  run : @core.TextRun,
  scale_factor : Double,
) -> NativeTextLayout? {
  match self.fallback {
    Some(fallback) =>
      match (fallback.layout_run)(run, scale_factor) {
        Some(layout) =>
          if layout.is_valid_for_run(run.text) {
            Some(layout)
          } else {
            None
          }
        None => None
      }
    None => None
  }
}

///|
fn NativeTextEngine::raster_glyph(
  self : NativeTextEngine,
  key : NativeGlyphKey,
) -> NativeRasterGlyph? {
  match (self.provider.raster_glyph)(key.cache_key, key.payload) {
    Some(glyph) if glyph.is_valid() => Some(glyph)
    _ => self.fallback_raster_glyph(key)
  }
}

///|
fn NativeTextEngine::fallback_raster_glyph(
  self : NativeTextEngine,
  key : NativeGlyphKey,
) -> NativeRasterGlyph? {
  match self.fallback {
    Some(fallback) =>
      match (fallback.raster_glyph)(key.cache_key, key.payload) {
        Some(glyph) => if glyph.is_valid() { Some(glyph) } else { None }
        None => None
      }
    None => None
  }
}

///|
fn NativeTextEngine::register_font_data(
  self : NativeTextEngine,
  family : @core.FontFamily,
  data : Bytes,
) -> Unit {
  (self.provider.register_font_data)(family, data)
  match self.fallback {
    Some(fallback) => (fallback.register_font_data)(family, data)
    None => ()
  }
}

///|
pub fn native_text_system() -> @core.TextSystem {
  native_text_system_with_cache(NativeTextCache::new())
}

///|
pub fn native_text_system_with_engine(
  engine : NativeTextEngine,
) -> @core.TextSystem {
  native_text_system_with_cache(NativeTextCache::new_with_engine(engine))
}

///|
pub fn native_text_system_with_cache(
  cache : NativeTextCache,
) -> @core.TextSystem {
  @core.TextSystem::new(
    id=cache.engine.id(),
    measure=input => cache.measure(input),
    register_font_data=(family, data) => cache.register_font_data(family, data),
  )
}