// xref.ce — cross-reference / call graph
//
// Usage:
//   cell xref <file>                   Full creation tree
//   cell xref --callers <N> <file>     Who creates function [N]?
//   cell xref --callees <N> <file>     What does [N] create/call?
//   cell xref --optimized <file>       Use optimized IR
//   cell xref --dot <file>             DOT graph for graphviz

var shop = use("internal/shop")

var run = function() {
  var filename = null
  var use_optimized = false
  var show_callers = null
  var show_callees = null
  var show_dot = false
  var i = 0
  var compiled = null
  var creates = {}
  var created_by = {}
  var func_names = {}
  var fi = 0
  var func = null
  var fname = null
  var main_name = null
  var creators = null
  var c = null
  var line_info = null
  var children = null
  var ch = null
  var ch_line = null
  var parent_keys = null
  var ki = 0
  var parent_idx = 0
  var ch_list = null
  var ci = 0
  var printed = {}

  while (i < length(args)) {
    if (args[i] == '--callers') {
      i = i + 1
      show_callers = number(args[i])
    } else if (args[i] == '--callees') {
      i = i + 1
      show_callees = number(args[i])
    } else if (args[i] == '--dot') {
      show_dot = true
    } else if (args[i] == '--optimized') {
      use_optimized = true
    } else if (args[i] == '--help' || args[i] == '-h') {
      log.console("Usage: cell xref [--callers <N>] [--callees <N>] [--dot] [--optimized] <file>")
      log.console("")
      log.console("  --callers <N>   Who creates function [N]?")
      log.console("  --callees <N>   What does [N] create/call?")
      log.console("  --dot           Output DOT format for graphviz")
      log.console("  --optimized     Use optimized IR")
      return null
    } else if (!starts_with(args[i], '-')) {
      filename = args[i]
    }
    i = i + 1
  }

  if (!filename) {
    log.console("Usage: cell xref [--callers <N>] [--callees <N>] [--dot] [--optimized] <file>")
    return null
  }

  if (use_optimized) {
    compiled = shop.compile_file(filename)
  } else {
    compiled = shop.mcode_file(filename)
  }

  main_name = compiled.name != null ? compiled.name : "<main>"
  func_names["-1"] = main_name

  var scan_func = function(func, parent_idx) {
    var instrs = func.instructions
    var j = 0
    var instr = null
    var n = 0
    var child_idx = null
    var instr_line = null
    if (instrs == null) return null
    while (j < length(instrs)) {
      instr = instrs[j]
      if (is_array(instr) && instr[0] == "function") {
        n = length(instr)
        child_idx = instr[2]
        instr_line = instr[n - 2]
        if (!creates[text(parent_idx)]) {
          creates[text(parent_idx)] = []
        }
        push(creates[text(parent_idx)], {child: child_idx, line: instr_line})
        if (!created_by[text(child_idx)]) {
          created_by[text(child_idx)] = []
        }
        push(created_by[text(child_idx)], {parent: parent_idx, line: instr_line})
      }
      j = j + 1
    }
    return null
  }

  if (compiled.main != null) {
    scan_func(compiled.main, -1)
  }

  if (compiled.functions != null) {
    fi = 0
    while (fi < length(compiled.functions)) {
      func = compiled.functions[fi]
      fname = func.name != null ? func.name : "<anonymous>"
      func_names[text(fi)] = fname
      scan_func(func, fi)
      fi = fi + 1
    }
  }

  var func_label = function(idx) {
    var name = func_names[text(idx)]
    if (idx == -1) return main_name
    if (name != null) return `[${text(idx)}] ${name}`
    return `[${text(idx)}]`
  }

  var safe_label = function(idx) {
    var name = func_names[text(idx)]
    if (name != null) return replace(name, '"', '\\"')
    if (idx == -1) return main_name
    return `func_${text(idx)}`
  }

  var node_id = function(idx) {
    if (idx == -1) return "main"
    return `f${text(idx)}`
  }

  // --callers mode
  if (show_callers != null) {
    creators = created_by[text(show_callers)]
    log.compile(`\nCallers of ${func_label(show_callers)}:`)
    if (creators == null || length(creators) == 0) {
      log.compile("  (none - may be main or unreferenced)")
    } else {
      i = 0
      while (i < length(creators)) {
        c = creators[i]
        line_info = c.line != null ? ` at line ${text(c.line)}` : ""
        log.compile(`  ${func_label(c.parent)}${line_info}`)
        i = i + 1
      }
    }
    return null
  }

  // --callees mode
  if (show_callees != null) {
    children = creates[text(show_callees)]
    log.compile(`\nCallees of ${func_label(show_callees)}:`)
    if (children == null || length(children) == 0) {
      log.compile("  (none)")
    } else {
      i = 0
      while (i < length(children)) {
        ch = children[i]
        ch_line = ch.line != null ? ` at line ${text(ch.line)}` : ""
        log.compile(`  ${func_label(ch.child)}${ch_line}`)
        i = i + 1
      }
    }
    return null
  }

  // --dot mode
  if (show_dot) {
    log.compile("digraph xref {")
    log.compile("  rankdir=TB;")
    log.compile("  node [shape=box, style=filled, fillcolor=lightyellow];")

    log.compile(`  ${node_id(-1)} [label="${safe_label(-1)}"];`)

    if (compiled.functions != null) {
      fi = 0
      while (fi < length(compiled.functions)) {
        log.compile(`  ${node_id(fi)} [label="${safe_label(fi)}"];`)
        fi = fi + 1
      }
    }

    parent_keys = array(creates)
    ki = 0
    while (ki < length(parent_keys)) {
      parent_idx = number(parent_keys[ki])
      ch_list = creates[parent_keys[ki]]
      ci = 0
      while (ci < length(ch_list)) {
        log.compile(`  ${node_id(parent_idx)} -> ${node_id(ch_list[ci].child)};`)
        ci = ci + 1
      }
      ki = ki + 1
    }

    log.compile("}")
    return null
  }

  // Default: indented tree from main
  var print_tree = function(idx, depth) {
    var indent = ""
    var d = 0
    var children = null
    var ci = 0
    var child = null
    while (d < depth) {
      indent = indent + "  "
      d = d + 1
    }

    log.compile(`${indent}${func_label(idx)}`)

    if (printed[text(idx)]) {
      log.compile(`${indent}  (already shown)`)
      return null
    }
    printed[text(idx)] = true

    children = creates[text(idx)]
    if (children != null) {
      ci = 0
      while (ci < length(children)) {
        child = children[ci]
        print_tree(child.child, depth + 1)
        ci = ci + 1
      }
    }
    return null
  }

  log.compile("")
  print_tree(-1, 0)

  return null
}

run()
$stop()
