///|
pub fn format_header(header : VcfHeader) -> String {
  let lines : Array[String] = []
  for item in header.meta {
    lines.push(format_meta_line(item))
  }
  lines.push(header.columns.join("\t"))
  lines.join("\n") + "\n"
}

///|
pub fn format_record(record : VcfRecord) -> String {
  let fields : Array[String] = [
    record.chrom,
    record.pos.to_string(),
    format_string_list(record.ids, ';'),
    record.ref_allele,
    format_string_list(record.alt_alleles, ','),
    format_optional_double(record.qual),
    format_string_list(record.filters, ';'),
    format_info(record.info),
  ]
  if record.format_keys.length() > 0 {
    fields.push(record.format_keys.join(":"))
    for sample in record.samples {
      fields.push(format_sample(sample, record.format_keys))
    }
  }
  fields.join("\t")
}

///|
pub fn format_document(document : VcfDocument) -> String {
  let builder = StringBuilder()
  builder.write_string(format_header(document.header))
  for record in document.records {
    builder.write_string(format_record(record))
    builder.write_char('\n')
  }
  builder.to_string()
}

///|
fn format_meta_line(item : MetaLine) -> String {
  match item {
    MetaLine::FileFormat(value) => "##fileformat=\{value}"
    Info(def) =>
      "##INFO="
    Format(def) =>
      "##FORMAT="
    Filter(def) =>
      "##FILTER="
    Contig(def) => {
      let base = "##contig= base + ",length=\{length}"
        None => base
      }
      with_length + format_extra_attrs(def.attributes, ["ID", "length"]) + ">"
    }
    Generic(key~, value~) => "##\{key}=\{value}"
  }
}

///|
fn format_cardinality(cardinality : Cardinality) -> String {
  match cardinality {
    Cardinality::Fixed(n) => n.to_string()
    PerAlt => "A"
    PerAllele => "R"
    PerGenotype => "G"
    Variable => "."
    Unknown(value) => value
  }
}

///|
fn format_value_type(value_type : ValueType) -> String {
  match value_type {
    ValueType::Integer => "Integer"
    Float => "Float"
    Flag => "Flag"
    Character => "Character"
    String => "String"
    Unknown(value) => value
  }
}

///|
fn format_extra_attrs(
  attrs : Array[(String, String)],
  reserved : Array[String],
) -> String {
  let extras : Array[String] = []
  for pair in attrs {
    let (key, value) = pair
    if !contains_string(reserved, key) {
      extras.push(",\{key}=\{value}")
    }
  }
  extras.join("")
}

///|
fn contains_string(values : Array[String], needle : String) -> Bool {
  for value in values {
    if value == needle {
      return true
    }
  }
  false
}

///|
fn format_string_list(values : Array[String], sep : Char) -> String {
  if values.length() == 0 {
    "."
  } else {
    values.join(sep.to_string())
  }
}

///|
fn format_optional_double(value : Double?) -> String {
  match value {
    Some(n) => n.to_string()
    None => "."
  }
}

///|
fn format_info(info : Array[(String, VcfValue)]) -> String {
  if info.length() == 0 {
    "."
  } else {
    let parts : Array[String] = []
    for pair in info {
      let (key, value) = pair
      match value {
        VcfValue::Flag => parts.push(key)
        _ => parts.push("\{key}=\{format_value(value)}")
      }
    }
    parts.join(";")
  }
}

///|
fn format_sample(sample : SampleCall, format_keys : Array[String]) -> String {
  let values : Array[String] = []
  for key in format_keys {
    values.push(
      match find_sample_value(sample, key) {
        Some(value) => format_value(value)
        None => "."
      },
    )
  }
  values.join(":")
}

///|
fn find_sample_value(sample : SampleCall, key : String) -> VcfValue? {
  for pair in sample.values {
    let (item_key, value) = pair
    if item_key == key {
      return Some(value)
    }
  }
  None
}

///|
fn format_value(value : VcfValue) -> String {
  match value {
    VcfValue::Missing => "."
    Flag => ""
    Integer(n) => n.to_string()
    Float(n) => n.to_string()
    String(value) => value
    List(values) => values.map(format_value).join(",")
  }
}