///|
/// `mctl model cache`: the cache-aside layer in front of a table's CRUD — read
/// through on `find`, invalidate on `write` and on `evict`.
///
/// The port is generic, so a cache holds the record itself rather than the text it
/// serialises to, and an in-memory cache is a `Map`: running this example drives the
/// generated layer against one, which is what a test does and what a small service
/// may do in production.
///
///     moon run src/examples/17-cache
#coverage.skip
fn main raise {
  let spec = @moonctl.parse(
    (
      #|service catalog {
      #|  @handler ping
      #|  get /ping
      #|}
      #|
      #|type Product {
      #|  id: int64 `json:"id"`
      #|  name: string `json:"name"`
      #|}
    ),
  )
  let layer = @moonctl.generate_cached(spec)
  println("cache layer: " + layer.length().to_string() + " bytes")
  println("file: " + @moonctl.cache_file_name(spec.service))
  println("")
  for
    anchor in [
      "pub(all) struct Cache[T] {", "pub fn[T] memory_cache() -> Cache[T] {", "pub fn product_cache_key(",
      "pub fn product_find_cached(",
    ] {
    println("---- " + anchor)
    println(excerpt(layer, anchor, 9))
  }
  println("---- what the layer promises")
  println(
    "  a failed cache read falls back to the table: " +
    (at_of(layer, "catch { _ => None }") >= 0).to_string(),
  )
  println(
    "  a write drops the cached copy:            " +
    (at_of(layer, "(cache.del)(") >= 0).to_string(),
  )
  println(
    "  evict does the same for a delete:         " +
    (at_of(layer, "remove(id)") >= 0).to_string(),
  )
}

///|
/// The first index in `hay` where `needle` starts, or `-1`.
fn at_of(hay : String, needle : String) -> Int32 {
  let hn = hay.length()
  let nn = needle.length()
  if nn == 0 {
    return 0
  }
  for i = 0; i + nn <= hn; i = i + 1 {
    let mut same = true
    for j = 0; j < nn; j = j + 1 {
      if hay[i + j] != needle[j] {
        same = false
        break
      }
    }
    if same {
      return i
    }
  }
  -1
}

///|
/// The `lines` lines of `text` that follow the first occurrence of `anchor`.
fn excerpt(text : String, anchor : String, lines : Int32) -> String {
  let at = at_of(text, anchor)
  if at < 0 {
    return anchor + ": not found"
  }
  let mut end = at
  let mut seen = 0
  while end < text.length() && seen < lines {
    if text[end] == '\n' {
      seen = seen + 1
    }
    end = end + 1
  }
  text[at:end].to_owned()
}