///|
fn quote_dbc(text : String) -> String {
  let out = StringBuilder::new()
  out.write_string("\"")
  for c in text {
    match c {
      '"' => out.write_string("\\\"")
      '\\' => out.write_string("\\\\")
      '\n' => out.write_string("\\n")
      '\r' => out.write_string("\\r")
      '\t' => out.write_string("\\t")
      _ => out.write_char(c)
    }
  }
  out.write_string("\"")
  out.to_string()
}

///|
fn double_text(value : Double) -> String {
  let text = value.to_string()
  if text.contains(".") || text.contains("e") || text.contains("E") {
    text
  } else {
    text + ".0"
  }
}

///|
fn scope_text(scope : Scope) -> String {
  match scope {
    DatabaseScope => ""
    NodeScope => "BU_ "
    MessageScope => "BO_ "
    SignalScope => "SG_ "
  }
}

///|
fn object_text(target : ObjectRef) -> String {
  match target {
    DatabaseRef => ""
    NodeRef(n) => "BU_ \{n} "
    MessageRef(id) => "BO_ \{id} "
    SignalRef(id, n) => "SG_ \{id} \{n} "
  }
}

///|
fn attribute_type_text(kind : AttributeType) -> String {
  match kind {
    IntType(lo, hi) => "INT \{lo} \{hi}"
    HexType(lo, hi) => "HEX \{lo} \{hi}"
    FloatType(lo, hi) => "FLOAT \{double_text(lo)} \{double_text(hi)}"
    StringType => "STRING"
    EnumType(choices) => "ENUM " + choices.map(quote_dbc).join(",")
  }
}

///|
fn attribute_value_text(value : AttributeValue) -> String {
  match value {
    Integer(n) => n.to_string()
    Real(n) => double_text(n)
    Text(t) => quote_dbc(t)
  }
}

///|
/// Deterministic semantic serialization. Reject invalid models rather than losing data.
/// Permissively preserved extensions are written verbatim after structured content.
pub fn write_dbc(db : Database) -> String raise DbcError {
  for d in validate_database(db) {
    if d.severity == Error {
      raise Failure(d)
    }
  }
  let out = StringBuilder::new()
  out.write_string("VERSION \{quote_dbc(db.version)}\n\nNS_ :\n\nBS_:\n\nBU_:")
  for n in db.nodes {
    out.write_string(" \{n.name}")
  }
  out.write_string("\n\n")
  for m in db.messages {
    out.write_string(
      "BO_ \{m.raw_dbc_id} \{m.name}: \{m.length} \{m.transmitter}\n",
    )
    for s in m.signals {
      let mux = match s.multiplex {
        Always => ""
        Multiplexer => " M"
        Branch(n) => " m\{n}"
      }
      let order = if s.byte_order == Intel { 1 } else { 0 }
      let sign = if s.signed { "-" } else { "+" }
      out.write_string(
        " SG_ \{s.name}\{mux} : \{s.start_bit}|\{s.bit_length}@\{order}\{sign} (\{double_text(s.factor)},\{double_text(s.offset)}) [\{double_text(s.minimum)}|\{double_text(s.maximum)}] \{quote_dbc(s.unit)} \{s.receivers.join(",")}\n",
      )
    }
    out.write_string("\n")
  }
  for c in db.comments {
    out.write_string("CM_ \{object_text(c.target)}\{quote_dbc(c.text)};\n")
  }
  for m in db.messages {
    for s in m.signals {
      if s.values.length() == 0 {
        continue
      }
      out.write_string("VAL_ \{m.raw_dbc_id} \{s.name}")
      for v in s.values {
        out.write_string(" \{v.raw} \{quote_dbc(v.label)}")
      }
      out.write_string(";\n")
    }
  }
  for d in db.attribute_definitions {
    out.write_string(
      "BA_DEF_ \{scope_text(d.scope)}\{quote_dbc(d.name)} \{attribute_type_text(d.kind)};\n",
    )
  }
  for a in db.attributes {
    match a.target {
      None =>
        out.write_string(
          "BA_DEF_DEF_ \{quote_dbc(a.name)} \{attribute_value_text(a.value)};\n",
        )
      Some(target) =>
        out.write_string(
          "BA_ \{quote_dbc(a.name)} \{object_text(target)}\{attribute_value_text(a.value)};\n",
        )
    }
  }
  for u in db.unsupported {
    out.write_string(u.text + "\n")
  }
  out.to_string()
}