// Ported from topojson-client src/quantize.js, ISC.

///|
fn replace_fields(j : Json, changes : Map[String, Json]) -> Json {
  let out = match j {
    Object(m) => m.copy()
    _ => Map([])
  }
  for k, v in changes {
    out[k] = v
  }
  Json::object(out)
}

///|
fn Quantizer::geometry(self : Quantizer, g : Json) -> Json raise TopoError {
  let out = match kind(g) {
    "Point" =>
      replace_fields(
        g,
        Map([
          (
            "coordinates",
            self.point(array(field(g, "coordinates")).map(number)).to_json(),
          ),
        ]),
      )
    "MultiPoint" =>
      replace_fields(
        g,
        Map([
          (
            "coordinates",
            array(field(g, "coordinates"))
            .map(p => self.point(array(p).map(number)))
            .to_json(),
          ),
        ]),
      )
    "GeometryCollection" =>
      replace_fields(
        g,
        Map([
          (
            "geometries",
            array(field(g, "geometries")).map(c => self.geometry(c)).to_json(),
          ),
        ]),
      )
    _ => g
  }
  out
}

///|
/// Explicit transform variant. Source must not already be quantized.
pub fn Topology::quantize_with(
  self : Topology,
  q : Quantizer,
) -> Topology raise TopoError {
  if field(self.data, "transform") != Json::null() {
    raise Invalid("quantize.already")
  }
  let objects : Map[String, Json] = Map([])
  for name in self.names() {
    objects[name] = q.geometry(self.object(name))
  }
  let arcs = array(field(self.data, "arcs")).map(a => {
    q.arc(array(a).map(p => array(p).map(number)))
  })
  let out : Map[String, Json] = Map([
    ("type", "Topology".to_json()),
    ("objects", Json::object(objects)),
    ("arcs", arcs.to_json()),
    (
      "transform",
      { "scale": q.scale.to_json(), "translate": q.translate.to_json() },
    ),
  ])
  let box = field(self.data, "bbox")
  if box != Json::null() {
    out["bbox"] = box
  }
  read(Json::object(out).stringify())
}

///|
/// Quantize to n grid values per axis (2..1e9); preserves references and metadata.
pub fn Topology::quantize(self : Topology, n : Int) -> Topology raise TopoError {
  if n < 2 || n > 1000000000 {
    raise Invalid("quantize.grid")
  }
  let stored = field(self.data, "bbox")
  let box = if stored == Json::null() {
    match self.bbox() {
      Some(b) => b
      None => raise Invalid("quantize.empty")
    }
  } else {
    let b = array(stored).map(number)
    if b.length() != 4 || b[0] > b[2] || b[1] > b[3] {
      raise Invalid("bbox.invalid")
    }
    for v in array(stored) {
      guard v is Number(x, ..) else { raise Invalid("bbox.number") }
      if !finite(x) {
        raise Invalid("bbox.number")
      }
    }
    b
  }
  let dx = box[2] - box[0]
  let dy = box[3] - box[1]
  let q = quantizer(
    [
      if dx == 0.0 {
        1.0
      } else {
        dx / (n - 1).to_double()
      },
      if dy == 0.0 {
        1.0
      } else {
        dy / (n - 1).to_double()
      },
    ],
    [box[0], box[1]],
  )
  let source : Topology = {
    data: replace_fields(self.data, Map([("bbox", box.to_json())])),
  }
  source.quantize_with(q)
}