///|
/// A cleavage model with offsets measured on the displayed recognition strand.
pub struct Enzyme {
  name : String
  motif : String
  top_offset : Int
  bottom_offset : Int
} derive(Eq, Debug, ToJson)

///|
/// Names are report identifiers, not claims about a curated enzyme catalogue.
pub fn Enzyme::new(
  name : String,
  motif : String,
  top_offset : Int,
  bottom_offset : Int,
) -> Enzyme raise RestrictError {
  if name.length() == 0 || name.length() > 64 {
    raise InvalidInput("ENZYME_NAME")
  }
  for c in name.iter() {
    if !((c >= 'A' && c <= 'Z') ||
      (c >= 'a' && c <= 'z') ||
      (c >= '0' && c <= '9') ||
      c == '_' ||
      c == '-') {
      raise InvalidInput("ENZYME_NAME")
    }
  }
  if motif.length() == 0 || motif.length() > 64 {
    raise InvalidInput("MOTIF_LENGTH")
  }
  let dna = Dna::new(motif)
  if top_offset < -64 ||
    top_offset > 128 ||
    bottom_offset < -64 ||
    bottom_offset > 128 {
    raise InvalidInput("CUT_OFFSET")
  }
  { name, motif: dna.sequence(), top_offset, bottom_offset }
}

///|
pub fn Enzyme::name(self : Enzyme) -> String {
  self.name
}

///|
pub fn Enzyme::motif(self : Enzyme) -> String {
  self.motif
}

///|
pub fn Enzyme::offsets(self : Enzyme) -> (Int, Int) {
  (self.top_offset, self.bottom_offset)
}

///|
/// IUPAC reverse complement is delegated to upstream base semantics.
pub fn reverse_complement(dna : Dna) -> Dna {
  let chars = dna.bases.to_array()
  let out = StringBuilder::new()
  for i = chars.length() - 1; i >= 0; i = i - 1 {
    out.write_char(@base.IupacBase::from_char(chars[i]).complement().to_char())
  }
  { bases: out.to_string(), topology: dna.topology }
}