// 命令行入口:把库的能力包成一个人能直接用的工具。
//
//     dxfkit check 图纸.dxf                 # 加工前检查
//     dxfkit check 图纸.dxf --min-gap 0.5   # 带最小净距阈值
//     dxfkit svg 图纸.dxf > 预览.svg         # 画成 SVG
//     dxfkit stats 大图纸.dxf                # 流式统计(整份不进内存)
//
// 退出码是给脚本用的:**0 没致命问题、1 有致命问题、2 用不了**
//(参数不对 / 文件读不了)。普通模式0仍可能有警告;--strict将警告也作为1。
//
// 这一层只做「读文件 → 调库 → 排版输出」——判定逻辑全在库里,
// CLI 只是门面,改门面碰不坏引擎。

///|
async fn main {
  let args = @env.args()
  if args.length() < 2 {
    usage()
    @sys.exit(2)
    return
  }
  match args[1] {
    "help" | "--help" | "-h" => usage()
    "check" => run_check(args[2:])
    "svg" => run_svg(args[2:])
    "stats" => run_stats(args[2:])
    cmd => {
      println("不认识的命令「\{cmd}」")
      usage()
      @sys.exit(2)
    }
  }
}

///|
fn run_check(args : ArrayView[String]) -> Unit {
  let mut path = ""
  let mut tol = 0.05
  let mut min_gap = 0.0
  let mut strict = false
  let mut i = 0
  while i < args.length() {
    if args[i] == "--tol" && i + 1 < args.length() {
      tol = match parse_num(args[i + 1]) {
        Some(value) => value
        None => {
          println("--tol 必须是正的有限数字")
          @sys.exit(2)
          return
        }
      }
      i = i + 2
    } else if args[i] == "--min-gap" && i + 1 < args.length() {
      min_gap = match parse_num(args[i + 1]) {
        Some(value) => value
        None => {
          println("--min-gap 必须是非负有限数字")
          @sys.exit(2)
          return
        }
      }
      i = i + 2
    } else if args[i] == "--strict" {
      strict = true
      i = i + 1
    } else if args[i].has_prefix("--") {
      println("未知选项或缺少参数值「\{args[i]}」")
      @sys.exit(2)
      return
    } else if path == "" {
      path = args[i]
      i = i + 1
    } else {
      println("多余的参数「\{args[i]}」")
      @sys.exit(2)
      return
    }
  }
  if path == "" {
    println("要给一个 DXF 文件:dxfkit check 图纸.dxf")
    @sys.exit(2)
    return
  }
  if tol <= 0.0 || min_gap < 0.0 {
    println("--tol 必须大于0,--min-gap 必须非负")
    @sys.exit(2)
    return
  }
  let scan = match scan_file(path) {
    Some(s) => s
    None => {
      @sys.exit(2)
      return
    }
  }
  let drawing = @dxfkit.build_drawing(scan)
  let gap_note = if min_gap > 0.0 {
    "最小净距 \{min_gap}"
  } else {
    "最小净距 不查(用 --min-gap 指定)"
  }
  println(
    "图纸 \{path} · 单位声明 \{drawing.declared_unit_label()} · 容差 \{tol} · \{gap_note}",
  )
  println(
    "坐标及阈值按图纸原始单位解释;单位声明需由使用者确认。",
  )
  println(
    "────────────────────────────────────────",
  )
  let report = drawing.check({ tol, min_gap, })
  println(report.to_text())
  if report.has_fatal() ||
    (
      strict &&
      (report.has_incomplete_checks() || report.count(@dxfkit.Warn) > 0)
    ) {
    @sys.exit(1)
  } else {
    @sys.exit(0)
  }
}

