// KML 解析:把 XML 树转成 KmlDocument。
//
// 这一版只认 Document / Folder / Placemark / name / description / Point / coordinates,
// 其它元素一律忽略——KML 允许扩展元素,忽略比报错更符合实际数据。

///|
/// 收集过程中的中间状态。
priv struct Collector {
  mut document_name : String
  placemarks : Array[Placemark]
  styles : Array[KmlStyle]
  style_maps : Array[KmlStyleMap]
}

///|
/// 去掉首尾空白。
fn trim_text(text : String) -> String {
  let data = @utf8.encode(text)
  let mut start = 0
  let mut end = data.length()
  while start < end && is_space(data[start]) {
    start = start + 1
  }
  while end > start && is_space(data[end - 1]) {
    end = end - 1
  }
  @utf8.decode_lossy(data.clamped_view(start~, end~))
}

///|
/// 第一个同名子元素。
fn first_element(elem : XmlElement, name : String) -> XmlElement? {
  elem.child(name)
}

///|
/// 直接子元素的文本(去首尾空白)。
fn child_text(elem : XmlElement, name : String) -> String? {
  match elem.child(name) {
    Some(c) => Some(trim_text(c.text()))
    None => None
  }
}

///|
/// 解析一个十进制浮点数:符号、整数、小数、指数。
fn parse_double(text : String) -> Double? {
  let data = @utf8.encode(text)
  let n = data.length()
  let mut i = 0
  let mut negative = false
  if i < n && (data[i] == b'-' || data[i] == b'+') {
    negative = data[i] == b'-'
    i = i + 1
  }
  let mut mantissa = 0.0
  let mut seen = false
  while i < n && dec_digit(data[i]) >= 0 {
    mantissa = mantissa * 10.0 + digit_value(dec_digit(data[i]))
    seen = true
    i = i + 1
  }
  let mut fraction = 0
  if i < n && data[i] == b'.' {
    i = i + 1
    while i < n && dec_digit(data[i]) >= 0 {
      mantissa = mantissa * 10.0 + digit_value(dec_digit(data[i]))
      fraction = fraction + 1
      seen = true
      i = i + 1
    }
  }
  if !seen {
    return None
  }
  let mut exponent = 0
  if i < n && (data[i] == b'e' || data[i] == b'E') {
    i = i + 1
    let mut exp_negative = false
    if i < n && (data[i] == b'-' || data[i] == b'+') {
      exp_negative = data[i] == b'-'
      i = i + 1
    }
    let mut value = 0
    let mut digits = 0
    while i < n && dec_digit(data[i]) >= 0 {
      value = value * 10 + dec_digit(data[i])
      digits = digits + 1
      i = i + 1
    }
    if digits == 0 {
      return None
    }
    exponent = if exp_negative { -value } else { value }
  }
  if i != n {
    return None
  }
  // 单次除法 / 乘法而不是逐位缩放,避免多次舍入把结果推离最近的双精度值。
  let mut value = mantissa
  if fraction > 0 {
    let scale = pow10(fraction)
    value = value / scale
  }
  if exponent != 0 {
    let scale = pow10(if exponent < 0 { -exponent } else { exponent })
    if exponent > 0 {
      value = value * scale
    } else {
      value = value / scale
    }
  }
  Some(if negative { -value } else { value })
}

///|
/// 10 的 n 次幂(n <= 308),用循环累乘得到最近的双精度值。
fn pow10(n : Int32) -> Double {
  let mut value = 1.0
  let mut k = 0
  while k < n {
    value = value * 10.0
    k = k + 1
  }
  value
}

///|
/// 0..9 到 Double 的映射。
fn digit_value(digit : Int32) -> Double {
  match digit {
    0 => 0.0
    1 => 1.0
    2 => 2.0
    3 => 3.0
    4 => 4.0
    5 => 5.0
    6 => 6.0
    7 => 7.0
    8 => 8.0
    _ => 9.0
  }
}

///|
/// 按逗号切分坐标文本,逐段去空白。
fn split_commas(text : String) -> Array[String] {
  let data = @utf8.encode(text)
  let out : Array[String] = []
  let mut start = 0
  let mut i = 0
  while i < data.length() {
    if data[i] == b',' {
      out.push(trim_text(@utf8.decode_lossy(data.clamped_view(start~, end=i))))
      start = i + 1
    }
    i = i + 1
  }
  out.push(
    trim_text(@utf8.decode_lossy(data.clamped_view(start~, end=data.length()))),
  )
  out
}

