///|
fn object(
  value : Json,
  allowed : Array[String],
) -> Map[String, Json] raise CalculusError {
  let map = match value {
    Object(m) => m
    _ => raise Invalid("expected an object")
  }
  for key in map.keys() {
    if !allowed.contains(key) {
      raise Invalid("unknown field: \{key}")
    }
  }
  map
}

///|
fn field(map : Map[String, Json], key : String) -> Json raise CalculusError {
  match map.get(key) {
    Some(v) => v
    None => raise Invalid("missing field: \{key}")
  }
}

///|
fn number(value : Json) -> Double raise CalculusError {
  match value {
    Number(x, ..) => {
      finite(x, "JSON number")
      x
    }
    _ => raise Invalid("expected a number")
  }
}

///|
fn text(value : Json) -> String raise CalculusError {
  match value {
    String(s) => s
    _ => raise Invalid("expected a string")
  }
}

///|
fn items(value : Json) -> Array[Json] raise CalculusError {
  match value {
    Array(a) => a
    _ => raise Invalid("expected an array")
  }
}

///|
fn read_arrival(value : Json) -> TokenBucket raise CalculusError {
  let m = object(value, ["burst", "rate"])
  token_bucket(number(field(m, "burst")), number(field(m, "rate")))
}

///|
fn read_service(value : Json) -> RateLatency raise CalculusError {
  let m = object(value, ["capacity", "latency"])
  rate_latency(number(field(m, "capacity")), number(field(m, "latency")))
}

///|
fn read_curve(value : Json) -> Curve raise CalculusError {
  let values = items(value)
  if values.length() > 513 {
    raise Limit("curve horizon exceeds 512 ticks")
  }
  curve(values.map(number))
}

///|
fn read_events(value : Json) -> Array[TrafficEvent] raise CalculusError {
  let values = items(value)
  if values.length() > 512 {
    raise Limit("at most 512 events")
  }
  values.map(v => {
    let m = object(v, ["at", "amount"])
    TrafficEvent::{
      at: number(field(m, "at")),
      amount: number(field(m, "amount")),
    }
  })
}

///|
fn dispatch(value : Json) -> Json raise CalculusError {
  let root = match value {
    Object(m) => m
    _ => raise Invalid("request must be an object")
  }
  let op = text(field(root, "op"))
  match op {
    "bound" => {
      let m = object(value, [
        "op", "arrival", "service", "delay_limit", "buffer_limit",
      ])
      let a = read_arrival(field(m, "arrival"))
      let s = read_service(field(m, "service"))
      match (m.get("delay_limit"), m.get("buffer_limit")) {
        (None, None) => bounds(a, s).to_json()
        (Some(d), Some(b)) => admit(a, s, number(d), number(b)).to_json()
        _ =>
          raise Invalid(
            "delay_limit and buffer_limit must be supplied together",
          )
      }
    }
    "tandem" => {
      let m = object(value, ["op", "arrival", "services"])
      let a = read_arrival(field(m, "arrival"))
      let list = items(field(m, "services"))
      if list.length() > 128 {
        raise Limit("at most 128 services")
      }
      let service = tandem(list.map(read_service))
      { "service": service.to_json(), "metrics": bounds(a, service).to_json() }
    }
    "leftover" => {
      let m = object(value, ["op", "service", "cross", "arrival"])
      let service = leftover(
        read_service(field(m, "service")),
        read_arrival(field(m, "cross")),
      )
      match m.get("arrival") {
        None => service.to_json()
        Some(a) =>
          {
            "service": service.to_json(),
            "metrics": bounds(read_arrival(a), service).to_json(),
          }
      }
    }
    "capacity" => {
      let m = object(value, [
        "op", "arrival", "latency", "delay_limit", "buffer_limit",
      ])
      required_capacity(
        read_arrival(field(m, "arrival")),
        number(field(m, "latency")),
        number(field(m, "delay_limit")),
        number(field(m, "buffer_limit")),
      ).to_json()
    }
    "curve" => {
      let m = object(value, ["op", "operation", "a", "b"])
      let operation = text(field(m, "operation"))
      let a = read_curve(field(m, "a"))
      if operation == "closure" {
        if m.contains("b") {
          raise Invalid("closure takes only a")
        }
        Json(subadditive_closure(a).samples())
      } else {
        let b = read_curve(field(m, "b"))
        match operation {
          "convolve" => Json(convolve(a, b).samples())
          "sum" => Json(curve_sum(a, b).samples())
          "min" => Json(curve_min(a, b).samples())
          "max" => Json(curve_max(a, b).samples())
          "deconvolve" => window_deconvolve(a, b).to_json()
          "bounds" => window_bounds(a, b).to_json()
          _ => raise Invalid("unknown curve operation")
        }
      }
    }
    "trace" => {
      let m = object(value, [
        "op", "events", "contract", "fit_rate", "windows", "tolerance",
      ])
      let events = read_events(field(m, "events"))
      let contract = read_arrival(field(m, "contract"))
      let tolerance = match m.get("tolerance") {
        None => 1.0e-9
        Some(v) => number(v)
      }
      let rate = match m.get("fit_rate") {
        None => contract.rate
        Some(v) => number(v)
      }
      let windows = match m.get("windows") {
        None => [0.0, 1.0, 10.0]
        Some(v) => {
          let values = items(v)
          if values.length() > 128 {
            raise Limit("at most 128 windows")
          }
          values.map(number)
        }
      }
      {
        "contract": check_contract(events, contract, tolerance~).to_json(),
        "fit": fit_burst(events, rate).to_json(),
        "empirical_envelope": Json(empirical_envelope(events, windows)),
      }
    }
    "network" => {
      let m = object(value, ["op", "servers", "flows"])
      let raw_servers = items(field(m, "servers"))
      let raw_flows = items(field(m, "flows"))
      if raw_servers.length() > 64 || raw_flows.length() > 64 {
        raise Limit("network is limited to 64 servers and 64 flows")
      }
      let servers = raw_servers.map(v => {
        let s = object(v, ["id", "service"])
        Server::{
          id: text(field(s, "id")),
          service: read_service(field(s, "service")),
        }
      })
      let flows = raw_flows.map(v => {
        let f = object(v, ["id", "arrival", "route"])
        let route = items(field(f, "route"))
        if route.length() > 64 {
          raise Limit("at most 64 hops per flow")
        }
        Flow::{
          id: text(field(f, "id")),
          arrival: read_arrival(field(f, "arrival")),
          route: route.map(text),
        }
      })
      analyze_network(servers, flows).to_json()
    }
    _ => raise Invalid("unknown operation: \{op}")
  }
}

