///|
fn quote_sieve(text : String) -> String {
  let out = StringBuilder()
  out.write_char('"')
  for c in text {
    if c == '"' || c == '\\' {
      out.write_char('\\')
    }
    out.write_char(c)
  }
  out.write_char('"')
  out.to_string()
}

///|
fn format_argument(arg : Argument) -> String {
  match arg {
    Flag(name) => ":" + name
    Literal(value) => quote_sieve(value)
    Quantity(n) => n.to_string()
    Strings(values) => {
      let out = StringBuilder()
      out.write_char('[')
      for i = 0; i < values.length(); i = i + 1 {
        if i > 0 {
          out.write_string(", ")
        }
        out.write_string(quote_sieve(values[i]))
      }
      out.write_char(']')
      out.to_string()
    }
  }
}

///|
fn write_arguments(out : StringBuilder, args : Array[Argument]) -> Unit {
  for arg in args {
    out.write_char(' ')
    out.write_string(format_argument(arg))
  }
}

///|
fn write_test(out : StringBuilder, expr : Test) -> Unit {
  match expr {
    Call(name, args, _) => {
      out.write_string(name)
      write_arguments(out, args)
    }
    Not(child, _) => {
      out.write_string("not ")
      write_test(out, child)
    }
    AnyOf(children, _) => write_test_list(out, "anyof", children)
    AllOf(children, _) => write_test_list(out, "allof", children)
  }
}

///|
fn write_test_list(
  out : StringBuilder,
  name : String,
  children : Array[Test],
) -> Unit {
  out.write_string(name)
  out.write_char('(')
  for i = 0; i < children.length(); i = i + 1 {
    if i > 0 {
      out.write_string(", ")
    }
    write_test(out, children[i])
  }
  out.write_char(')')
}

///|
fn indentation(out : StringBuilder, depth : Int) -> Unit {
  for i = 0; i < depth; i = i + 1 {
    out.write_string("  ")
  }
}

///|
fn write_statements(
  out : StringBuilder,
  statements : Array[Statement],
  depth : Int,
) -> Unit {
  for statement in statements {
    indentation(out, depth)
    match statement {
      Command(name, args, _) => {
        out.write_string(name)
        write_arguments(out, args)
        out.write_string(";\n")
      }
      Branch(branches, otherwise, _) => {
        for i = 0; i < branches.length(); i = i + 1 {
          if i == 0 {
            out.write_string("if ")
          } else {
            out.write_string(" elsif ")
          }
          write_test(out, branches[i].condition)
          out.write_string(" {\n")
          write_statements(out, branches[i].body, depth + 1)
          indentation(out, depth)
          out.write_char('}')
        }
        if !otherwise.is_empty() {
          out.write_string(" else {\n")
          write_statements(out, otherwise, depth + 1)
          indentation(out, depth)
          out.write_char('}')
        }
        out.write_char('\n')
      }
    }
  }
}

///|
pub fn format_script(
  source : String,
  limits? : Limits = Limits::default(),
) -> String raise SieveError {
  let syntax = parse(source, limits~)
  let out = StringBuilder()
  write_statements(out, syntax.statements, 0)
  out.to_string()
}

///|
pub fn Program::canonical_source(self : Program) -> String {
  let out = StringBuilder()
  write_statements(out, self.syntax.statements, 0)
  out.to_string()
}