///|
/// 按任意空白或换行切分坐标元组文本,逐段去空白。
fn split_whitespace(text : String) -> Array[String] {
  let data = @utf8.encode(text)
  let out : Array[String] = []
  let mut i = 0
  let n = data.length()
  while i < n {
    while i < n && is_space(data[i]) {
      i = i + 1
    }
    if i >= n {
      break
    }
    let start = i
    while i < n && !is_space(data[i]) {
      i = i + 1
    }
    out.push(@utf8.decode_lossy(data.clamped_view(start~, end=i)))
  }
  out
}

///|
/// 解析单个坐标文本「lon,lat[,alt]」。
fn parse_coordinate(text : String) -> KmlPoint raise KmlError {
  let parts = split_commas(text)
  if parts.length() < 2 || parts[0] == "" || parts[1] == "" {
    raise BadCoordinates(text)
  }
  let lon = parse_double(parts[0])
  let lat = parse_double(parts[1])
  let alt = if parts.length() >= 3 && parts[2] != "" {
    parse_double(parts[2])
  } else {
    None
  }
  match (lon, lat) {
    (Some(lon), Some(lat)) => { lon, lat, alt, }
    _ => raise BadCoordinates(text)
  }
}

///|
/// 解析 Placemark 里的 Point 几何;没有 Point 返回 None。
fn parse_point(elem : XmlElement) -> KmlPoint? raise KmlError {
  match first_element(elem, "Point") {
    None => None
    Some(point_elem) =>
      match first_element(point_elem, "coordinates") {
        None => raise MissingCoordinates
        Some(coords) => Some(parse_coordinate(trim_text(coords.text())))
      }
  }
}

///|
/// 解析 Placemark 里的 LineString 几何;没有 LineString 返回 None。
///
/// KML 允许用换行分隔坐标元组、空格分隔分量,两者都按任意空白切分。
fn parse_line_string(elem : XmlElement) -> KmlLineString? raise KmlError {
  match first_element(elem, "LineString") {
    None => None
    Some(ls_elem) =>
      match first_element(ls_elem, "coordinates") {
        None => raise MissingCoordinates
        Some(coords) => {
          let text = trim_text(coords.text())
          let tuples = split_whitespace(text)
          let points : Array[KmlPoint] = []
          let mut i = 0
          while i < tuples.length() {
            if tuples[i] != "" {
              points.push(parse_coordinate(tuples[i]))
            }
            i = i + 1
          }
          if points.length() == 0 {
            raise BadCoordinates(text)
          }
          Some({ points, })
        }
      }
  }
}

///|
/// 解析一个 LinearRing:coordinate 元组序列,首尾应闭合。
fn parse_ring(elem : XmlElement) -> LinearRing raise KmlError {
  match first_element(elem, "coordinates") {
    None => raise MissingCoordinates
    Some(coords) => {
      let text = trim_text(coords.text())
      let tuples = split_whitespace(text)
      let points : Array[KmlPoint] = []
      let mut i = 0
      while i < tuples.length() {
        if tuples[i] != "" {
          points.push(parse_coordinate(tuples[i]))
        }
        i = i + 1
      }
      if points.length() < 3 {
        raise BadCoordinates(text)
      }
      let ring : LinearRing = { points, }
      ring
    }
  }
}

///|
/// 解析一个 Polygon:outerBoundaryIs / innerBoundaryIs 里各一个 LinearRing。
fn parse_polygon(elem : XmlElement) -> KmlPolygon? raise KmlError {
  match first_element(elem, "Polygon") {
    None => None
    Some(poly) =>
      match first_element(poly, "outerBoundaryIs") {
        None => raise BadCoordinates("missing outerBoundaryIs")
        Some(outer_boundary) =>
          match first_element(outer_boundary, "LinearRing") {
            None => raise BadCoordinates("missing outer LinearRing")
            Some(outer_ring) => {
              let inner : Array[LinearRing] = []
              let mut i = 0
              while i < poly.children.length() {
                match poly.children[i] {
                  Element(inner_boundary) =>
                    if inner_boundary.name == "innerBoundaryIs" {
                      match first_element(inner_boundary, "LinearRing") {
                        Some(ring_elem) => inner.push(parse_ring(ring_elem))
                        None =>
                          raise BadCoordinates(
                            "innerBoundaryIs missing LinearRing",
                          )
                      }
                    }
                  Text(_) => ()
                }
                i = i + 1
              }
              Some({ outer: parse_ring(outer_ring), inner, })
            }
          }
      }
  }
}

