///|
fn main {
  run() catch {
    error => abort("MoonMVT interoperability bridge failed: \{error}")
  }
}

///|
fn run() -> Unit raise {
  let args = @env.args()
  if args.length() == 1 || args[1] == "export" {
    emit_export_vectors()
    return
  }
  if args.length() != 4 || args[1] != "verify" {
    abort("usage: interop_bridge [export | verify CASE HEX]")
  }
  let bytes = @hex.decode(args[3])
  verify_import(args[2], @codec.decode_tile(bytes))
  println("PASS|python-to-moonbit|" + args[2])
}

///|
fn ensure(condition : Bool, message : String) -> Unit {
  if !condition {
    abort("semantic mismatch: " + message)
  }
}

///|
fn point(x : Int, y : Int) -> @model.Point {
  @model.Point::new(x, y)
}

///|
fn prop(key : String, value : @model.MvtValue) -> @model.Property {
  @builder.property(key, value)
}

///|
fn one_layer(layer : @builder.LayerBuilder) -> @model.Tile raise {
  let tile = @builder.TileBuilder::new()
  tile.add_built_layer(layer)
  tile.finish()
}

///|
fn export_point() -> @model.Tile raise {
  let layer = @builder.LayerBuilder::new("point")
  layer.add_points([point(7, 11)], id=Some(101UL), properties=[
    prop("name", @model.MvtValue::StringValue("moon")),
    prop("ok", @model.MvtValue::BoolValue(true)),
  ])
  one_layer(layer)
}

///|
fn export_multipoint() -> @model.Tile raise {
  let layer = @builder.LayerBuilder::new("multipoint")
  layer.add_points([point(1, 2), point(20, 30), point(-4, 8)], id=Some(102UL))
  one_layer(layer)
}

///|
fn export_line() -> @model.Tile raise {
  let layer = @builder.LayerBuilder::new("line")
  layer.add_lines(
    [[point(0, 0), point(10, 5), point(30, 25)]],
    id=Some(103UL),
    properties=[prop("class", @model.MvtValue::StringValue("primary"))],
  )
  one_layer(layer)
}

///|
fn export_multiline() -> @model.Tile raise {
  let layer = @builder.LayerBuilder::new("multiline")
  layer.add_lines(
    [
      [point(2, 2), point(4, 8)],
      [point(100, 80), point(90, 70), point(120, 60)],
    ],
    id=Some(104UL),
  )
  one_layer(layer)
}

///|
fn export_polygon() -> @model.Tile raise {
  let layer = @builder.LayerBuilder::new("polygon")
  layer.add_polygons(
    [
      [point(0, 0), point(40, 0), point(40, 40), point(0, 40)],
      [point(10, 10), point(10, 30), point(30, 30), point(30, 10)],
    ],
    id=Some(105UL),
  )
  one_layer(layer)
}

///|
fn export_attributes() -> @model.Tile raise {
  let layer = @builder.LayerBuilder::new("attributes", extent=512)
  layer.add_points([point(256, 256)], id=Some(106UL), properties=[
    prop("string", @model.MvtValue::StringValue("MoonMVT")),
    prop("float", @model.MvtValue::FloatValue(1.5)),
    prop("double", @model.MvtValue::DoubleValue(-2.25)),
    prop("int", @model.MvtValue::IntValue(-3L)),
    prop("uint", @model.MvtValue::UIntValue(4UL)),
    prop("sint", @model.MvtValue::SIntValue(-5L)),
    prop("bool", @model.MvtValue::BoolValue(false)),
  ])
  one_layer(layer)
}

///|
fn emit(name : String, tile : @model.Tile) -> Unit raise {
  let bytes = @codec.encode_tile(tile)
  println("MVT|" + name + "|" + @hex.encode(bytes))
}

///|
fn emit_export_vectors() -> Unit raise {
  emit("point", export_point())
  emit("multipoint", export_multipoint())
  emit("line", export_line())
  emit("multiline", export_multiline())
  emit("polygon", export_polygon())
  emit("attributes", export_attributes())
}

///|
fn verify_import(case_name : String, tile : @model.Tile) -> Unit raise {
  match case_name {
    "point" => verify_point(tile)
    "multipoint" => verify_multipoint(tile)
    "line" => verify_line(tile)
    "multiline" => verify_multiline(tile)
    "polygon" => verify_polygon(tile)
    "attributes" => verify_attributes(tile)
    _ => abort("unknown interoperability case: " + case_name)
  }
}

