// GeoJSON 导入:RFC 7946 FeatureCollection → KML 文档。
//
// 支持的几何:Point / LineString / Polygon / MultiPoint / MultiLineString /
// MultiPolygon;Multi* 映射为 KML 的 MultiGeometry。

///|
/// 取 Json 对象。
fn json_as_object(value : Json) -> Map[String, Json]? {
  match value {
    Json::Object(m) => Some(m)
    _ => None
  }
}

///|
/// 取 Json 数组。
fn json_as_array(value : Json) -> Array[Json]? {
  match value {
    Json::Array(a) => Some(a)
    _ => None
  }
}

///|
/// 取 Json 字符串。
fn json_as_string(value : Json) -> String? {
  match value {
    Json::String(s) => Some(s)
    _ => None
  }
}

///|
/// 取 Json 数字。
fn json_as_number(value : Json) -> Double? {
  match value {
    Json::Number(n, ..) => Some(n)
    _ => None
  }
}

///|
/// 从 Json 对象里取字段。
fn json_field(obj : Map[String, Json], key : String) -> Json? {
  obj.get(key)
}

///|
/// 解析一个 GeoJSON position([lon, lat] 或 [lon, lat, alt])。
fn position_from_json(value : Json) -> KmlPoint raise KmlError {
  match json_as_array(value) {
    None => raise BadGeoJson("position is not an array")
    Some(arr) => {
      if arr.length() < 2 {
        raise BadGeoJson("position needs at least 2 numbers")
      }
      let lon = json_as_number(arr[0])
      let lat = json_as_number(arr[1])
      let alt = if arr.length() >= 3 { json_as_number(arr[2]) } else { None }
      match (lon, lat) {
        (Some(lon), Some(lat)) => { lon, lat, alt, }
        _ => raise BadGeoJson("position is not numeric")
      }
    }
  }
}

///|
/// 解析一串 position。
fn positions_from_json(value : Json) -> Array[KmlPoint] raise KmlError {
  match json_as_array(value) {
    None => raise BadGeoJson("coordinates is not an array")
    Some(arr) => {
      let out : Array[KmlPoint] = []
      let mut i = 0
      while i < arr.length() {
        out.push(position_from_json(arr[i]))
        i = i + 1
      }
      out
    }
  }
}

///|
/// 解析 GeoJSON 几何成 KML 几何。
fn geometry_from_json(value : Json) -> Geometry? raise KmlError {
  match json_as_object(value) {
    None => raise BadGeoJson("geometry is not an object")
    Some(obj) => {
      let kind = match json_field(obj, "type") {
        Some(t) =>
          match json_as_string(t) {
            Some(s) => s
            None => raise BadGeoJson("geometry type is not a string")
          }
        None => raise BadGeoJson("geometry has no type")
      }
      let coords = match json_field(obj, "coordinates") {
        Some(c) => c
        None => raise BadGeoJson("geometry has no coordinates")
      }
      if kind == "Point" {
        Some(Geometry::Point(position_from_json(coords)))
      } else if kind == "LineString" {
        Some(Geometry::LineString({ points: positions_from_json(coords), }))
      } else if kind == "Polygon" {
        Some(Geometry::Polygon(polygon_from_json(coords)))
      } else if kind == "MultiPoint" {
        let pts = positions_from_json(coords)
        let parts : Array[Geometry] = []
        let mut i = 0
        while i < pts.length() {
          parts.push(Geometry::Point(pts[i]))
          i = i + 1
        }
        Some(Geometry::MultiGeometry(parts))
      } else if kind == "MultiLineString" {
        let lines = json_as_array(coords)
        match lines {
          None => raise BadGeoJson("MultiLineString coordinates invalid")
          Some(ls) => {
            let parts : Array[Geometry] = []
            let mut i = 0
            while i < ls.length() {
              parts.push(
                Geometry::LineString({ points: positions_from_json(ls[i]), }),
              )
              i = i + 1
            }
            Some(Geometry::MultiGeometry(parts))
          }
        }
      } else if kind == "MultiPolygon" {
        let polys = json_as_array(coords)
        match polys {
          None => raise BadGeoJson("MultiPolygon coordinates invalid")
          Some(ps) => {
            let parts : Array[Geometry] = []
            let mut i = 0
            while i < ps.length() {
              parts.push(Geometry::Polygon(polygon_from_json(ps[i])))
              i = i + 1
            }
            Some(Geometry::MultiGeometry(parts))
          }
        }
      } else {
        None
      }
    }
  }
}

///|
/// 解析 GeoJSON Polygon 的 coordinates:第一个环是外环,其余是内环。
fn polygon_from_json(value : Json) -> KmlPolygon raise KmlError {
  match json_as_array(value) {
    None => raise BadGeoJson("Polygon coordinates is not an array")
    Some(rings) => {
      if rings.length() == 0 {
        raise BadGeoJson("Polygon needs at least one ring")
      }
      let outer : LinearRing = { points: positions_from_json(rings[0]), }
      let inner : Array[LinearRing] = []
      let mut i = 1
      while i < rings.length() {
        inner.push({ points: positions_from_json(rings[i]), })
        i = i + 1
      }
      { outer, inner, }
    }
  }
}

///|
/// 解析一个 Feature。
fn placemark_from_feature(value : Json) -> Placemark raise KmlError {
  match json_as_object(value) {
    None => raise BadGeoJson("feature is not an object")
    Some(obj) => {
      let mut name = ""
      let mut description = ""
      match json_field(obj, "properties") {
        Some(props) =>
          match json_as_object(props) {
            Some(p) => {
              match json_field(p, "name") {
                Some(n) =>
                  match json_as_string(n) {
                    Some(s) => name = s
                    None => ()
                  }
                None => ()
              }
              match json_field(p, "description") {
                Some(d) =>
                  match json_as_string(d) {
                    Some(s) => description = s
                    None => ()
                  }
                None => ()
              }
            }
            None => ()
          }
        None => ()
      }
      let geometry = match json_field(obj, "geometry") {
        Some(g) => geometry_from_json(g)
        None => None
      }
      {
        name,
        description,
        geometry,
        style_url: "",
        extended_data: [],
        folder: "",
        timestamp: None,
      }
    }
  }
}

///|
/// 从 GeoJSON 文本导入 KML 文档。
pub fn from_geojson(text : String) -> KmlDocument raise KmlError {
  let json = @json.parse(text) catch { _ => raise BadGeoJson("invalid JSON") }
  let obj = match json_as_object(json) {
    Some(o) => o
    None => raise BadGeoJson("root is not an object")
  }
  let kind = match json_field(obj, "type") {
    Some(t) =>
      match json_as_string(t) {
        Some(s) => s
        None => raise BadGeoJson("type is not a string")
      }
    None => raise BadGeoJson("root has no type")
  }
  let placemarks : Array[Placemark] = []
  if kind == "FeatureCollection" {
    match json_field(obj, "features") {
      Some(f) =>
        match json_as_array(f) {
          Some(features) => {
            let mut i = 0
            while i < features.length() {
              placemarks.push(placemark_from_feature(features[i]))
              i = i + 1
            }
          }
          None => raise BadGeoJson("features is not an array")
        }
      None => raise BadGeoJson("FeatureCollection has no features")
    }
  } else if kind == "Feature" {
    placemarks.push(placemark_from_feature(json))
  } else {
    let geometry = geometry_from_json(json)
    placemarks.push({
      name: "",
      description: "",
      geometry,
      style_url: "",
      extended_data: [],
      folder: "",
      timestamp: None,
    })
  }
  { name: "", placemarks, styles: [], style_maps: [], }
}