///|
/// Validate every CDS feature that provides a `/translation` qualifier.
pub fn Record::check_cds_translations(self : Record) -> Array[TranslationCheck] {
  let checks : Array[TranslationCheck] = []
  for index, feature in self.features {
    if feature.key == "CDS" {
      match feature.qualifier("translation") {
        Some(expected) => {
          let dna = self.sequence_for(feature)
          let actual = translate_dna(dna)
          checks.push({
            feature_index: index,
            location: feature.location.raw,
            expected,
            actual,
            ok: expected == actual,
          })
        }
        None => ()
      }
    }
  }
  checks
}

///|
/// Extract and orient feature sequence from the parsed ORIGIN.
pub fn Record::sequence_for(self : Record, feature : Feature) -> String {
  let pieces : Array[String] = []
  for segment in feature.location.segments {
    if segment.remote is Some(_) {
      continue
    }
    let range = segment.range
    let start = Int::max(0, range.start - 1)
    let end = Int::min(self.origin.length(), range.end)
    if start < end {
      let piece = self.origin.unsafe_substring(start~, end~)
      pieces.push(
        if segment.reverse {
          reverse_complement(piece)
        } else {
          piece
        },
      )
    }
  }
  pieces.join("")
}

///|
pub fn translate_dna(seq : String) -> String {
  let upper = seq.to_upper().replace_all(old="U", new="T")
  let amino : Array[String] = []
  for i = 0; i + 2 < upper.length(); i = i + 3 {
    let codon = upper.unsafe_substring(start=i, end=i + 3)
    amino.push(codon_to_amino(codon))
  }
  amino.join("")
}

///|
pub fn reverse_complement(seq : String) -> String {
  let out = StringBuilder()
  for ch in seq.rev_iter() {
    out.write_char(complement_base(ch))
  }
  out.to_string()
}

///|
fn complement_base(ch : Char) -> Char {
  match ch {
    'A' | 'a' => 'T'
    'C' | 'c' => 'G'
    'G' | 'g' => 'C'
    'T' | 't' | 'U' | 'u' => 'A'
    'R' | 'r' => 'Y'
    'Y' | 'y' => 'R'
    'S' | 's' => 'S'
    'W' | 'w' => 'W'
    'K' | 'k' => 'M'
    'M' | 'm' => 'K'
    'B' | 'b' => 'V'
    'V' | 'v' => 'B'
    'D' | 'd' => 'H'
    'H' | 'h' => 'D'
    'N' | 'n' => 'N'
    _ => 'N'
  }
}

///|
fn codon_to_amino(codon : String) -> String {
  match codon {
    "TTT" | "TTC" => "F"
    "TTA" | "TTG" | "CTT" | "CTC" | "CTA" | "CTG" => "L"
    "ATT" | "ATC" | "ATA" => "I"
    "ATG" => "M"
    "GTT" | "GTC" | "GTA" | "GTG" => "V"
    "TCT" | "TCC" | "TCA" | "TCG" | "AGT" | "AGC" => "S"
    "CCT" | "CCC" | "CCA" | "CCG" => "P"
    "ACT" | "ACC" | "ACA" | "ACG" => "T"
    "GCT" | "GCC" | "GCA" | "GCG" => "A"
    "TAT" | "TAC" => "Y"
    "TAA" | "TAG" | "TGA" => "*"
    "CAT" | "CAC" => "H"
    "CAA" | "CAG" => "Q"
    "AAT" | "AAC" => "N"
    "AAA" | "AAG" => "K"
    "GAT" | "GAC" => "D"
    "GAA" | "GAG" => "E"
    "TGT" | "TGC" => "C"
    "TGG" => "W"
    "CGT" | "CGC" | "CGA" | "CGG" | "AGA" | "AGG" => "R"
    "GGT" | "GGC" | "GGA" | "GGG" => "G"
    _ => "X"
  }
}