///|
/// Called only after the standard parser has validated the syntax. Scans keys
/// with their decoded spelling so duplicate and escaped-duplicate keys fail.
fn reject_duplicate_keys(input : String) -> Unit raise CalculusError {
  let stack : Array[Map[String, Bool]] = []
  let mut i = 0
  while i < input.length() {
    let c = input[i]
    if c == '{' || c == '[' {
      stack.push(Map([]))
      if stack.length() > 32 {
        raise Limit("JSON depth exceeds 32")
      }
      i = i + 1
    } else if c == '}' || c == ']' {
      ignore(stack.pop())
      i = i + 1
    } else if c == '"' {
      let start = i
      i = i + 1
      while i < input.length() && input[i] != '"' {
        if input[i] == '\\' {
          i = i + 2
        } else {
          i = i + 1
        }
      }
      i = i + 1
      let end = i
      while i < input.length() &&
            (
              input[i] == ' ' ||
              input[i] == '\n' ||
              input[i] == '\r' ||
              input[i] == '\t'
            ) {
        i = i + 1
      }
      if i < input.length() && input[i] == ':' {
        let key_json = @json.parse(input[start:end]) catch {
          _ => raise Invalid("invalid object key")
        }
        let decoded = match key_json {
          String(s) => s
          _ => raise Invalid("invalid object key")
        }
        let keys = stack[stack.length() - 1]
        if keys.contains(decoded) {
          raise Invalid("duplicate field: \{decoded}")
        }
        keys[decoded] = true
      }
    } else {
      i = i + 1
    }
  }
}

///|
/// Machine-readable response: {ok:true,result:...} or
/// {ok:false,code:invalid|unbounded|infeasible|limit|numerical,message:...}.
/// JSON is bounded to 1 MiB UTF-16 units and depth 32; duplicate fields fail.
pub fn process_json(input : String) -> String {
  if input.length() > 1048576 {
    let reply : Json = {
      "ok": false,
      "code": "limit",
      "message": "request too large",
    }
    return reply.stringify()
  }
  let parsed = @json.parse(input) catch {
    _ => {
      let reply : Json = {
        "ok": false,
        "code": "invalid",
        "message": "malformed or too deeply nested JSON",
      }
      return reply.stringify()
    }
  }
  let reply : Json = try {
    reject_duplicate_keys(input)
    let result = dispatch(parsed)
    { "ok": true, "result": result }
  } catch {
    error => {
      let (code, message) = match error {
        Invalid(s) => ("invalid", s)
        Unbounded(s) => ("unbounded", s)
        Infeasible(s) => ("infeasible", s)
        Limit(s) => ("limit", s)
        Numerical(s) => ("numerical", s)
      }
      { "ok": false, "code": Json(code), "message": Json(message) }
    }
  }
  reply.stringify()
}