// Filters applied via the `|` operator, e.g. `{{ name | upper }}`.
//
// Common filters implemented by the portable core.

///|
/// Uppercase an ASCII string.
fn str_upper(s : String) -> String {
  let chars = s.to_array()
  let out : Array[Char] = []
  for c in chars {
    if c >= 'a' && c <= 'z' {
      match (c.to_int() - 32).to_char() {
        Some(u) => out.push(u)
        None => out.push(c)
      }
    } else {
      out.push(c)
    }
  }
  String::from_iter(out.iter())
}

///|
/// Lowercase an ASCII string.
fn str_lower(s : String) -> String {
  let chars = s.to_array()
  let out : Array[Char] = []
  for c in chars {
    if c >= 'A' && c <= 'Z' {
      match (c.to_int() + 32).to_char() {
        Some(u) => out.push(u)
        None => out.push(c)
      }
    } else {
      out.push(c)
    }
  }
  String::from_iter(out.iter())
}

///|
/// Apply a named filter to a value with already-evaluated arguments.
/// Unknown filters pass the input through unchanged.
pub fn apply_filter(name : String, input : Value, args : Array[Value]) -> Value {
  match name {
    "upper" =>
      match input {
        Str(s) => Str(str_upper(s))
        _ => input
      }
    "lower" =>
      match input {
        Str(s) => Str(str_lower(s))
        _ => input
      }
    "trim" =>
      match input {
        Str(s) => Str(s.trim().to_owned())
        _ => input
      }
    "length" =>
      match input {
        Str(s) => Int(s.char_length())
        Array(a) => Int(a.length())
        Object(o) => Int(o.length())
        _ => Int(0)
      }
    "default" => {
      let missing = match input {
        Null => true
        _ => false
      }
      if missing && args.length() > 0 {
        args[0]
      } else {
        input
      }
    }
    "first" =>
      match input {
        Array(a) => if a.length() > 0 { a[0] } else { Null }
        _ => input
      }
    "last" =>
      match input {
        Array(a) => if a.length() > 0 { a[a.length() - 1] } else { Null }
        _ => input
      }
    "abs" =>
      match input {
        Int(i) => Int(if i < 0 { -i } else { i })
        _ => input
      }
    "reverse" =>
      match input {
        Array(a) => {
          let r : Array[Value] = []
          for k = a.length() - 1; k >= 0; k = k - 1 {
            r.push(a[k])
          }
          Array(r)
        }
        _ => input
      }
    "nth" =>
      match input {
        Array(a) =>
          match args[0] {
            Int(i) => if i >= 0 && i < a.length() { a[i] } else { Null }
            _ => Null
          }
        _ => input
      }
    "int" =>
      match input {
        Int(i) => Int(i)
        Float(f) => Int(f.to_int())
        _ => input
      }
    "round" =>
      match input {
        Float(f) => Float(f.round())
        _ => input
      }
    "keys" =>
      match input {
        Object(o) => {
          let ks : Array[Value] = []
          for entry in o {
            let (k, _) = entry
            ks.push(Str(k))
          }
          Array(ks)
        }
        _ => input
      }
    "values" =>
      match input {
        Object(o) => {
          let vs : Array[Value] = []
          for entry in o {
            let (_, v) = entry
            vs.push(v)
          }
          Array(vs)
        }
        _ => input
      }
    "json_encode" => Str(value_to_json(input))
    "escape_html" | "e" =>
      match input {
        Str(s) => Str(escape_html(s))
        _ => input
      }
    "capitalize" =>
      match input {
        Str(s) => Str(capitalize_str(s))
        _ => input
      }
    "concat" =>
      match input {
        Array(a) =>
          match args[0] {
            Array(b) => {
              let r : Array[Value] = []
              for x in a {
                r.push(x)
              }
              for x in b {
                r.push(x)
              }
              Array(r)
            }
            _ => input
          }
        _ => input
      }
    "unique" =>
      match input {
        Array(a) => {
          let out : Array[Value] = []
          for x in a {
            let mut found = false
            for y in out {
              if value_eq(x, y) {
                found = true
              }
            }
            if !found {
              out.push(x)
            }
          }
          Array(out)
        }
        _ => input
      }
    "slice" =>
      match input {
        Array(a) =>
          match (args[0], args[1]) {
            (Int(s), Int(e)) => {
              let r : Array[Value] = []
              let mut j = s
              while j < e && j < a.length() {
                if j >= 0 {
                  r.push(a[j])
                }
                j = j + 1
              }
              Array(r)
            }
            _ => input
          }
        _ => input
      }
    "replace" =>
      match input {
        Str(s) =>
          match (args[0], args[1]) {
            (Str(old), Str(new)) => Str(replace_str(s, old, new))
            _ => input
          }
        _ => input
      }
    "split" =>
      match input {
        Str(s) =>
          match args[0] {
            Str(sep) => {
              let parts : Array[Value] = []
              let buf = StringBuilder::new()
              let sc = sep.to_array()
              let delim = if sc.length() > 0 { sc[0] } else { ' ' }
              for c in s.to_array() {
                if c == delim {
                  parts.push(Str(buf.to_string()))
                  buf.reset()
                } else {
                  buf.write_char(c)
                }
              }
              parts.push(Str(buf.to_string()))
              Array(parts)
            }
            _ => input
          }
        _ => input
      }
    "join" =>
      match input {
        Array(a) =>
          match args[0] {
            Str(sep) => {
              let parts : Array[String] = []
              for x in a {
                parts.push(render_value(x))
              }
              Str(join_str(parts, sep))
            }
            _ => input
          }
        _ => input
      }
    "sort" =>
      match input {
        Array(a) => {
          let r : Array[Value] = []
          for x in a {
            r.push(x)
          }
          let n = r.length()
          for i = 0; i < n; i = i + 1 {
            for j = 0; j < n - 1 - i; j = j + 1 {
              if value_lt(r[j + 1], r[j]) {
                let tmp = r[j]
                r[j] = r[j + 1]
                r[j + 1] = tmp
              }
            }
          }
          Array(r)
        }
        _ => input
      }
    "title" =>
      match input {
        Str(s) => Str(title_str(s))
        _ => input
      }
    "striptags" =>
      match input {
        Str(s) => Str(striptags_str(s))
        _ => input
      }
    "get" =>
      match (input, args[0]) {
        (Object(_), Str(k)) => lookup(k, input)
        _ => input
      }
    "pairs" =>
      match input {
        Object(o) => {
          let r : Array[Value] = []
          for entry in o {
            let (k, v) = entry
            r.push(array_value([str_value(k), v]))
          }
          Array(r)
        }
        _ => input
      }
    "safe" => input // marks output as pre-escaped (no-op until autoescape lands)
    "as_float" =>
      match input {
        Int(i) => Float(i.to_double())
        Float(f) => Float(f)
        _ => input
      }
    "trim_start" =>
      match input {
        Str(s) => Str(trim_start_str(s))
        _ => input
      }
    "truncate" =>
      match (input, args[0]) {
        (Str(s), Int(n)) => Str(truncate_str(s, n))
        _ => input
      }
    "max" =>
      match input {
        Array(a) => {
          let mut best : Value = Null
          let mut seen = false
          for x in a {
            if !seen {
              best = x
              seen = true
            } else if value_lt(best, x) {
              best = x
            }
          }
          best
        }
        _ => input
      }
    "min" =>
      match input {
        Array(a) => {
          let mut best : Value = Null
          let mut seen = false
          for x in a {
            if !seen {
              best = x
              seen = true
            } else if value_lt(x, best) {
              best = x
            }
          }
          best
        }
        _ => input
      }
    "sum" =>
      match input {
        Array(a) => {
          let mut total = 0
          for x in a {
            match x {
              Int(i) => total = total + i
              _ => ()
            }
          }
          Int(total)
        }
        _ => input
      }
    "lines" =>
      match input {
        Str(s) => {
          let r : Array[Value] = []
          let buf = StringBuilder::new()
          for c in s.to_array() {
            if c == '\n' {
              r.push(Str(buf.to_string()))
              buf.reset()
            } else {
              buf.write_char(c)
            }
          }
          r.push(Str(buf.to_string()))
          Array(r)
        }
        _ => input
      }
    "floor" =>
      match input {
        Float(f) => Float(f.floor())
        _ => input
      }
    "ceil" =>
      match input {
        Float(f) => Float(f.ceil())
        _ => input
      }
    "urlencode" =>
      match input {
        Str(s) => Str(urlencode_str(s))
        _ => input
      }
    "indent" =>
      match (input, args[0]) {
        (Str(s), Int(n)) => Str(indent_str(s, n))
        _ => input
      }
    "repeat" =>
      match (input, args[0]) {
        (Str(s), Int(n)) => Str(repeat_str(s, n))
        _ => input
      }
    "pluralize" =>
      match (input, args[0], args[1]) {
        (Int(n), Str(singular), Str(plural)) =>
          Str(if n == 1 { singular } else { plural })
        _ => input
      }
    "count" =>
      match input {
        Str(s) => Int(s.char_length())
        Array(a) => Int(a.length())
        Object(o) => Int(o.length())
        _ => Int(0)
      }
    "wordcount" =>
      match input {
        Str(s) => Int(wordcount_str(s))
        _ => input
      }
    "spaceless" =>
      match input {
        Str(s) => Str(spaceless_str(s))
        _ => input
      }
    "center" =>
      match (input, args[0]) {
        (Str(s), Int(n)) => Str(center_str(s, n))
        _ => input
      }
    "ljust" =>
      match (input, args[0]) {
        (Str(s), Int(n)) => Str(ljust_str(s, n))
        _ => input
      }
    "rjust" =>
      match (input, args[0]) {
        (Str(s), Int(n)) => Str(rjust_str(s, n))
        _ => input
      }
    "as_str" => Str(render_value(input))
    _ => input // unknown filter: pass through
  }
}