///|
/// 把一个「几何元素本身」按名字解析成 Geometry。
///
/// 与 parse_point/parse_line_string/parse_polygon 不同:那些从 Placemark
/// 容器里找子元素,这里 elem 自己就是 Point/LineString/Polygon。
fn geometry_from_elem(elem : XmlElement) -> Geometry? raise KmlError {
  if elem.name == "Point" {
    match first_element(elem, "coordinates") {
      None => raise MissingCoordinates
      Some(coords) =>
        Some(Geometry::Point(parse_coordinate(trim_text(coords.text()))))
    }
  } else if elem.name == "LineString" {
    match first_element(elem, "coordinates") {
      None => raise MissingCoordinates
      Some(coords) => {
        let text = trim_text(coords.text())
        let tuples = split_whitespace(text)
        let points : Array[KmlPoint] = []
        let mut i = 0
        while i < tuples.length() {
          if tuples[i] != "" {
            points.push(parse_coordinate(tuples[i]))
          }
          i = i + 1
        }
        if points.length() == 0 {
          raise BadCoordinates(text)
        }
        Some(Geometry::LineString({ points, }))
      }
    }
  } else if elem.name == "Polygon" {
    match parse_polygon_container(elem) {
      Some(poly) => Some(Geometry::Polygon(poly))
      None => None
    }
  } else if elem.name == "gx:Track" {
    let whens : Array[String] = []
    let coords : Array[KmlPoint] = []
    let mut i = 0
    while i < elem.children.length() {
      match elem.children[i] {
        Element(child) =>
          if child.name == "when" {
            whens.push(trim_text(child.text()))
          } else if child.name == "gx:coord" {
            let parts = split_whitespace(trim_text(child.text()))
            if parts.length() >= 2 {
              let lon = parse_double(parts[0])
              let lat = parse_double(parts[1])
              match (lon, lat) {
                (Some(lon), Some(lat)) => coords.push({ lon, lat, alt: None, })
                _ => ()
              }
            }
          }
        Text(_) => ()
      }
      i = i + 1
    }
    Some(Geometry::GxTrack({ whens, coords, }))
  } else {
    None
  }
}

///|
/// 解析一个 Polygon 元素本身(不找子元素,elem 就是 Polygon)。
fn parse_polygon_container(elem : XmlElement) -> KmlPolygon? raise KmlError {
  match first_element(elem, "outerBoundaryIs") {
    None => raise BadCoordinates("missing outerBoundaryIs")
    Some(outer_boundary) =>
      match first_element(outer_boundary, "LinearRing") {
        None => raise BadCoordinates("missing outer LinearRing")
        Some(outer_ring) => {
          let inner : Array[LinearRing] = []
          let mut i = 0
          while i < elem.children.length() {
            match elem.children[i] {
              Element(inner_boundary) =>
                if inner_boundary.name == "innerBoundaryIs" {
                  match first_element(inner_boundary, "LinearRing") {
                    Some(ring_elem) => inner.push(parse_ring(ring_elem))
                    None =>
                      raise BadCoordinates("innerBoundaryIs missing LinearRing")
                  }
                }
              Text(_) => ()
            }
            i = i + 1
          }
          Some({ outer: parse_ring(outer_ring), inner, })
        }
      }
  }
}

///|
/// 解析一条 gx:Track。
///
/// gx:coord 是空格分隔的「lon lat [alt]」,与 when 按出现顺序对应。
fn parse_gx_track(elem : XmlElement) -> KmlGxTrack? raise KmlError {
  match first_element(elem, "gx:Track") {
    None => None
    Some(track) => {
      let whens : Array[String] = []
      let coords : Array[KmlPoint] = []
      let mut i = 0
      while i < track.children.length() {
        match track.children[i] {
          Element(child) =>
            if child.name == "when" {
              whens.push(trim_text(child.text()))
            } else if child.name == "gx:coord" {
              let parts = split_whitespace(trim_text(child.text()))
              if parts.length() < 2 {
                raise BadCoordinates(trim_text(child.text()))
              }
              let lon = parse_double(parts[0])
              let lat = parse_double(parts[1])
              let alt = if parts.length() >= 3 && parts[2] != "" {
                parse_double(parts[2])
              } else {
                None
              }
              match (lon, lat) {
                (Some(lon), Some(lat)) => coords.push({ lon, lat, alt, })
                _ => raise BadCoordinates(trim_text(child.text()))
              }
            }
          Text(_) => ()
        }
        i = i + 1
      }
      Some({ whens, coords, })
    }
  }
}

