///|
fn Ty::source(self : Ty) -> String {
  match self {
    Int => "Int"
    Bool => "Bool"
    Tuple(fields) => "(\{fields.map(ty => ty.source()).join(", ")})"
    Enum(id) => "E\{id}"
  }
}

///|
fn Expr::source(self : Expr) -> String {
  match self {
    Hole(_) => abort("a CompleteProgram cannot contain a hole")
    IntLiteral(value) => if value < 0 { "(\{value})" } else { "\{value}" }
    BoolLiteral(value) => "\{value}"
    Ref(id) => "v\{id}"
    Let(binder, value, body) =>
      "{ let v\{binder.id} : \{binder.ty.source()} = \{value.source()}; \{body.source()} }"
    Add(a, b) => "(\{a.source()} + \{b.source()})"
    Subtract(a, b) => "(\{a.source()} - \{b.source()})"
    Less(a, b) => "(\{a.source()} < \{b.source()})"
    Not(e) => "(!\{e.source()})"
    If(condition, yes, no) =>
      "(if \{condition.source()} { \{yes.source()} } else { \{no.source()} })"
    Call(id, arguments) =>
      "f\{id}(\{arguments.map(e => e.source()).join(", ")})"
    Tuple(fields) => "(\{fields.map(e => e.source()).join(", ")})"
    Project(value, index) => "(\{value.source()}).\{index}"
    Variant(id, tag, arguments) => {
      let name = "E\{id}::C\{tag}"
      if arguments.is_empty() {
        name
      } else {
        "\{name}(\{arguments.map(e => e.source()).join(", ")})"
      }
    }
    Match(id, value, arms) => {
      let branches = []
      for tag, arm in arms {
        let parameters = arm.parameters.map(p => "v\{p.id}").join(", ")
        let pattern = if arm.parameters.is_empty() {
          "E\{id}::C\{tag}"
        } else {
          "E\{id}::C\{tag}(\{parameters})"
        }
        branches.push("\{pattern} => (\{arm.body.source()})")
      }
      "(match \{value.source()} {\n\{branches.join("\n")}\n})"
    }
  }
}

///|
pub fn CompleteProgram::emit(self : CompleteProgram) -> String {
  let lines : Array[String] = []
  for id, definition in self.syntax.enums {
    lines.push("enum E\{id} {")
    for tag, fields in definition.variants {
      let name = if fields.is_empty() {
        "C\{tag}"
      } else {
        "C\{tag}(\{fields.map(ty => ty.source()).join(", ")})"
      }
      lines.push("  \{name}")
    }
    lines.push("}")
    lines.push("")
  }
  for id, function in self.syntax.functions {
    let parameters = function.parameters
      .map(p => "v\{p.id} : \{p.ty.source()}")
      .join(", ")
    lines.push("fn f\{id}(\{parameters}) -> \{function.returns.source()} {")
    lines.push("  \{function.body.source()}")
    lines.push("}")
    lines.push("")
  }
  lines.push("fn main {")
  lines.push("  println(\{self.syntax.result.source()})")
  lines.push("}")
  lines.join("\n") + "\n"
}