///|
/// Count whitespace-separated words.
fn wordcount_str(s : String) -> Int {
  let mut count = 0
  let mut in_word = false
  for c in s.to_array() {
    if c == ' ' || c == '\t' || c == '\n' {
      in_word = false
    } else if !in_word {
      in_word = true
      count = count + 1
    }
  }
  count
}

///|
/// Remove whitespace between `>` and `<` (HTML spaceless).
fn spaceless_str(s : String) -> String {
  let chars = s.to_array()
  let buf = StringBuilder::new()
  let mut i = 0
  while i < chars.length() {
    let c = chars[i]
    if c == '>' {
      buf.write_char(c)
      let mut j = i + 1
      while j < chars.length() &&
            (chars[j] == ' ' || chars[j] == '\n' || chars[j] == '\t') {
        j = j + 1
      }
      i = j
    } else {
      buf.write_char(c)
      i = i + 1
    }
  }
  buf.to_string()
}

///|
/// Pad `s` with spaces on the right to width `n`.
fn ljust_str(s : String, n : Int) -> String {
  let len = s.char_length()
  if len >= n {
    s
  } else {
    s + repeat_str(" ", n - len)
  }
}

///|
/// Pad `s` with spaces on the left to width `n`.
fn rjust_str(s : String, n : Int) -> String {
  let len = s.char_length()
  if len >= n {
    s
  } else {
    repeat_str(" ", n - len) + s
  }
}