///|
/// 解析 MultiGeometry:按出现顺序收集子几何。
fn parse_multi_geometry(elem : XmlElement) -> Array[Geometry]? raise KmlError {
  match first_element(elem, "MultiGeometry") {
    None => None
    Some(multi) => {
      let geoms : Array[Geometry] = []
      let mut i = 0
      while i < multi.children.length() {
        match multi.children[i] {
          Element(child) =>
            match geometry_from_elem(child) {
              Some(g) => geoms.push(g)
              None => ()
            }
          Text(_) => ()
        }
        i = i + 1
      }
      Some(geoms)
    }
  }
}

///|
/// 解析 KML 颜色 aabbggrr(十六进制)。
fn parse_color(text : String) -> String? {
  let trimmed = trim_text(text)
  let data = @utf8.encode(trimmed)
  if data.length() != 8 {
    return None
  }
  let mut i = 0
  while i < 8 {
    let d = hex_digit(data[i])
    if d < 0 {
      return None
    }
    i = i + 1
  }
  Some(trimmed)
}

///|
/// 解析一个 Style 元素(id + LineStyle/PolyStyle 的颜色与宽度)。
fn parse_style(elem : XmlElement) -> KmlStyle {
  let id = match first_element(elem, "id") {
    Some(_) => "x"
    None => ""
  }
  // 实际 id 是元素属性,不是子元素
  let real_id = match elem.attr("id") {
    Some(v) => v
    None => id
  }
  let mut line_color : String? = None
  let mut line_width : Double? = None
  let mut fill_color : String? = None
  let mut icon_href : String? = None
  let mut icon_scale : Double? = None
  let mut label_color : String? = None
  let mut label_scale : Double? = None
  match first_element(elem, "LineStyle") {
    Some(ls) => {
      match first_element(ls, "color") {
        Some(c) => line_color = parse_color(c.text())
        None => ()
      }
      match first_element(ls, "width") {
        Some(w) => line_width = parse_double(trim_text(w.text()))
        None => ()
      }
    }
    None => ()
  }
  match first_element(elem, "PolyStyle") {
    Some(ps) =>
      match first_element(ps, "color") {
        Some(c) => fill_color = parse_color(c.text())
        None => ()
      }
    None => ()
  }
  match first_element(elem, "IconStyle") {
    Some(icon) => {
      match first_element(icon, "Icon") {
        Some(icon_el) =>
          match first_element(icon_el, "href") {
            Some(h) => icon_href = Some(trim_text(h.text()))
            None => ()
          }
        None => ()
      }
      match first_element(icon, "scale") {
        Some(s) => icon_scale = parse_double(trim_text(s.text()))
        None => ()
      }
    }
    None => ()
  }
  match first_element(elem, "LabelStyle") {
    Some(label) => {
      match first_element(label, "color") {
        Some(c) => label_color = parse_color(c.text())
        None => ()
      }
      match first_element(label, "scale") {
        Some(s) => label_scale = parse_double(trim_text(s.text()))
        None => ()
      }
    }
    None => ()
  }
  {
    id: real_id,
    line_color,
    line_width,
    fill_color,
    icon_href,
    icon_scale,
    label_color,
    label_scale,
  }
}

///|
/// 解析 ExtendedData 里的 Data 条目。
fn parse_extended_data(elem : XmlElement) -> Array[KmlData] {
  let out : Array[KmlData] = []
  match first_element(elem, "ExtendedData") {
    None => ()
    Some(ext) => {
      let mut i = 0
      while i < ext.children.length() {
        match ext.children[i] {
          Element(data_elem) =>
            if data_elem.name == "Data" {
              let name = match data_elem.attr("name") {
                Some(v) => v
                None => ""
              }
              let value = match first_element(data_elem, "value") {
                Some(v) => trim_text(v.text())
                None => ""
              }
              out.push({ name, value, })
            }
          Text(_) => ()
        }
        i = i + 1
      }
    }
  }
  out
}