///|
fn only_feature(
  tile : @model.Tile,
  layer_name : String,
) -> (@model.Layer, @model.Feature) {
  ensure(tile.layers.length() == 1, layer_name + " layer count")
  let layer = tile.layers[0]
  ensure(layer.name == layer_name, layer_name + " layer name")
  ensure(layer.version == 2, layer_name + " layer version")
  ensure(layer.features.length() == 1, layer_name + " feature count")
  (layer, layer.features[0])
}

///|
fn verify_point(tile : @model.Tile) -> Unit raise {
  let (layer, feature) = only_feature(tile, "point")
  ensure(feature.id == Some(201UL), "point id")
  ensure(feature.geometry_type == @model.GeomType::Point, "point type")
  let geometry = @geometry.decode_geometry(
    feature.geometry_type,
    feature.geometry,
  )
  ensure(geometry.paths.length() == 1, "point path count")
  ensure(geometry.paths[0].points == [point(7, 11)], "point coordinate")
  ensure(
    layer.property(feature, "name") ==
    Some(@model.MvtValue::StringValue("python")),
    "point string property",
  )
  ensure(
    layer.property(feature, "ok") == Some(@model.MvtValue::BoolValue(true)),
    "point bool property",
  )
}

///|
fn verify_multipoint(tile : @model.Tile) -> Unit raise {
  let (_, feature) = only_feature(tile, "multipoint")
  ensure(feature.geometry_type == @model.GeomType::Point, "multipoint type")
  let geometry = @geometry.decode_geometry(
    feature.geometry_type,
    feature.geometry,
  )
  ensure(
    geometry.paths[0].points == [point(1, 2), point(20, 30), point(40, 8)],
    "multipoint coordinates",
  )
}

///|
fn verify_line(tile : @model.Tile) -> Unit raise {
  let (_, feature) = only_feature(tile, "line")
  ensure(feature.geometry_type == @model.GeomType::LineString, "line type")
  let geometry = @geometry.decode_geometry(
    feature.geometry_type,
    feature.geometry,
  )
  ensure(geometry.paths.length() == 1, "line path count")
  ensure(
    geometry.paths[0].points == [point(0, 0), point(10, 5), point(30, 25)],
    "line coordinates",
  )
}

///|
fn verify_multiline(tile : @model.Tile) -> Unit raise {
  let (_, feature) = only_feature(tile, "multiline")
  ensure(feature.geometry_type == @model.GeomType::LineString, "multiline type")
  let geometry = @geometry.decode_geometry(
    feature.geometry_type,
    feature.geometry,
  )
  ensure(geometry.paths.length() == 2, "multiline path count")
  ensure(
    geometry.paths[0].points == [point(2, 2), point(4, 8)],
    "multiline first path",
  )
  ensure(
    geometry.paths[1].points == [point(100, 80), point(90, 70), point(120, 60)],
    "multiline second path",
  )
}

///|
fn verify_polygon(tile : @model.Tile) -> Unit raise {
  let (_, feature) = only_feature(tile, "polygon")
  ensure(feature.geometry_type == @model.GeomType::Polygon, "polygon type")
  let geometry = @geometry.decode_geometry(
    feature.geometry_type,
    feature.geometry,
  )
  ensure(geometry.paths.length() == 2, "polygon ring count")
  ensure(geometry.paths[0].closed, "polygon outer ring closure")
  ensure(geometry.paths[1].closed, "polygon inner ring closure")
  ensure(geometry.paths[0].points.length() == 4, "polygon outer vertices")
  ensure(geometry.paths[1].points.length() == 4, "polygon inner vertices")
}

///|
fn verify_attributes(tile : @model.Tile) -> Unit raise {
  let (layer, feature) = only_feature(tile, "attributes")
  ensure(layer.extent == 512, "custom extent")
  ensure(feature.id == Some(206UL), "attributes id")
  ensure(
    layer.property(feature, "string") ==
    Some(@model.MvtValue::StringValue("MoonMVT")),
    "string value",
  )
  ensure(
    layer.property(feature, "bool") == Some(@model.MvtValue::BoolValue(false)),
    "bool value",
  )
  ensure(
    layer.property(feature, "positive").map(value => value.display()) ==
    Some("4"),
    "positive integer value",
  )
  ensure(
    layer.property(feature, "negative").map(value => value.display()) ==
    Some("-5"),
    "negative integer value",
  )
  ensure(
    layer.property(feature, "double").map(value => value.display()) ==
    Some("1.5"),
    "double value",
  )
}