///|
fn step_quote(text : String) -> String {
  let output = StringBuilder()
  output.write_char('\'')
  for character in text {
    if character == '\'' {
      output.write_char('\'')
      output.write_char('\'')
    } else {
      output.write_char(character)
    }
  }
  output.write_char('\'')
  output.to_string()
}

///|
fn step_optional_string(value : String?) -> String {
  match value {
    Some(text) => step_quote(text)
    None => "$".to_string()
  }
}

///|
fn step_string_list(values : Array[String]) -> String {
  "(" + values.map(step_quote).join(",") + ")"
}

///|
fn render_value(value : Value) -> String {
  match value {
    Null => "$".to_string()
    Omitted => "*".to_string()
    Integer(number) => number.to_string()
    Real(number) => number.to_string()
    String(text) => step_quote(text)
    Enumeration(text) => "." + text + "."
    Reference(id) => "#" + id.to_string()
    Binary(text) => "\"" + text + "\""
    Typed(name~, value~) => name + "(" + render_value(value) + ")"
    List(values) => "(" + values.map(render_value).join(",") + ")"
  }
}

///|
pub fn Value::to_step_lossless(self : Value) -> String {
  render_value(self)
}

///|
fn render_entity(entity : Entity) -> String {
  "#" +
  entity.id.to_string() +
  "=" +
  entity.name +
  "(" +
  entity.arguments.map(render_value).join(",") +
  ");"
}

///|
fn render_header(header : Header) -> String {
  let output = StringBuilder()
  output <+ "HEADER;\n"
  let description = "FILE_DESCRIPTION(" +
    step_string_list(header.description) +
    ",'2;1');\n"
  output <+ "\{description}"
  let file_name = "FILE_NAME(" +
    step_optional_string(header.file_name) +
    "," +
    step_optional_string(header.time_stamp) +
    "," +
    step_string_list(header.authors) +
    "," +
    step_string_list(header.organizations) +
    "," +
    step_optional_string(header.preprocessor_version) +
    "," +
    step_optional_string(header.originating_system) +
    "," +
    step_optional_string(header.authorization) +
    ");\n"
  output <+ "\{file_name}"
  let schema = "FILE_SCHEMA(" +
    step_string_list(header.schema_identifiers) +
    ");\n"
  output <+ "\{schema}"
  output <+ "ENDSEC;\n"
  output.to_string()
}

///|
/// Serialize the generic model back to a valid ISO 10303-21 document.
pub fn Model::to_step(self : Model) -> String {
  let output = StringBuilder()
  output <+ "ISO-10303-21;\n"
  let header = render_header(self.header)
  output <+ "\{header}"
  output <+ "DATA;\n"
  for entity in self.entities {
    let rendered = render_entity(entity)
    output <+ "\{rendered}\n"
  }
  output <+ "ENDSEC;\nEND-ISO-10303-21;\n"
  output.to_string()
}

///|
pub fn serialize(model : Model) -> String {
  model.to_step()
}

///|
pub fn round_trip(source : String) -> Model raise ParseError {
  let model = parse(source)
  parse(model.to_step())
}

///|
pub fn step_statistics(source : String) -> StepDocumentStats {
  let mut line_count = 1
  let mut string_count = 0
  let mut comment_count = 0
  let mut in_string = false
  let mut in_comment = false
  for character in source {
    if character == '\n' {
      line_count += 1
      in_comment = false
    }
    if in_comment {
      continue
    }
    if character == '/' && !in_string {
      in_comment = true
      comment_count += 1
    } else if character == '\'' {
      if !in_string {
        in_string = true
        string_count += 1
      } else {
        in_string = false
      }
    }
  }
  let model = try! parse(source)
  let index = model.index()
  {
    byte_length: source.length(),
    line_count,
    entity_count: model.entity_count(),
    reference_count: graph_stats(index).reference_count,
    string_count,
    comment_count,
  }
}