///|
/// 解析一个 StyleMap:收集 Pair 里 normal / highlight 的 styleUrl。
fn parse_style_map(elem : XmlElement) -> KmlStyleMap {
  let id = match elem.attr("id") {
    Some(v) => v
    None => ""
  }
  let mut normal = ""
  let mut highlight = ""
  let mut i = 0
  while i < elem.children.length() {
    match elem.children[i] {
      Element(pair) =>
        if pair.name == "Pair" {
          let key = match first_element(pair, "key") {
            Some(k) => trim_text(k.text())
            None => ""
          }
          let url = match first_element(pair, "styleUrl") {
            Some(u) => trim_text(u.text())
            None => ""
          }
          if key == "normal" {
            normal = url
          } else if key == "highlight" {
            highlight = url
          }
        }
      Text(_) => ()
    }
    i = i + 1
  }
  { id, normal, highlight, }
}

///|
/// 解析 TimeStamp / TimeSpan。
fn parse_time(elem : XmlElement) -> KmlTime? {
  match first_element(elem, "TimeStamp") {
    Some(ts) =>
      match first_element(ts, "when") {
        Some(w) => Some({ when: trim_text(w.text()), begin: "", end: "", })
        None => None
      }
    None =>
      match first_element(elem, "TimeSpan") {
        Some(span) => {
          let begin = match first_element(span, "begin") {
            Some(b) => trim_text(b.text())
            None => ""
          }
          let end = match first_element(span, "end") {
            Some(e) => trim_text(e.text())
            None => ""
          }
          Some({ when: "", begin, end, })
        }
        None => None
      }
  }
}

///|
/// 解析一个 Placemark。
fn parse_placemark(
  elem : XmlElement,
  folder : String,
) -> Placemark raise KmlError {
  let name = match child_text(elem, "name") {
    Some(t) => t
    None => ""
  }
  let description = match child_text(elem, "description") {
    Some(t) => t
    None => ""
  }
  let geometry : Geometry? = match parse_point(elem) {
    Some(p) => Some(Geometry::Point(p))
    None =>
      match parse_line_string(elem) {
        Some(ls) => Some(Geometry::LineString(ls))
        None =>
          match parse_polygon(elem) {
            Some(poly) => Some(Geometry::Polygon(poly))
            None =>
              match parse_gx_track(elem) {
                Some(track) => Some(Geometry::GxTrack(track))
                None =>
                  match parse_multi_geometry(elem) {
                    Some(geoms) => Some(Geometry::MultiGeometry(geoms))
                    None => None
                  }
              }
          }
      }
  }
  let style_url = match child_text(elem, "styleUrl") {
    Some(t) => t
    None => ""
  }
  let extended_data = parse_extended_data(elem)
  let timestamp = parse_time(elem)
  { name, description, geometry, style_url, extended_data, folder, timestamp, }
}

///|
/// 递归遍历元素树,收集 Document 名称与 Placemark。
fn walk(
  elem : XmlElement,
  acc : Collector,
  folder : String,
) -> Unit raise KmlError {
  if elem.name == "Document" && acc.document_name == "" {
    match child_text(elem, "name") {
      Some(t) => acc.document_name = t
      None => ()
    }
  }
  if elem.name == "Placemark" {
    acc.placemarks.push(parse_placemark(elem, folder))
  }
  if elem.name == "Style" {
    acc.styles.push(parse_style(elem))
  }
  if elem.name == "StyleMap" {
    acc.style_maps.push(parse_style_map(elem))
  }
  let mut i = 0
  while i < elem.children.length() {
    match elem.children[i] {
      Element(child) => {
        let child_folder = if child.name == "Folder" {
          let fname = match child_text(child, "name") {
            Some(t) => t
            None => ""
          }
          if folder == "" {
            fname
          } else if fname == "" {
            folder
          } else {
            folder + "/" + fname
          }
        } else {
          folder
        }
        walk(child, acc, child_folder)
      }
      Text(_) => ()
    }
    i = i + 1
  }
}

///|
/// 解析 KML 文本。
pub fn parse(text : String) -> KmlDocument raise KmlError {
  let root = parse_xml(text)
  let acc : Collector = {
    document_name: "",
    placemarks: [],
    styles: [],
    style_maps: [],
  }
  walk(root, acc, "")
  {
    name: acc.document_name,
    placemarks: acc.placemarks,
    styles: acc.styles,
    style_maps: acc.style_maps,
  }
}