///|
fn run_svg(args : ArrayView[String]) -> Unit {
  let mut path = ""
  let mut tol = 0.05
  let mut i = 0
  while i < args.length() {
    if args[i] == "--tol" && i + 1 < args.length() {
      tol = match parse_num(args[i + 1]) {
        Some(value) => value
        None => {
          println("--tol 必须是正的有限数字")
          @sys.exit(2)
          return
        }
      }
      i = i + 2
    } else if args[i].has_prefix("--") {
      println("未知选项或缺少参数值「\{args[i]}」")
      @sys.exit(2)
      return
    } else if path == "" {
      path = args[i]
      i = i + 1
    } else {
      println("多余的参数「\{args[i]}」")
      @sys.exit(2)
      return
    }
  }
  if path == "" {
    println("要给一个 DXF 文件:dxfkit svg 图纸.dxf > 预览.svg")
    @sys.exit(2)
    return
  }
  if tol <= 0.0 {
    println("--tol 必须大于0")
    @sys.exit(2)
    return
  }
  let scan = match scan_file(path) {
    Some(s) => s
    None => {
      @sys.exit(2)
      return
    }
  }
  println(@dxfkit.build_drawing(scan).to_svg(tol))
  @sys.exit(0)
}

///|
/// 流式统计:一块一块地读,边读边数——**整个文件不进内存**,
/// 警告、未完成行和统计表仍可能增长。走 `moonbitlang/async/fs`:`moonbitlang/x/fs`
/// 只有「一次读完」,没有带偏移量的读取。
///
/// 只报能流出来算的东西(tag 数、段、版本、编码、警告数);
/// 要看几何走 `check` / `svg`——那两个需要整份图纸,是另一条路。
async fn run_stats(args : ArrayView[String]) -> Unit {
  let mut path = ""
  for a in args {
    if path == "" {
      path = a
    } else {
      println("多余的参数「\{a}」")
      @sys.exit(2)
      return
    }
  }
  if path == "" {
    println("要给一个 DXF 文件:dxfkit stats 图纸.dxf")
    @sys.exit(2)
    return
  }
  let file = @asyncfs.open(path, mode=@asyncfs.Mode::ReadOnly) catch {
    err => {
      println("打不开 \{path}:\{err}")
      @sys.exit(2)
      return
    }
  }
  let size = file.size() catch {
    err => {
      let _ = file.close()
      println("读不了文件大小:\{err}")
      @sys.exit(2)
      return
    }
  }
  let stream = @dxfkit.TagStream::new()
  let tracker = StatsTracker::new()
  // 256 KB 一块:太小系统调用多,太大缓冲区本身占内存——关键是**固定**,
  // 文件再大也不涨
  let chunk = 262144
  let buf : FixedArray[Byte] = FixedArray::make(chunk, b'\x00')
  let mut pos = 0L
  let mut failure : String? = None
  while pos < size && failure is None {
    let remain = size - pos
    let want = if remain < chunk.to_int64() { remain.to_int() } else { chunk }
    let n = file.read_at(buf, position=pos, offset=0, len=want) catch {
      err => {
        failure = Some("读文件失败:\{err}")
        0
      }
    }
    if n <= 0 {
      break
    }
    let part : Array[Byte] = []
    for i = 0; i < n; i = i + 1 {
      part.push(buf[i])
    }
    let tags = stream.feed(Bytes::from_array(part))
    tracker.feed(tags)
    pos = pos + n.to_int64()
  }
  let tail = stream.finish()
  tracker.feed(tail)
  let _ = file.close()
  match failure {
    Some(msg) => {
      println(msg)
      @sys.exit(2)
      return
    }
    None => ()
  }
  let warnings = stream.warnings()
  println("图纸 \{path}")
  println(
    "文件 \{size} 字节,一块 \{chunk} 字节地读(整份不进内存)",
  )
  let ver = tracker.version()
  let ver_note = if ver == "" { "(没有 HEADER)" } else { ver }
  println(
    "编码 \{stream.encoding().unwrap().to_display()} · 版本 \{ver_note}",
  )
  println("tag \{tracker.tag_count()} 个 · 警告 \{warnings.length()} 条")
  // 段里数的是组码 0 记录:HEADER 的变量不是记录,所以它显示 0
  let sb = StringBuilder()
  for s in tracker.sections() {
    if sb.to_string().length() > 0 {
      sb.write_string("  ")
    }
    sb.write_string("\{s.0}(\{s.1})")
  }
  println("段        \{sb}")
  @sys.exit(0)
}