///|
/// Center `s` in a field of width `n`.
fn center_str(s : String, n : Int) -> String {
  let len = s.char_length()
  if len >= n {
    s
  } else {
    let total = n - len
    let left = total / 2
    let right = total - left
    repeat_str(" ", left) + s + repeat_str(" ", right)
  }
}

///|
/// Percent-encode a string for URLs (ASCII subset).
fn urlencode_str(s : String) -> String {
  let buf = StringBuilder::new()
  for c in s.to_array() {
    if (c >= 'a' && c <= 'z') ||
      (c >= 'A' && c <= 'Z') ||
      (c >= '0' && c <= '9') ||
      c == '-' ||
      c == '_' ||
      c == '.' ||
      c == '~' {
      buf.write_char(c)
    } else {
      let hex = "0123456789ABCDEF".to_array()
      let v = c.to_int()
      buf.write_string("%")
      buf.write_char(hex[v / 16])
      buf.write_char(hex[v % 16])
    }
  }
  buf.to_string()
}

///|
/// Indent every line of `s` by `n` spaces (except the first).
fn indent_str(s : String, n : Int) -> String {
  let pad = repeat_str(" ", n)
  let buf = StringBuilder::new()
  let mut first = true
  for c in s.to_array() {
    if first {
      first = false
    }
    if c == '\n' {
      buf.write_char(c)
      buf.write_string(pad)
    } else {
      buf.write_char(c)
    }
  }
  buf.to_string()
}

