///|
fn format_reg_list(regs : Array[Reg]) -> String {
  regs.map(fn(reg) { reg.to_string() }).join(", ")
}

///|
fn format_operand(operand : Operand) -> String {
  let role = match operand.role {
    Use => "use"
    Def => "def"
    UseDef => "use_def"
  }
  "\{role} \{operand.reg}"
}

///|
fn format_instruction(inst : Instruction) -> String {
  let operands = inst.operands.map(format_operand).join(", ")
  if operands.length() == 0 {
    "    i\{inst.id}: \{to_repr(inst.opcode)}"
  } else {
    "    i\{inst.id}: \{to_repr(inst.opcode)} \{operands}"
  }
}

///|
fn format_target_args(target : Int, args : Array[Reg]) -> String {
  if args.length() == 0 {
    "block\{target}"
  } else {
    "block\{target}(\{format_reg_list(args)})"
  }
}

///|
fn format_terminator(term : Terminator) -> String {
  match term {
    TermJump(target, args) => "    jump \{format_target_args(target, args)}"
    TermBranch(cond, true_target, true_args, false_target, false_args) =>
      "    branch \{cond}, \{format_target_args(true_target, true_args)}, \{format_target_args(false_target, false_args)}"
    TermBranchCmp(
      lhs,
      rhs,
      cond,
      _,
      true_target,
      true_args,
      false_target,
      false_args
    ) => {
      let cond_text = to_repr(cond).to_string()
      "    branch_cmp \{lhs}, \{rhs}, \{cond_text}, \{format_target_args(true_target, true_args)}, \{format_target_args(false_target, false_args)}"
    }
    TermBranchZero(
      reg,
      is_nonzero,
      _,
      true_target,
      true_args,
      false_target,
      false_args
    ) =>
      "    branch_zero \{reg}, nonzero=\{is_nonzero}, \{format_target_args(true_target, true_args)}, \{format_target_args(false_target, false_args)}"
    TermBranchCmpImm(
      reg,
      imm,
      cond,
      _,
      true_target,
      true_args,
      false_target,
      false_args
    ) => {
      let cond_text = to_repr(cond).to_string()
      "    branch_cmp_imm \{reg}, \{imm}, \{cond_text}, \{format_target_args(true_target, true_args)}, \{format_target_args(false_target, false_args)}"
    }
    TermReturn(values) => "    return \{format_reg_list(values)}"
    TermTrap(payload) => "    trap #\{payload}"
    TermBrTable(index, targets, default_target) => {
      let targets_text = targets.map(fn(t) { t.to_string() }).join(", ")
      "    br_table \{index}, [\{targets_text}], block\{default_target}"
    }
  }
}

///|
fn format_block_header(block : Block) -> String {
  if block.params.length() == 0 {
    "block\{block.id}:"
  } else {
    let params = block.params.map(fn(param) { param.to_string() }).join(", ")
    "block\{block.id}(\{params}):"
  }
}

///|
pub fn AbstractFunction::print(self : AbstractFunction) -> String {
  let lines : Array[String] = []
  let params = self.params.map(fn(param) { param.to_string() }).join(", ")
  let results = self.results.map(fn(class) { class.to_string() }).join(", ")
  if results.length() == 0 {
    lines.push("function \{self.name}(\{params}) {")
  } else {
    lines.push("function \{self.name}(\{params}) -> (\{results}) {")
  }
  for block in self.blocks {
    lines.push(format_block_header(block))
    for inst in block.instructions {
      lines.push(format_instruction(inst))
    }
    if block.terminator is Some(term) {
      lines.push(format_terminator(term))
    }
  }
  lines.push("}")
  lines.join("\n") + "\n"
}