// SeamInspector — analyze inlined code seam and duplicates
// Usage: import and call report(text) or analyze_dump_file(path)

using selfhost.shared.common.string_helpers as StringHelpers
using selfhost.shared.common.string_ops as StringOps
using selfhost.shared.common.mini_vm_scan as MiniVmScan

static box SeamInspector {
  _int_to_str(n) { return StringHelpers.int_to_str(n) }
  _str_to_int(s) { return StringHelpers.to_i64(s) }
  _brace_delta_ignoring_strings(text, start, end) {
    @i = start
    @n = end
    @delta = 0
    loop (i < n) {
      @ch = text.substring(i, i+1)
      if ch == "\"" {
        i = i + 1
        loop (i < n) {
          @c = text.substring(i, i+1)
          if c == "\\" { i = i + 2  continue }
          if c == "\"" { i = i + 1  break }
          i = i + 1
        }
        continue
      }
      if ch == "{" { delta = delta + 1 }
      if ch == "}" { delta = delta - 1 }
      i = i + 1
    }
    return delta
  }
  _scan_boxes(text) {
    @i = 0
    @n = text.length()
    @res = new ArrayBox()
    @tok = "static box "
    loop (i < n) {
      @p = StringOps.index_of_from(text, tok, i)
      if p < 0 { break }
      @j = p + tok.length()
      @name = ""
      loop (j < n) {
        @c = text.substring(j, j+1)
        if c == "_" { name = name + c  j = j + 1  continue }
        if c == "0" or c == "1" or c == "2" or c == "3" or c == "4" or c == "5" or c == "6" or c == "7" or c == "8" or c == "9" { name = name + c  j = j + 1  continue }
        if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") { name = name + c  j = j + 1  continue }
        break
      }
      loop (j < n) { @c2 = text.substring(j, j+1) if c2 == "{" { break } j = j + 1 }
      @end = MiniVmScan.find_balanced_object_end(text, j)
      if end < 0 { end = j }
      @obj = {}
      obj.set("name", name)
      obj.set("start", _int_to_str(p))
      obj.set("end", _int_to_str(end))
      res.push(obj)
      i = end + 1
    }
    return res
  }
  _report_duplicate_boxes(text) {
    @boxes = _scan_boxes(text)
    @cnt = {}
    @names = new ArrayBox()
    @i = 0
    loop (i < boxes.length()) {
      @name = boxes.get(i).get("name")
      @cur = cnt.get(name)
      if cur == null { cnt.set(name, "1")  names.push(name) } else { cnt.set(name, _int_to_str(_str_to_int(cur) + 1)) }
      i = i + 1
    }
    @j = 0
    loop (j < names.length()) {
      @k = names.get(j)
      @v = cnt.get(k)
      if _str_to_int(v) > 1 { print("dup_box " + k + " x" + v) }
      j = j + 1
    }
    return 0
  }
  _report_duplicate_functions_in_box(text, box_name) {
    @boxes = _scan_boxes(text)
    @i = 0
    @fnmap = {}
    @fnames = new ArrayBox()
    loop (i < boxes.length()) {
      @b = boxes.get(i)
      if b.get("name") == box_name {
        @s = _str_to_int(b.get("start"))
        @e = _str_to_int(b.get("end"))
        @j = s
        loop (j < e) {
          @k = j
          loop (k < e) {
            @ch = text.substring(k, k+1)
            if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { k = k + 1  continue }
            break
          }
          if k >= e { break }
          @name = ""
          @p = k
          @c0 = text.substring(p, p+1)
          if (c0 >= "A" and c0 <= "Z") or (c0 >= "a" and c0 <= "z") or c0 == "_" {
            loop (p < e) {
              @c = text.substring(p, p+1)
              if (c >= "A" and c <= "Z") or (c >= "a" and c <= "z") or (c >= "0" and c <= "9") or c == "_" { name = name + c  p = p + 1  continue }
              break
            }
            if text.substring(p, p+1) == "(" {
              @d = 0
              @r = p
              loop (r < e) {
                @cc = text.substring(r, r+1)
                if cc == "(" { d = d + 1  r = r + 1  continue }
                if cc == ")" { d = d - 1  r = r + 1  if d <= 0 { break }  continue }
                if cc == "\"" {
                  r = r + 1
                  loop (r < e) {
                    @c2 = text.substring(r, r+1)
                    if c2 == "\\" { r = r + 2  continue }
                    if c2 == "\"" { r = r + 1  break }
                    r = r + 1
                  }
                  continue
                }
                r = r + 1
              }
              loop (r < e) { @ws = text.substring(r, r+1) if ws == " " or ws == "\t" or ws == "\r" or ws == "\n" { r = r + 1  continue } break }
              if r < e and text.substring(r, r+1) == "{" {
                @cur = fnmap.get(name)
                if cur == null { fnmap.set(name, "1")  fnames.push(name) } else { fnmap.set(name, _int_to_str(_str_to_int(cur) + 1)) }
              }
            }
          }
          @nl = StringOps.index_of_from(text, "\n", k+1)
          if nl < 0 || nl > e { break }
          j = nl + 1
        }
        break
      }
      i = i + 1
    }
    @x = 0
    loop (x < fnames.length()) {
      @nm = fnames.get(x)
      @ct = fnmap.get(nm)
      if _str_to_int(ct) > 1 { print("dup_fn " + box_name + "." + nm + " x" + ct) }
      x = x + 1
    }
    return 0
  }
  report(text) {
    @m = StringOps.index_of_from(text, "static box Main {", 0)
    @delta = -9999
    if m > 0 { delta = _brace_delta_ignoring_strings(text, 0, m) }
    print("prelude_brace_delta=" + _int_to_str(delta))
    _report_duplicate_boxes(text)
    _report_duplicate_functions_in_box(text, "MiniVmPrints")
    return 0
  }
  analyze_dump_file(path) {
    @fb = new FileBox()
    @f = fb.open(path)
    if f == null { print("warn: cannot open " + path)  return 0 }
    @text = f.read()
    f.close()
    return me.report(text)
  }
}