///|
/// `stats` 的流式统计:从连续喂进来的 tag 里摘版本号、段和记录数、总数。
///
/// 只认 tag 序列、不碰文件,所以天然是流式的——喂进来多少统计多少。
struct StatsTracker {
  mut count : Int
  mut ver : String
  /// 当前段名(`ENDSEC` 之后清空)
  mut section : String?
  /// 刚看到 `0 / SECTION`,下一对 `2 / 名字` 是段名
  mut want_section_name : Bool
  sections : Array[(String, Int)]
  /// 刚看到 `9 / $XXX`,下一条是它的值——盯 `$ACADVER`
  mut want_var : String?
}

///|
fn StatsTracker::new() -> StatsTracker {
  {
    count: 0,
    ver: "",
    section: None,
    want_section_name: false,
    sections: [],
    want_var: None,
  }
}

///|
fn StatsTracker::feed(self : StatsTracker, tags : Array[@dxfkit.Tag]) -> Unit {
  for t in tags {
    self.count = self.count + 1
    // $ACADVER:9 是变量名,紧跟的那条是值
    match self.want_var {
      Some(name) => {
        if t.code == 1 && name == "$ACADVER" {
          self.ver = t.value.as_string()
        }
        self.want_var = None
      }
      None => ()
    }
    if t.code == 9 {
      self.want_var = Some(t.value.as_string())
    }
    if t.code == 0 {
      let kind = t.value.as_string()
      if kind == "SECTION" {
        self.section = None
        self.want_section_name = true
      } else if kind == "ENDSEC" {
        self.section = None
      } else {
        // 段里的记录(SECTION / ENDSEC 自己不算)
        self.bump_current()
      }
    } else if t.code == 2 && self.want_section_name {
      self.section = Some(t.value.as_string())
      self.want_section_name = false
      self.sections.push((t.value.as_string(), 0))
    }
  }
}

///|
fn StatsTracker::bump_current(self : StatsTracker) -> Unit {
  match self.section {
    Some(name) => {
      let mut i = 0
      while i < self.sections.length() {
        if self.sections[i].0 == name {
          self.sections[i] = (name, self.sections[i].1 + 1)
        }
        i = i + 1
      }
    }
    None => ()
  }
}

///|
fn StatsTracker::tag_count(self : StatsTracker) -> Int {
  self.count
}

///|
fn StatsTracker::version(self : StatsTracker) -> String {
  self.ver
}

///|
fn StatsTracker::sections(self : StatsTracker) -> Array[(String, Int)] {
  self.sections.copy()
}

///|
/// 读文件并扫描。读不了返回 `None`(问题已经打在屏幕上了)。
fn scan_file(path : String) -> @dxfkit.TagScan? {
  let data = @fs.read_file_to_bytes(path) catch {
    @fs.IOError(msg) => {
      println("读不了 \{path}:\{msg}")
      return None
    }
  }
  Some(@dxfkit.scan_tags(data))
}

///|
/// 命令行数字必须有限,非法值由调用方以参数错误报告。
fn parse_num(s : String) -> Double? {
  let n = @string.parse_double(s) catch { _ => return None }
  if n.is_nan() || n.is_inf() {
    None
  } else {
    Some(n)
  }
}

///|
fn usage() -> Unit {
  println("dxfkit —— DXF 图纸的加工前检查")
  println("")
  println("用法:")
  println("  dxfkit check <图纸.dxf> [--tol 0.05] [--min-gap 0.5] [--strict]")
  println(
    "        加工前检查:轮廓闭合、自相交、重复切割、最小间隙、",
  )
  println(
    "        退化几何、文字未转曲线。有致命问题退出码 1,读不了 2,其余 0",
  )
  println(
    "        --strict 将警告也视为退出码1;0只表示当前检查范围内未触发退出条件",
  )
  println("  dxfkit svg <图纸.dxf> [--tol 0.05]")
  println(
    "        把图纸画成 SVG 打到 stdout(重定向存成文件)",
  )
  println("  dxfkit stats <图纸.dxf>")
  println(
    "        流式统计:tag 数、段、版本、编码;不保存完整文件,警告和未完成行仍占内存",
  )
  println("  dxfkit help")
  println("")
  println(
    "容差和净距都用图纸自己的单位;--min-gap 不给就不查净距",
  )
}