///|
/// Repeat a string `n` times.
fn repeat_str(s : String, n : Int) -> String {
  let buf = StringBuilder::new()
  for i = 0; i < n; i = i + 1 {
    buf.write_string(s)
  }
  buf.to_string()
}

///|
/// Trim leading whitespace.
fn trim_start_str(s : String) -> String {
  let chars = s.to_array()
  let mut i = 0
  while i < chars.length() && (chars[i] == ' ' || chars[i] == '\t') {
    i = i + 1
  }
  s[i:chars.length()].to_owned()
}

///|
/// Truncate a string to at most `n` characters, appending "..." if cut.
fn truncate_str(s : String, n : Int) -> String {
  let len = s.char_length()
  if len <= n {
    s
  } else {
    s[0:n].to_owned() + "..."
  }
}

///|
/// Title-case each whitespace-separated word (ASCII).
fn title_str(s : String) -> String {
  let chars = s.to_array()
  let out : Array[Char] = []
  let mut prev_space = true
  for c in chars {
    let nc = if prev_space && c >= 'a' && c <= 'z' {
      match (c.to_int() - 32).to_char() {
        Some(u) => u
        None => c
      }
    } else {
      c
    }
    out.push(nc)
    prev_space = c == ' '
  }
  String::from_iter(out.iter())
}

///|
/// Strip HTML-like `<...>` tags from a string.
fn striptags_str(s : String) -> String {
  let buf = StringBuilder::new()
  let mut in_tag = false
  for c in s.to_array() {
    if c == '<' {
      in_tag = true
    } else if c == '>' {
      in_tag = false
    } else if !in_tag {
      buf.write_char(c)
    }
  }
  buf.to_string()
}

///|
/// HTML-escape a string.
fn escape_html(s : String) -> String {
  let buf = StringBuilder::new()
  for c in s.to_array() {
    match c {
      '&' => buf.write_string("&")
      '<' => buf.write_string("<")
      '>' => buf.write_string(">")
      '"' => buf.write_string(""")
      '\'' => buf.write_string("'")
      _ => buf.write_char(c)
    }
  }
  buf.to_string()
}

///|
/// Capitalize the first ASCII character (rest unchanged).
fn capitalize_str(s : String) -> String {
  let chars = s.to_array()
  if chars.length() == 0 {
    return ""
  }
  let out : Array[Char] = []
  for idx = 0; idx < chars.length(); idx = idx + 1 {
    let c = chars[idx]
    if idx == 0 && c >= 'a' && c <= 'z' {
      match (c.to_int() - 32).to_char() {
        Some(u) => out.push(u)
        None => out.push(c)
      }
    } else {
      out.push(c)
    }
  }
  String::from_iter(out.iter())
}

///|
/// Replace all occurrences of `old` with `new` in `s`.
fn replace_str(s : String, old : String, new : String) -> String {
  if old == "" {
    return s
  }
  let chars = s.to_array()
  let oldchars = old.to_array()
  let n = chars.length()
  let m = oldchars.length()
  let buf = StringBuilder::new()
  let mut i = 0
  while i < n {
    let mut matches = true
    if i + m > n {
      matches = false
    }
    let mut k = 0
    while matches && k < m {
      if chars[i + k] != oldchars[k] {
        matches = false
      }
      k = k + 1
    }
    if matches {
      buf.write_string(new)
      i = i + m
    } else {
      buf.write_char(chars[i])
      i = i + 1
    }
  }
  buf.to_string()
}

///|
fn join_str(arr : Array[String], sep : String) -> String {
  let buf = StringBuilder::new()
  for k = 0; k < arr.length(); k = k + 1 {
    if k > 0 {
      buf.write_string(sep)
    }
    buf.write_string(arr[k])
  }
  buf.to_string()
}

///|
/// Render a Value as deterministic JSON.
fn value_to_json(v : Value) -> String {
  v.to_json_string()
}