use bio::{
    alignment::distance,
    alphabets::{dna, rna},
    seq_analysis::orf::{self, Orf},
};
use serde::{Deserialize, Serialize};
use std::{fmt, slice::SliceIndex, str};

use crate::{
    data::{ALPHABETS, ALPHABET_MAP, CODON_TABLE, IUPAC_CODON_TABLE, IUPAC_GC_PROBS},
    types::{ByteMap, Case},
};

// TODO: All of the structs and impls in this file need a more logical ordering
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub enum Error {
    FindOrf(Kind),
    InvalidConversion(Kind, Kind),
    InvalidSeq(Vec<(Kind, Alphabet)>),
    ReverseComplement(Kind),
    GcContent(Kind),
    HammingDistance(usize, usize),
    DistanceKindMismatch(Kind, Kind),
}

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub enum Kind {
    Dna,
    Rna,
    Protein,
}

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub enum Alphabet {
    Base,
    N,
    Iupac,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)]
pub struct Seq {
    bytes: Vec<u8>,
    kind: Kind,
    alphabet: Alphabet,
}

impl Seq {
    // ===== "Magic" Constructors ==================================================================

    pub fn new_with_kind(
        seq: impl AsRef<[u8]>,
        kinds: impl AsRef<[Kind]>,
        alphabet: Alphabet,
    ) -> Result<Self, Error> {
        let seq = seq.as_ref();
        let kinds = kinds.as_ref();

        let potential_kinds: Vec<_> = ALPHABETS
            .iter()
            .copied()
            .filter(|(k, a)| kinds.contains(k) && a <= &alphabet)
            .collect();
        let mut candidates: Vec<_> = potential_kinds
            .iter()
            .map(|ka| (ka, &ALPHABET_MAP[ka]))
            .collect();

        // OPTIMISATION: This algorithm will rescan large regions of sequence if the first
        // candidate fails to validate, but the best case (it is the first alphabet — or even the
        // second or third) outperforms a single-pass approach (which involves more comparisons per
        // sequence element). To ameliorate the performance hit from rescanning, the mismatch
        // character is used to filter the candidates before a rescan, ensuring that the next
        // attempted candidate won't get stuck on the same character.
        while let Some((&(kind, alphabet), a)) = candidates.get(0) {
            if let Some(c) = seq
                .iter()
                .copied()
                .find(|&c| !a.symbols.contains(c as usize))
            {
                candidates.retain(|(_, a)| a.symbols.contains(c as usize));
            } else {
                return Ok(Self {
                    bytes: seq.to_vec(),
                    kind,
                    alphabet,
                });
            }
        }

        Err(Error::InvalidSeq(potential_kinds))
    }

    pub fn new(seq: impl AsRef<[u8]>) -> Result<Self, Error> {
        Self::new_with_kind(&seq, [Kind::Dna, Kind::Rna, Kind::Protein], Alphabet::Iupac)
    }

    // ===== Standard Constructors =================================================================

    pub fn dna(seq: impl AsRef<[u8]>) -> Result<Self, Error> {
        Self::new_with_kind(seq, [Kind::Dna], Alphabet::Base)
    }

    pub fn dna_n(seq: impl AsRef<[u8]>) -> Result<Self, Error> {
        Self::new_with_kind(seq, [Kind::Dna], Alphabet::N)
    }

    pub fn dna_iupac(seq: impl AsRef<[u8]>) -> Result<Self, Error> {
        Self::new_with_kind(seq, [Kind::Dna], Alphabet::Iupac)
    }

    pub fn rna(seq: impl AsRef<[u8]>) -> Result<Self, Error> {
        Self::new_with_kind(seq, [Kind::Rna], Alphabet::Base)
    }

    pub fn rna_n(seq: impl AsRef<[u8]>) -> Result<Self, Error> {
        Self::new_with_kind(seq, [Kind::Rna], Alphabet::N)
    }

    pub fn rna_iupac(seq: impl AsRef<[u8]>) -> Result<Self, Error> {
        Self::new_with_kind(seq, [Kind::Rna], Alphabet::Iupac)
    }

    pub fn protein(seq: impl AsRef<[u8]>) -> Result<Self, Error> {
        Self::new_with_kind(seq, [Kind::Protein], Alphabet::Base)
    }

    pub fn protein_iupac(seq: impl AsRef<[u8]>) -> Result<Self, Error> {
        Self::new_with_kind(seq, [Kind::Protein], Alphabet::Iupac)
    }

    // ===== Getters ===============================================================================

    pub fn kind(&self) -> Kind {
        self.kind
    }

    pub fn alphabet(&self) -> Alphabet {
        self.alphabet
    }

    pub fn len(&self) -> usize {
        self.bytes.len()
    }

    pub fn is_empty(&self) -> bool {
        self.bytes.is_empty()
    }

    // ===== Chainable Tools =======================================================================

    // OPTIMISATION: In the future, it may be worth looking into a `SubSeq` type that contains
    // references / slices of the original data — that would help avoid the copying and allocation
    // done by `slice::to_vec` here
    pub fn subseq(&self, range: impl SliceIndex<[u8], Output = [u8]>) -> Self {
        Self::new_with_kind(&self.bytes[range], [self.kind], self.alphabet).unwrap()
    }

    pub fn rev(&self) -> Self {
        Self {
            bytes: self.bytes.iter().copied().rev().collect(),
            ..*self
        }
    }

    pub fn reverse_complement(&self) -> Result<Self, Error> {
        match self.kind {
            Kind::Dna => Ok(Self {
                bytes: dna::revcomp(&self.bytes),
                ..*self
            }),
            Kind::Rna => Ok(Self {
                bytes: rna::revcomp(&self.bytes),
                ..*self
            }),
            Kind::Protein => Err(Error::ReverseComplement(self.kind)),
        }
    }

    pub fn normalize_case(&self, case: Case) -> Self {
        // OPTIMISATION: Pulling the `u8::to_ascii_upper/lowercase` out into a variable, then just
        // writing the whole `map` chain once seems to tank performance — likely by obscuring an
        // optimisation taking place here
        let bytes = match case {
            Case::Lower => self.bytes.iter().map(u8::to_ascii_lowercase).collect(),
            Case::Upper => self.bytes.iter().map(u8::to_ascii_uppercase).collect(),
        };

        Self { bytes, ..*self }
    }

    pub fn convert(&self, kind: Kind) -> Result<Self, Error> {
        match (self.kind, kind) {
            // OPTIMISATION: Using bytestrings and `b + 1` is 103 times faster than converting to a `str`
            // and using `str::replace`. It's also 20.4 times faster than checking case with an `else if`
            // statement and explicitly returning b'U' or b'u'
            (Kind::Dna, Kind::Rna) => Ok(Self {
                bytes: self
                    .bytes
                    .iter()
                    .map(|&b| if b == b'T' || b == b't' { b + 1 } else { b })
                    .collect(),
                kind: Kind::Rna,
                ..*self
            }),
            (Kind::Rna, Kind::Dna) => Ok(Self {
                bytes: self
                    .bytes
                    .iter()
                    .map(|&b| if b == b'U' || b == b'u' { b - 1 } else { b })
                    .collect(),
                kind: Kind::Dna,
                ..*self
            }),
            // OPTIMISATION: Lookups in the IUPAC codon table and rescanning the protein sequence
            // to tighten the alphabet both have a performance hit here. Translating IUPAC
            // nucleotides is ~0.72 times the speed of non-IUPAC sequence
            (Kind::Rna, Kind::Protein) => {
                let bytes = self.normalize_case(Case::Upper);
                let bytes = bytes.bytes.chunks_exact(3);

                let bytes = match self.alphabet {
                    Alphabet::Base => bytes.map(|c| CODON_TABLE[c]).collect(),
                    _ => bytes.map(|c| IUPAC_CODON_TABLE[c]).collect(),
                };

                match self.alphabet {
                    Alphabet::Base => Ok(Self {
                        bytes,
                        kind: Kind::Protein,
                        ..*self
                    }),
                    _ => Seq::protein_iupac(bytes),
                }
            }
            (Kind::Dna, Kind::Protein) => self.convert(Kind::Rna)?.convert(Kind::Protein),
            (from, to) if from == to => Ok(self.clone()),
            (from, to) => Err(Error::InvalidConversion(from, to)),
        }
    }

    // TODO: Add parameters allowing the user to change which start and stop codons are used
    pub fn find_orfs(&self, min_len: usize) -> Result<Vec<(Orf, Self)>, Error> {
        if self.kind == Kind::Protein {
            return Err(Error::FindOrf(self.kind));
        }

        let min_len = min_len * 3;
        let start_codons = vec![b"AUG"];
        // OPTIMISATION: The longer this list, the slower the ORF search, so don't bother looking
        // for IUPAC stop codons when the sequence isn't IUPAC. Looking for IUPAC codons is ~0.89
        // times the speed of the shorter, non-IUPAC codon set
        let stop_codons = if self.alphabet == Alphabet::Iupac {
            vec![b"UGA", b"UAG", b"UAA", b"UAR", b"URA"]
        } else {
            vec![b"UGA", b"UAG", b"UAA"]
        };

        // TODO: `orf::Finder::new()` should be changed to take `AsRef<[&[u8; 3]]>`
        let finder = orf::Finder::new(start_codons, stop_codons, min_len);
        Ok(finder
            .find_all(&self.convert(Kind::Rna)?.normalize_case(Case::Upper).bytes)
            .map(|orf| (orf, self.subseq(orf.start..orf.end)))
            .collect())
    }

    // ===== Terminal Tools ========================================================================

    pub fn gc_content(&self) -> Result<f64, Error> {
        if self.kind == Kind::Protein {
            return Err(Error::GcContent(self.kind));
        }

        // OPTIMISATION: Curiously, using `.normalize_case(Case::Upper)` is faster than directly
        // adding `.map(u8::to_ascii_uppercase)` to the iterator chain
        let seq = self.normalize_case(Case::Upper);

        // OPTIMISATION: Here a simpler version of the code is used for `Alphabet::Base` sequences
        // that is ~1.92 times faster than the version accounting for N and IUPAC sequences
        Ok(match self.alphabet {
            Alphabet::Base => seq
                .bytes
                .iter()
                .filter(|&&b| matches!(b, b'G' | b'C'))
                .count() as f64,
            Alphabet::N | Alphabet::Iupac => {
                let counts = seq.count_elements();
                IUPAC_GC_PROBS
                    .into_iter()
                    .map(|(&k, v)| counts[k] as f64 * v)
                    .sum::<f64>()
            }
        } / self.len() as f64)
    }

    // OPTIMISATION: This code indexing a sparse ByteMap to keep counts is 14.3 times faster than
    // the equivalent (and more canonical) code written using `HashMap` and the `Entry` API
    pub fn count_elements(&self) -> ByteMap<usize> {
        let mut counts = ByteMap::default();
        for &b in &self.bytes {
            counts[b] += 1;
        }
        counts
    }

    pub fn hamming_distance(&self, other: &Self) -> Result<usize, Error> {
        if self.len() != other.len() {
            return Err(Error::HammingDistance(self.len(), other.len()));
        }
        if self.kind != other.kind {
            return Err(Error::DistanceKindMismatch(self.kind, other.kind));
        }

        // OPTIMISATION: Where SIMD is available, it can be many times faster than a non-SIMD
        // implementation, but the SIMD version of the hamming distance function is limited to
        // returning a u32. If the sequences being compared are too long to compute using the SIMD
        // version of this function, fall back to the naive implementation
        let hamming = if self.len() > u32::MAX as usize {
            distance::hamming
        } else {
            distance::simd::hamming
        };

        Ok(hamming(
            &self.normalize_case(Case::Upper).bytes,
            &other.normalize_case(Case::Upper).bytes,
        ) as usize)
    }

    pub fn levenshtein_distance(&self, other: &Self) -> Result<usize, Error> {
        if self.kind != other.kind {
            return Err(Error::DistanceKindMismatch(self.kind, other.kind));
        }

        Ok(distance::simd::levenshtein(
            &self.normalize_case(Case::Upper).bytes,
            &other.normalize_case(Case::Upper).bytes,
        ) as usize)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Error::FindOrf(kind) => write!(f, "Cannot find ORFs in a {kind}")?,
            Error::InvalidConversion(from, to) => write!(f, "Cannot convert {from} to {to}")?,
            Error::InvalidSeq(kinds) => {
                let kinds: Vec<_> = kinds.iter().map(|(k, a)| format!("{k} ({a})")).collect();
                let kinds = kinds[..kinds.len() - 1].join(", ") + ", or " + &kinds[kinds.len() - 1];
                write!(f, "The provided sequence was not valid {kinds}")?;
            }
            Error::ReverseComplement(kind) => write!(f, "Cannot reverse complement {kind}")?,
            Error::GcContent(kind) => write!(f, "Cannot provide GC content for {kind}")?,
            Error::HammingDistance(l1, l2) => write!(
                f,
                "Cannot compute Hamming distance between sequences of different lengths ({l1} and {l2})"
            )?,
            Error::DistanceKindMismatch(k1, k2) => {
                write!(f, "Cannot compute distance between {k1} and {k2}")?;
            }
        }
        Ok(())
    }
}

impl std::error::Error for Error {}

impl fmt::Display for Kind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Kind::Dna => write!(f, "DNA")?,
            Kind::Rna => write!(f, "RNA")?,
            Kind::Protein => write!(f, "Protein")?,
        }
        Ok(())
    }
}

impl fmt::Display for Alphabet {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Alphabet::Base => write!(f, "Base")?,
            Alphabet::N => write!(f, "N")?,
            Alphabet::Iupac => write!(f, "IUPAC")?,
        }
        Ok(())
    }
}

impl fmt::Display for Seq {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(str::from_utf8(&self.bytes).expect("Seq did not contain valid UTF-8"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // ===== "Magic" Sequence Constructor Tests (Typical Usage) ====================================

    #[test]
    fn magic_dna_sequence() {
        let dna = Seq::new("AGCTTTTCATTCTGACTGCA");
        let dna = dna.unwrap();
        assert_eq!(dna.kind(), Kind::Dna);
        assert_eq!(dna.alphabet(), Alphabet::Base);
    }

    #[test]
    fn magic_dna_n_sequence() {
        let dna = Seq::new("AGCTTNTCATTCTNNCTGCA");
        let dna = dna.unwrap();
        assert_eq!(dna.kind(), Kind::Dna);
        assert_eq!(dna.alphabet(), Alphabet::N);
    }

    #[test]
    fn magic_dna_iupac_sequence() {
        let dna = Seq::new("ABCTTNTCASTCTNNCTGWA");
        let dna = dna.unwrap();
        assert_eq!(dna.kind(), Kind::Dna);
        assert_eq!(dna.alphabet(), Alphabet::Iupac);
    }

    #[test]
    fn magic_rna_sequence() {
        let rna = Seq::new("AGCUUUUCAUUCUGACUGCA");
        let rna = rna.unwrap();
        assert_eq!(rna.kind(), Kind::Rna);
        assert_eq!(rna.alphabet(), Alphabet::Base);
    }

    #[test]
    fn magic_rna_n_sequence() {
        let rna = Seq::new("AGCUNNUCAUUCUGANUGCA");
        let rna = rna.unwrap();
        assert_eq!(rna.kind(), Kind::Rna);
        assert_eq!(rna.alphabet(), Alphabet::N);
    }

    #[test]
    fn magic_rna_iupac_sequence() {
        let rna = Seq::new("ADHUNNUCAUUVUGANUKCA");
        let rna = rna.unwrap();
        assert_eq!(rna.kind(), Kind::Rna);
        assert_eq!(rna.alphabet(), Alphabet::Iupac);
    }

    #[test]
    fn magic_protein_sequence() {
        let protein = Seq::new("MAMAPRTEINSTRING");
        let protein = protein.unwrap();
        assert_eq!(protein.kind(), Kind::Protein);
        assert_eq!(protein.alphabet(), Alphabet::Base);
    }

    #[test]
    fn magic_protein_iupac_sequence() {
        let protein = Seq::new("MAMXPRTEIBSTRINZ");
        let protein = protein.unwrap();
        assert_eq!(protein.kind(), Kind::Protein);
        assert_eq!(protein.alphabet(), Alphabet::Iupac);
    }

    // ===== "Magic" Sequence Constructor Tests (Edge Cases + Errors) ==============================

    #[test]
    fn magic_tricky_rna_iupac_sequence() {
        let rna = Seq::new("ADHANNCCAGGVAGANCKCAU");
        let rna = rna.unwrap();
        assert_eq!(rna.kind(), Kind::Rna);
        assert_eq!(rna.alphabet(), Alphabet::Iupac);
    }

    #[test]
    fn magic_tricky_rna_sequence() {
        let rna = Seq::new("AGCTTTTCATTCTGACTGCAU");
        assert!(rna.is_err());
    }

    #[test]
    fn magic_not_sequence() {
        let protein = Seq::new("MAMAPUTEINSTRINX");
        assert_eq!(
            protein,
            Err(Error::InvalidSeq(vec![
                (Kind::Dna, Alphabet::Base),
                (Kind::Rna, Alphabet::Base),
                (Kind::Dna, Alphabet::N),
                (Kind::Rna, Alphabet::N),
                (Kind::Protein, Alphabet::Base),
                (Kind::Dna, Alphabet::Iupac),
                (Kind::Rna, Alphabet::Iupac),
                (Kind::Protein, Alphabet::Iupac)
            ]))
        );
    }

    #[test]
    fn magic_all_alphabets_reachable() {
        // NOTE: The priority list of alphabets that the magic constructor searches through must be
        // ordered in a way that makes it possible (with the right input) to reach all of the
        // `Kind` + `Alphabet` combinations. This test is here so that, even if the order in which
        // alphabets are tried changes in the future, this property holds
        for (i, curr) in ALPHABETS.iter().enumerate() {
            assert!(&ALPHABETS[..i].iter().all(|past| {
                let past = &ALPHABET_MAP[past].symbols;
                let curr = &ALPHABET_MAP[curr].symbols;
                !past.is_superset(curr)
            }));
        }
    }

    // ===== Standard Sequence Constructor Tests (DNA) =============================================

    #[test]
    fn read_valid_dna_sequence() {
        let dna = Seq::dna("AGCTTTTCATTCTGACTGCA");
        let dna = dna.unwrap();
        assert_eq!(dna.kind(), Kind::Dna);
        assert_eq!(dna.alphabet(), Alphabet::Base);
    }

    #[test]
    fn read_invalid_dna_sequence() {
        let dna = Seq::dna("AGCTTTXCATTCTGACNGCA");
        assert_eq!(
            dna,
            Err(Error::InvalidSeq(vec![(Kind::Dna, Alphabet::Base)]))
        );
    }

    #[test]
    fn read_valid_dna_n_sequence() {
        let dna = Seq::dna_n("AGCTTNTCATTCTNNCTGCA");
        let dna = dna.unwrap();
        assert_eq!(dna.kind(), Kind::Dna);
        assert_eq!(dna.alphabet(), Alphabet::N);
    }

    #[test]
    fn read_invalid_dna_n_sequence() {
        let dna = Seq::dna_n("AGCUUNUCAUUCUNNCUGCA");
        assert_eq!(
            dna,
            Err(Error::InvalidSeq(vec![
                (Kind::Dna, Alphabet::Base),
                (Kind::Dna, Alphabet::N)
            ]))
        );
    }

    #[test]
    fn read_valid_dna_iupac_sequence() {
        let dna = Seq::dna_iupac("ABCTTNTCASTCTNNCTGWA");
        let dna = dna.unwrap();
        assert_eq!(dna.kind(), Kind::Dna);
        assert_eq!(dna.alphabet(), Alphabet::Iupac);
    }

    #[test]
    fn read_invalid_dna_iupac_sequence() {
        let dna = Seq::dna_iupac("ABCUUNUCASUCUNNCUGWA");
        assert_eq!(
            dna,
            Err(Error::InvalidSeq(vec![
                (Kind::Dna, Alphabet::Base),
                (Kind::Dna, Alphabet::N),
                (Kind::Dna, Alphabet::Iupac)
            ]))
        );
    }

    #[test]
    fn read_valid_dna_downcasting() {
        let dna = Seq::dna("AGCTTTTCATTCTGACTGCA");
        let dna_n = Seq::dna_n("AGCTTTTCATTCTGACTGCA");
        let dna_iupac = Seq::dna_iupac("AGCTTTTCATTCTGACTGCA");
        assert_eq!(dna, dna_n);
        assert_eq!(dna_n, dna_iupac);
    }

    // ===== Standard Sequence Constructor Tests (RNA) =============================================

    #[test]
    fn read_valid_rna_sequence() {
        let rna = Seq::rna("AGCUUUUCAUUCUGACUGCA");
        let rna = rna.unwrap();
        assert_eq!(rna.kind(), Kind::Rna);
        assert_eq!(rna.alphabet(), Alphabet::Base);
    }

    #[test]
    fn read_invalid_rna_sequence() {
        let rna = Seq::rna("AGCUUTUCAUUCUGACTGCA");
        assert_eq!(
            rna,
            Err(Error::InvalidSeq(vec![(Kind::Rna, Alphabet::Base)]))
        );
    }

    #[test]
    fn read_valid_rna_n_sequence() {
        let rna = Seq::rna_n("AGCUUNUCAUUCUNNCUGCA");
        let rna = rna.unwrap();
        assert_eq!(rna.kind(), Kind::Rna);
        assert_eq!(rna.alphabet(), Alphabet::N);
    }

    #[test]
    fn read_invalid_rna_n_sequence() {
        let rna = Seq::rna_n("AGCTTNTCATTCTNNCTGCA");
        assert_eq!(
            rna,
            Err(Error::InvalidSeq(vec![
                (Kind::Rna, Alphabet::Base),
                (Kind::Rna, Alphabet::N)
            ]))
        );
    }

    #[test]
    fn read_valid_rna_iupac_sequence() {
        let rna = Seq::rna_iupac("ABCUUNUCASUCUNNCUGWA");
        let rna = rna.unwrap();
        assert_eq!(rna.kind(), Kind::Rna);
        assert_eq!(rna.alphabet(), Alphabet::Iupac);
    }

    #[test]
    fn read_invalid_rna_iupac_sequence() {
        let rna = Seq::rna_iupac("ABCTTNTCASTCTNNCTGWA");
        assert_eq!(
            rna,
            Err(Error::InvalidSeq(vec![
                (Kind::Rna, Alphabet::Base),
                (Kind::Rna, Alphabet::N),
                (Kind::Rna, Alphabet::Iupac)
            ]))
        );
    }

    #[test]
    fn read_valid_rna_downcasting() {
        let rna = Seq::rna("AGCUUUUCAUUCUGACUGCA");
        let rna_n = Seq::rna_n("AGCUUUUCAUUCUGACUGCA");
        let rna_iupac = Seq::rna_iupac("AGCUUUUCAUUCUGACUGCA");
        assert_eq!(rna, rna_n);
        assert_eq!(rna_n, rna_iupac);
    }

    // ===== Standard Sequence Constructor Tests (Protein) =========================================

    #[test]
    fn read_valid_protein_sequence() {
        let protein = Seq::protein("MAMAPRTEINSTRING");
        let protein = protein.unwrap();
        assert_eq!(protein.kind(), Kind::Protein);
        assert_eq!(protein.alphabet(), Alphabet::Base);
    }

    #[test]
    fn read_invalid_protein_sequence() {
        let protein = Seq::protein("MAMAPUTEINSTRINX");
        assert_eq!(
            protein,
            Err(Error::InvalidSeq(vec![(Kind::Protein, Alphabet::Base)]))
        );
    }

    #[test]
    fn read_valid_protein_iupac_sequence() {
        let protein_iupac = Seq::protein_iupac("MAMXPRTEIBSTRINZ");
        let protein_iupac = protein_iupac.unwrap();
        assert_eq!(protein_iupac.kind(), Kind::Protein);
        assert_eq!(protein_iupac.alphabet(), Alphabet::Iupac);
    }

    #[test]
    fn read_invalid_protein_iupac_sequence() {
        let protein_iupac = Seq::protein_iupac("MAMXPUTEIBSTRINZ");
        assert_eq!(
            protein_iupac,
            Err(Error::InvalidSeq(vec![
                (Kind::Protein, Alphabet::Base),
                (Kind::Protein, Alphabet::Iupac)
            ]))
        );
    }

    // ===== Case Conversion Tests =================================================================

    #[test]
    fn to_lowercase() -> Result<(), Error> {
        let dna = Seq::dna("GaTgGaAcTtGaCtAcGtAaAtT")?;
        assert_eq!(
            dna.normalize_case(Case::Lower),
            Seq::dna("gatggaacttgactacgtaaatt")?
        );
        Ok(())
    }

    #[test]
    fn to_uppercase() -> Result<(), Error> {
        let dna = Seq::dna("GaTgGaAcTtGaCtAcGtAaAtT")?;
        assert_eq!(
            dna.normalize_case(Case::Upper),
            Seq::dna("GATGGAACTTGACTACGTAAATT")?
        );
        Ok(())
    }

    #[test]
    fn to_lowercase_protein() -> Result<(), Error> {
        let protein = Seq::protein("MAMAPRTEINSTRING*")?;
        assert_eq!(
            protein.normalize_case(Case::Lower),
            Seq::protein("mamaprteinstring*")?
        );
        Ok(())
    }

    // ===== String Conversion Tests ===============================================================

    #[test]
    fn dna_to_string() -> Result<(), Error> {
        let dna = Seq::dna("AGCTTTTCATTCTGACTGCA")?;
        assert_eq!(dna.to_string(), String::from("AGCTTTTCATTCTGACTGCA"));
        Ok(())
    }

    #[test]
    fn rna_to_string() -> Result<(), Error> {
        let rna = Seq::rna("AGCUUUUCAUUCUGACUGCA")?;
        assert_eq!(rna.to_string(), String::from("AGCUUUUCAUUCUGACUGCA"));
        Ok(())
    }

    #[test]
    fn protein_to_string() -> Result<(), Error> {
        let protein = Seq::protein("MAMAPRTEINSTRING")?;
        assert_eq!(protein.to_string(), String::from("MAMAPRTEINSTRING"));
        Ok(())
    }

    // ===== Sequence Length Tests =================================================================

    #[test]
    fn get_sequence_length() -> Result<(), Error> {
        let dna = Seq::dna("AGCTTTTCATTCTGACTGCA")?;
        assert_eq!(dna.len(), 20);
        Ok(())
    }

    #[test]
    fn is_sequence_empty() -> Result<(), Error> {
        let dna = Seq::dna("")?;
        assert!(dna.is_empty());
        let dna = Seq::dna("ACGT")?;
        assert!(!dna.is_empty());
        Ok(())
    }

    // ===== Subsequence Tests =====================================================================

    #[test]
    fn get_subseq_length() -> Result<(), Error> {
        let dna = Seq::dna("AGCTTTTCATTCTGACTGCA")?;
        assert_eq!(dna.len(), 20);
        let dna = dna.subseq(..);
        assert_eq!(dna.len(), 20);
        let dna = dna.subseq(5..);
        assert_eq!(dna.len(), 15);
        let dna = dna.subseq(5..10);
        assert_eq!(dna.len(), 5);
        assert_eq!(dna.to_string(), "TCTGA");
        Ok(())
    }

    #[test]
    fn preserve_subseq_metadata() -> Result<(), Error> {
        let seq = Seq::new("AGCTTTTCATTCTGACTGCS")?;
        assert_eq!(seq.kind(), Kind::Protein);
        assert_eq!(seq.alphabet(), Alphabet::Base);
        let seq = seq.subseq(..10);
        assert_eq!(seq.len(), 10);
        assert_eq!(seq.to_string(), "AGCTTTTCAT");
        assert_eq!(seq.kind(), Kind::Protein);
        assert_eq!(seq.alphabet(), Alphabet::Base);
        let seq = Seq::new("AGCTTTTCAT")?;
        assert_eq!(seq.kind(), Kind::Dna);
        assert_eq!(seq.alphabet(), Alphabet::Base);
        Ok(())
    }

    #[test]
    fn update_subseq_alphabet() -> Result<(), Error> {
        let seq = Seq::new("AGCTTTTCATTCTGACTGCSX")?;
        assert_eq!(seq.kind(), Kind::Protein);
        assert_eq!(seq.alphabet(), Alphabet::Iupac);
        let seq = seq.subseq(..10);
        assert_eq!(seq.len(), 10);
        assert_eq!(seq.to_string(), "AGCTTTTCAT");
        assert_eq!(seq.kind(), Kind::Protein);
        assert_eq!(seq.alphabet(), Alphabet::Base);
        let seq = Seq::new("AGCTTTTCAT")?;
        assert_eq!(seq.kind(), Kind::Dna);
        assert_eq!(seq.alphabet(), Alphabet::Base);
        Ok(())
    }

    // ===== Sequence Reversal Tool Tests ==========================================================

    #[test]
    fn reverse_sequence() -> Result<(), Error> {
        let dna = Seq::dna("AGCTTTTCATTCTGACTGCA")?;
        let dna_rev = Seq::dna("ACGTCAGTCTTACTTTTCGA")?;
        assert_eq!(dna.rev(), dna_rev);

        let rna = Seq::rna("AGCUUUUCAUUCUGACUGCA")?;
        let rna_rev = Seq::rna("ACGUCAGUCUUACUUUUCGA")?;
        assert_eq!(rna.rev(), rna_rev);

        let protein = Seq::protein("MAMAPRTEINSTRING")?;
        let protein_rev = Seq::protein("GNIRTSNIETRPAMAM")?;
        assert_eq!(protein.rev(), protein_rev);
        Ok(())
    }

    // ===== Element Counting Tool Tests ===========================================================

    #[test]
    fn count_nucleotides() -> Result<(), Error> {
        let dna =
            Seq::dna("AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC")?;
        let counts = dna.count_elements();
        assert_eq!(counts[b'A'], 20);
        assert_eq!(counts[b'C'], 12);
        assert_eq!(counts[b'G'], 17);
        assert_eq!(counts[b'T'], 21);
        Ok(())
    }

    // ===== GC Content Tool Tests =================================================================

    #[test]
    fn gc_cont_simple() -> Result<(), Error> {
        let dna =
            Seq::dna("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGCGCGCGCGCGCGCGCGCGCGCGGCGCGCGCGCGG")?;
        assert_eq!(dna.gc_content()?, 0.5);
        Ok(())
    }

    #[test]
    fn gc_cont_missing_val() -> Result<(), Error> {
        let dna =
            Seq::dna("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT")?;
        assert_eq!(dna.gc_content()?, 0.0);
        Ok(())
    }

    #[test]
    fn gc_cont_lower_case() -> Result<(), Error> {
        let dna =
            Seq::dna("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaagcgcgcgcgcgcgcgcgcgcgcggcgcgcgcgcgg")?;
        assert_eq!(dna.gc_content()?, 0.5);
        Ok(())
    }

    #[test]
    fn gc_cont_iupac() -> Result<(), Error> {
        let dna = Seq::dna_iupac("GCNS")?;
        assert_eq!(dna.gc_content()?, 0.8125);
        Ok(())
    }

    #[test]
    fn gc_cont_iupac_lower_case() -> Result<(), Error> {
        let dna = Seq::dna_iupac("cgbdnsrykm")?;
        assert_eq!(dna.gc_content()?, 0.625);
        Ok(())
    }

    #[test]
    fn gc_cont_protein() -> Result<(), Error> {
        let protein = Seq::protein("MAMAPRTEINSTRING*")?;
        assert_eq!(protein.gc_content(), Err(Error::GcContent(Kind::Protein)));
        Ok(())
    }

    // ===== Hamming + Levenshtein Distance Tool Tests =============================================

    #[test]
    fn hamming_dna() -> Result<(), Error> {
        let a = Seq::dna("ACGTGTACGTGTACGT")?;
        let b = Seq::dna("ACGTCTACTTGTGCCG")?;
        assert_eq!(a.hamming_distance(&b)?, 5);
        Ok(())
    }

    #[test]
    fn hamming_rna() -> Result<(), Error> {
        let a = Seq::rna("ACGUGUACGUGUACGU")?;
        let b = Seq::rna("ACGUCUACUUGUGCCG")?;
        assert_eq!(a.hamming_distance(&b)?, 5);
        Ok(())
    }

    #[test]
    fn hamming_protein() -> Result<(), Error> {
        let a = Seq::protein("MAMAPRTEINSTRING")?;
        let b = Seq::protein("MIMAPNSGINSTRENN")?;
        assert_eq!(a.hamming_distance(&b)?, 6);
        Ok(())
    }

    #[test]
    fn hamming_different_alphabet() -> Result<(), Error> {
        let a = Seq::rna("ACGUGUACGUGUACGU")?;
        let b = Seq::rna_n("AGCUUNUCAUUCUNNC")?;
        assert_eq!(a.hamming_distance(&b)?, 12);
        Ok(())
    }

    #[test]
    fn hamming_type_mismatch() -> Result<(), Error> {
        let a = Seq::protein("MAMAPRTEINSTRING")?;
        let b = Seq::rna("ACGUGUACGUGUACGU")?;
        assert_eq!(
            a.hamming_distance(&b),
            Err(Error::DistanceKindMismatch(Kind::Protein, Kind::Rna))
        );
        Ok(())
    }

    #[test]
    fn hamming_unequal_len() -> Result<(), Error> {
        let a = Seq::rna("ACGUGUACGUGUACGU")?;
        let b = Seq::rna("ACGUGUACGUGUACGUACG")?;
        assert_eq!(a.hamming_distance(&b), Err(Error::HammingDistance(16, 19)));
        Ok(())
    }

    #[test]
    fn levenshtein_dna() -> Result<(), Error> {
        let a = Seq::dna("AACGTGTACGTGTACGTAGCT")?;
        let b = Seq::dna("ACGTCTACTTGTGCCG")?;
        assert_eq!(a.levenshtein_distance(&b)?, 9);
        Ok(())
    }

    #[test]
    fn levenshtein_rna() -> Result<(), Error> {
        let a = Seq::rna("AACACGUGUACGUGUACGUCGUUGC")?;
        let b = Seq::rna("ACGUCUACUUGUGCCG")?;
        assert_eq!(a.levenshtein_distance(&b)?, 12);
        Ok(())
    }

    #[test]
    fn levenshtein_protein() -> Result<(), Error> {
        let a = Seq::protein("MPRMAMAPRTEINSTRINGM")?;
        let b = Seq::protein("MIMAPNSGINSTRENN")?;
        assert_eq!(a.levenshtein_distance(&b)?, 10);
        Ok(())
    }

    #[test]
    fn levenshtein_different_alphabet() -> Result<(), Error> {
        let a = Seq::rna("ACGUGUACGUGUACGU")?;
        let b = Seq::rna_n("AGCUUNUCAUUCUNNC")?;
        assert_eq!(a.levenshtein_distance(&b)?, 11);
        Ok(())
    }

    #[test]
    fn levenshtein_type_mismatch() -> Result<(), Error> {
        let a = Seq::protein("MAMAPRTEINSTRING")?;
        let b = Seq::rna("ACGUGUACGUGUACGU")?;
        assert_eq!(
            a.levenshtein_distance(&b),
            Err(Error::DistanceKindMismatch(Kind::Protein, Kind::Rna))
        );
        Ok(())
    }

    // ===== ORF Finding + Translation Tool Tests ==================================================

    #[test]
    fn find_orfs_dna() -> Result<(), Error> {
        let dna = Seq::dna(
            "AGCCATGTAGCTAACTCAGGTTACATGGGGATGACCCCGCGACTTGGA\
             TTAGAGTCTCTTTTGGAATAAGCCTGAATGATCCGAGTAGCATCTCAG",
        )?;
        assert_eq!(
            vec![
                (
                    Orf {
                        start: 4,
                        end: 10,
                        offset: 1,
                    },
                    Seq::dna("ATGTAG")?
                ),
                (
                    Orf {
                        start: 24,
                        end: 69,
                        offset: 0,
                    },
                    Seq::dna("ATGGGGATGACCCCGCGACTTGGATTAGAGTCTCTTTTGGAATAA")?
                ),
                (
                    Orf {
                        start: 30,
                        end: 69,
                        offset: 0,
                    },
                    Seq::dna("ATGACCCCGCGACTTGGATTAGAGTCTCTTTTGGAATAA")?
                )
            ],
            dna.find_orfs(1)?
        );
        assert_eq!(
            vec![
                (
                    Orf {
                        start: 70,
                        end: 76,
                        offset: 1,
                    },
                    Seq::dna("ATGTAA")?
                ),
                (
                    Orf {
                        start: 5,
                        end: 86,
                        offset: 2,
                    },
                    Seq::dna("ATGCTACTCGGATCATTCAGGCTTATTCCAAAAGAGACTCTAATCCAAGTCGCGGGGTCATCCCCATGTAACCTGAGTTAG")?
                ),
            ],
            dna.reverse_complement()?.find_orfs(1)?
        );
        Ok(())
    }

    #[test]
    fn find_orfs_lowercase_dna() -> Result<(), Error> {
        let dna = Seq::dna(
            "agccatgtagctaactcaggttacatggggatgaccccgcgacttgga\
             ttagagtctcttttggaataagcctgaatgatccgagtagcatctcag",
        )?;
        assert_eq!(
            vec![
                (
                    Orf {
                        start: 4,
                        end: 10,
                        offset: 1,
                    },
                    Seq::dna("atgtag")?
                ),
                (
                    Orf {
                        start: 24,
                        end: 69,
                        offset: 0,
                    },
                    Seq::dna("atggggatgaccccgcgacttggattagagtctcttttggaataa")?
                ),
                (
                    Orf {
                        start: 30,
                        end: 69,
                        offset: 0,
                    },
                    Seq::dna("atgaccccgcgacttggattagagtctcttttggaataa")?
                )
            ],
            dna.find_orfs(1)?
        );
        assert_eq!(
            vec![
                (
                    Orf {
                        start: 70,
                        end: 76,
                        offset: 1,
                    },
                    Seq::dna("atgtaa")?
                ),
                (
                    Orf {
                        start: 5,
                        end: 86,
                        offset: 2,
                    },
                    Seq::dna("atgctactcggatcattcaggcttattccaaaagagactctaatccaagtcgcggggtcatccccatgtaacctgagttag")?
                ),
            ],
            dna.reverse_complement()?.find_orfs(1)?
        );
        Ok(())
    }

    #[test]
    fn find_orfs_rna() -> Result<(), Error> {
        let rna = Seq::rna(
            "AGCCAUGUAGCUAACUCAGGUUACAUGGGGAUGACCCCGCGACUUGGA\
             UUAGAGUCUCUUUUGGAAUAAGCCUGAAUGAUCCGAGUAGCAUCUCAG",
        )?;
        assert_eq!(
            vec![
                (
                    Orf {
                        start: 4,
                        end: 10,
                        offset: 1,
                    },
                    Seq::rna("AUGUAG")?
                ),
                (
                    Orf {
                        start: 24,
                        end: 69,
                        offset: 0,
                    },
                    Seq::rna("AUGGGGAUGACCCCGCGACUUGGAUUAGAGUCUCUUUUGGAAUAA")?
                ),
                (
                    Orf {
                        start: 30,
                        end: 69,
                        offset: 0,
                    },
                    Seq::rna("AUGACCCCGCGACUUGGAUUAGAGUCUCUUUUGGAAUAA")?
                )
            ],
            rna.find_orfs(1)?
        );
        assert_eq!(
            vec![
                (
                    Orf {
                        start: 70,
                        end: 76,
                        offset: 1,
                    },
                    Seq::rna("AUGUAA")?
                ),
                (
                    Orf {
                        start: 5,
                        end: 86,
                        offset: 2,
                    },
                    Seq::rna("AUGCUACUCGGAUCAUUCAGGCUUAUUCCAAAAGAGACUCUAAUCCAAGUCGCGGGGUCAUCCCCAUGUAACCUGAGUUAG")?
                ),
            ],
            rna.reverse_complement()?.find_orfs(1)?
        );
        Ok(())
    }

    #[test]
    fn find_orfs_iupac_rna() -> Result<(), Error> {
        let rna = Seq::rna_iupac(
            "AGCCAUGUARCUAACUCAGGUUACAUGGGGAUGACCCCGCGACUUGGA\
             UUAGAGUCUCUUUUGGAAURAGCCUGAAUGAUCCGAGUAGCAUCUCAG",
        )?;
        assert_eq!(
            vec![
                (
                    Orf {
                        start: 4,
                        end: 10,
                        offset: 1,
                    },
                    Seq::rna_iupac("AUGUAR")?
                ),
                (
                    Orf {
                        start: 24,
                        end: 69,
                        offset: 0,
                    },
                    Seq::rna_iupac("AUGGGGAUGACCCCGCGACUUGGAUUAGAGUCUCUUUUGGAAURA")?
                ),
                (
                    Orf {
                        start: 30,
                        end: 69,
                        offset: 0,
                    },
                    Seq::rna_iupac("AUGACCCCGCGACUUGGAUUAGAGUCUCUUUUGGAAURA")?
                )
            ],
            rna.find_orfs(1)?
        );
        Ok(())
    }

    #[test]
    fn find_orfs_protein() -> Result<(), Error> {
        let protein = Seq::protein("MAMAPRTEINSTRING*")?;
        assert_eq!(Err(Error::FindOrf(Kind::Protein)), protein.find_orfs(1));
        Ok(())
    }

    #[test]
    fn find_large_orfs() -> Result<(), Error> {
        let dna = Seq::dna(
            "AGCCATGTAGCTAACTCAGGTTACATGGGGATGACCCCGCGACTTGGA\
             TTAGAGTCTCTTTTGGAATAAGCCTGAATGATCCGAGTAGCATCTCAG",
        )?;
        assert_eq!(
            vec![
                (
                    Orf {
                        start: 24,
                        end: 69,
                        offset: 0,
                    },
                    Seq::dna("ATGGGGATGACCCCGCGACTTGGATTAGAGTCTCTTTTGGAATAA")?
                ),
                (
                    Orf {
                        start: 30,
                        end: 69,
                        offset: 0,
                    },
                    Seq::dna("ATGACCCCGCGACTTGGATTAGAGTCTCTTTTGGAATAA")?
                )
            ],
            dna.find_orfs(12)?
        );
        assert_eq!(
            vec![
                (
                    Orf {
                        start: 5,
                        end: 86,
                        offset: 2,
                    },
                    Seq::dna("ATGCTACTCGGATCATTCAGGCTTATTCCAAAAGAGACTCTAATCCAAGTCGCGGGGTCATCCCCATGTAACCTGAGTTAG")?
                ),
            ],
            dna.reverse_complement()?.find_orfs(12)?
        );
        Ok(())
    }

    // ===== Sequence Conversion Tool Tests ========================================================

    #[test]
    fn self_conversions() -> Result<(), Error> {
        let dna = Seq::dna("GATGGAACTTGACTACGTAAATT")?;
        let rna = Seq::rna("AGCUUUUCAUUCUGACUGCA")?;
        let protein = Seq::protein("MAMAPRTEINSTRING*")?;
        assert_eq!(dna, dna.convert(Kind::Dna)?);
        assert_eq!(rna, rna.convert(Kind::Rna)?);
        assert_eq!(protein, protein.convert(Kind::Protein)?);
        Ok(())
    }

    #[test]
    fn dna_to_rna() -> Result<(), Error> {
        let dna = Seq::dna("GATGGAACTTGACTACGTAAATT")?;
        let rna = dna.convert(Kind::Rna)?;
        assert_eq!(rna, Seq::rna("GAUGGAACUUGACUACGUAAAUU")?);
        Ok(())
    }

    #[test]
    fn dna_to_rna_keep_case() -> Result<(), Error> {
        let dna = Seq::dna("GaTgGaAcTtGaCtAcGtAaAtT")?;
        let rna = dna.convert(Kind::Rna)?;
        assert_eq!(rna, Seq::rna("GaUgGaAcUuGaCuAcGuAaAuU")?);
        Ok(())
    }

    #[test]
    fn rna_to_dna() -> Result<(), Error> {
        let rna = Seq::rna("GAUGGAACUUGACUACGUAAAUU")?;
        let dna = rna.convert(Kind::Dna)?;
        assert_eq!(dna, Seq::dna("GATGGAACTTGACTACGTAAATT")?);
        Ok(())
    }

    #[test]
    fn rna_to_dna_keep_case() -> Result<(), Error> {
        let rna = Seq::rna("GaUgGaAcUuGaCuAcGuAaAuU")?;
        let dna = rna.convert(Kind::Dna)?;
        assert_eq!(dna, Seq::dna("GaTgGaAcTtGaCtAcGtAaAtT")?);
        Ok(())
    }

    #[test]
    fn rna_to_protein() -> Result<(), Error> {
        let rna = Seq::rna("AUGGCCAUGGCGCCCAGAACUGAGAUCAAUAGUACCCGUAUUAACGGGUGA")?;
        let protein = rna.convert(Kind::Protein)?;
        assert_eq!(protein, Seq::protein("MAMAPRTEINSTRING*")?);
        Ok(())
    }

    #[test]
    fn rna_all_codons() -> Result<(), Error> {
        let rna = Seq::rna(
            "UUUUUCUUAUUGUCUUCCUCAUCGUAUUACUAAUAGUGUUGCUGAUGGCUUCUCCUACUGCCUCCCCCACCGCAUCACCAACAGCG\
             UCGCCGACGGAUUAUCAUAAUGACUACCACAACGAAUAACAAAAAGAGUAGCAGAAGGGUUGUCGUAGUGGCUGCCGCAGCGGAUG\
             ACGAAGAGGGUGGCGGAGGG",
        )?;
        let protein = rna.convert(Kind::Protein)?;
        assert_eq!(
            protein,
            Seq::protein("FFLLSSSSYY**CC*WLLLLPPPPHHQQRRRRIIIMTTTTNNKKSSRRVVVVAAAADDEEGGGG")?
        );
        Ok(())
    }

    #[test]
    #[allow(clippy::too_many_lines)]
    fn rna_all_iupac_codons() -> Result<(), Error> {
        let rna = Seq::rna_iupac(
            "AAAAABAACAADAAGAAHAAKAAMAANAARAASAAUAAVAAWAAYABAABBABCABDABGABHABKABMABNABRABSABUABVAB\
             WABYACAACBACCACDACGACHACKACMACNACRACSACUACVACWACYADAADBADCADDADGADHADKADMADNADRADSADUA\
             DVADWADYAGAAGBAGCAGDAGGAGHAGKAGMAGNAGRAGSAGUAGVAGWAGYAHAAHBAHCAHDAHGAHHAHKAHMAHNAHRAHS\
             AHUAHVAHWAHYAKAAKBAKCAKDAKGAKHAKKAKMAKNAKRAKSAKUAKVAKWAKYAMAAMBAMCAMDAMGAMHAMKAMMAMNAM\
             RAMSAMUAMVAMWAMYANAANBANCANDANGANHANKANMANNANRANSANUANVANWANYARAARBARCARDARGARHARKARMA\
             RNARRARSARUARVARWARYASAASBASCASDASGASHASKASMASNASRASSASUASVASWASYAUAAUBAUCAUDAUGAUHAUK\
             AUMAUNAURAUSAUUAUVAUWAUYAVAAVBAVCAVDAVGAVHAVKAVMAVNAVRAVSAVUAVVAVWAVYAWAAWBAWCAWDAWGAW\
             HAWKAWMAWNAWRAWSAWUAWVAWWAWYAYAAYBAYCAYDAYGAYHAYKAYMAYNAYRAYSAYUAYVAYWAYYBAABABBACBADB\
             AGBAHBAKBAMBANBARBASBAUBAVBAWBAYBBABBBBBCBBDBBGBBHBBKBBMBBNBBRBBSBBUBBVBBWBBYBCABCBBCC\
             BCDBCGBCHBCKBCMBCNBCRBCSBCUBCVBCWBCYBDABDBBDCBDDBDGBDHBDKBDMBDNBDRBDSBDUBDVBDWBDYBGABG\
             BBGCBGDBGGBGHBGKBGMBGNBGRBGSBGUBGVBGWBGYBHABHBBHCBHDBHGBHHBHKBHMBHNBHRBHSBHUBHVBHWBHYB\
             KABKBBKCBKDBKGBKHBKKBKMBKNBKRBKSBKUBKVBKWBKYBMABMBBMCBMDBMGBMHBMKBMMBMNBMRBMSBMUBMVBMW\
             BMYBNABNBBNCBNDBNGBNHBNKBNMBNNBNRBNSBNUBNVBNWBNYBRABRBBRCBRDBRGBRHBRKBRMBRNBRRBRSBRUBR\
             VBRWBRYBSABSBBSCBSDBSGBSHBSKBSMBSNBSRBSSBSUBSVBSWBSYBUABUBBUCBUDBUGBUHBUKBUMBUNBURBUSB\
             UUBUVBUWBUYBVABVBBVCBVDBVGBVHBVKBVMBVNBVRBVSBVUBVVBVWBVYBWABWBBWCBWDBWGBWHBWKBWMBWNBWR\
             BWSBWUBWVBWWBWYBYABYBBYCBYDBYGBYHBYKBYMBYNBYRBYSBYUBYVBYWBYYCAACABCACCADCAGCAHCAKCAMCA\
             NCARCASCAUCAVCAWCAYCBACBBCBCCBDCBGCBHCBKCBMCBNCBRCBSCBUCBVCBWCBYCCACCBCCCCCDCCGCCHCCKC\
             CMCCNCCRCCSCCUCCVCCWCCYCDACDBCDCCDDCDGCDHCDKCDMCDNCDRCDSCDUCDVCDWCDYCGACGBCGCCGDCGGCGH\
             CGKCGMCGNCGRCGSCGUCGVCGWCGYCHACHBCHCCHDCHGCHHCHKCHMCHNCHRCHSCHUCHVCHWCHYCKACKBCKCCKDCK\
             GCKHCKKCKMCKNCKRCKSCKUCKVCKWCKYCMACMBCMCCMDCMGCMHCMKCMMCMNCMRCMSCMUCMVCMWCMYCNACNBCNCC\
             NDCNGCNHCNKCNMCNNCNRCNSCNUCNVCNWCNYCRACRBCRCCRDCRGCRHCRKCRMCRNCRRCRSCRUCRVCRWCRYCSACSB\
             CSCCSDCSGCSHCSKCSMCSNCSRCSSCSUCSVCSWCSYCUACUBCUCCUDCUGCUHCUKCUMCUNCURCUSCUUCUVCUWCUYCV\
             ACVBCVCCVDCVGCVHCVKCVMCVNCVRCVSCVUCVVCVWCVYCWACWBCWCCWDCWGCWHCWKCWMCWNCWRCWSCWUCWVCWWC\
             WYCYACYBCYCCYDCYGCYHCYKCYMCYNCYRCYSCYUCYVCYWCYYDAADABDACDADDAGDAHDAKDAMDANDARDASDAUDAV\
             DAWDAYDBADBBDBCDBDDBGDBHDBKDBMDBNDBRDBSDBUDBVDBWDBYDCADCBDCCDCDDCGDCHDCKDCMDCNDCRDCSDC\
             UDCVDCWDCYDDADDBDDCDDDDDGDDHDDKDDMDDNDDRDDSDDUDDVDDWDDYDGADGBDGCDGDDGGDGHDGKDGMDGNDGRD\
             GSDGUDGVDGWDGYDHADHBDHCDHDDHGDHHDHKDHMDHNDHRDHSDHUDHVDHWDHYDKADKBDKCDKDDKGDKHDKKDKMDKN\
             DKRDKSDKUDKVDKWDKYDMADMBDMCDMDDMGDMHDMKDMMDMNDMRDMSDMUDMVDMWDMYDNADNBDNCDNDDNGDNHDNKDN\
             MDNNDNRDNSDNUDNVDNWDNYDRADRBDRCDRDDRGDRHDRKDRMDRNDRRDRSDRUDRVDRWDRYDSADSBDSCDSDDSGDSHD\
             SKDSMDSNDSRDSSDSUDSVDSWDSYDUADUBDUCDUDDUGDUHDUKDUMDUNDURDUSDUUDUVDUWDUYDVADVBDVCDVDDVG\
             DVHDVKDVMDVNDVRDVSDVUDVVDVWDVYDWADWBDWCDWDDWGDWHDWKDWMDWNDWRDWSDWUDWVDWWDWYDYADYBDYCDY\
             DDYGDYHDYKDYMDYNDYRDYSDYUDYVDYWDYYGAAGABGACGADGAGGAHGAKGAMGANGARGASGAUGAVGAWGAYGBAGBBG\
             BCGBDGBGGBHGBKGBMGBNGBRGBSGBUGBVGBWGBYGCAGCBGCCGCDGCGGCHGCKGCMGCNGCRGCSGCUGCVGCWGCYGDA\
             GDBGDCGDDGDGGDHGDKGDMGDNGDRGDSGDUGDVGDWGDYGGAGGBGGCGGDGGGGGHGGKGGMGGNGGRGGSGGUGGVGGWGG\
             YGHAGHBGHCGHDGHGGHHGHKGHMGHNGHRGHSGHUGHVGHWGHYGKAGKBGKCGKDGKGGKHGKKGKMGKNGKRGKSGKUGKVG\
             KWGKYGMAGMBGMCGMDGMGGMHGMKGMMGMNGMRGMSGMUGMVGMWGMYGNAGNBGNCGNDGNGGNHGNKGNMGNNGNRGNSGNU\
             GNVGNWGNYGRAGRBGRCGRDGRGGRHGRKGRMGRNGRRGRSGRUGRVGRWGRYGSAGSBGSCGSDGSGGSHGSKGSMGSNGSRGS\
             SGSUGSVGSWGSYGUAGUBGUCGUDGUGGUHGUKGUMGUNGURGUSGUUGUVGUWGUYGVAGVBGVCGVDGVGGVHGVKGVMGVNG\
             VRGVSGVUGVVGVWGVYGWAGWBGWCGWDGWGGWHGWKGWMGWNGWRGWSGWUGWVGWWGWYGYAGYBGYCGYDGYGGYHGYKGYM\
             GYNGYRGYSGYUGYVGYWGYYHAAHABHACHADHAGHAHHAKHAMHANHARHASHAUHAVHAWHAYHBAHBBHBCHBDHBGHBHHB\
             KHBMHBNHBRHBSHBUHBVHBWHBYHCAHCBHCCHCDHCGHCHHCKHCMHCNHCRHCSHCUHCVHCWHCYHDAHDBHDCHDDHDGH\
             DHHDKHDMHDNHDRHDSHDUHDVHDWHDYHGAHGBHGCHGDHGGHGHHGKHGMHGNHGRHGSHGUHGVHGWHGYHHAHHBHHCHHD\
             HHGHHHHHKHHMHHNHHRHHSHHUHHVHHWHHYHKAHKBHKCHKDHKGHKHHKKHKMHKNHKRHKSHKUHKVHKWHKYHMAHMBHM\
             CHMDHMGHMHHMKHMMHMNHMRHMSHMUHMVHMWHMYHNAHNBHNCHNDHNGHNHHNKHNMHNNHNRHNSHNUHNVHNWHNYHRAH\
             RBHRCHRDHRGHRHHRKHRMHRNHRRHRSHRUHRVHRWHRYHSAHSBHSCHSDHSGHSHHSKHSMHSNHSRHSSHSUHSVHSWHSY\
             HUAHUBHUCHUDHUGHUHHUKHUMHUNHURHUSHUUHUVHUWHUYHVAHVBHVCHVDHVGHVHHVKHVMHVNHVRHVSHVUHVVHV\
             WHVYHWAHWBHWCHWDHWGHWHHWKHWMHWNHWRHWSHWUHWVHWWHWYHYAHYBHYCHYDHYGHYHHYKHYMHYNHYRHYSHYUH\
             YVHYWHYYKAAKABKACKADKAGKAHKAKKAMKANKARKASKAUKAVKAWKAYKBAKBBKBCKBDKBGKBHKBKKBMKBNKBRKBS\
             KBUKBVKBWKBYKCAKCBKCCKCDKCGKCHKCKKCMKCNKCRKCSKCUKCVKCWKCYKDAKDBKDCKDDKDGKDHKDKKDMKDNKD\
             RKDSKDUKDVKDWKDYKGAKGBKGCKGDKGGKGHKGKKGMKGNKGRKGSKGUKGVKGWKGYKHAKHBKHCKHDKHGKHHKHKKHMK\
             HNKHRKHSKHUKHVKHWKHYKKAKKBKKCKKDKKGKKHKKKKKMKKNKKRKKSKKUKKVKKWKKYKMAKMBKMCKMDKMGKMHKMK\
             KMMKMNKMRKMSKMUKMVKMWKMYKNAKNBKNCKNDKNGKNHKNKKNMKNNKNRKNSKNUKNVKNWKNYKRAKRBKRCKRDKRGKR\
             HKRKKRMKRNKRRKRSKRUKRVKRWKRYKSAKSBKSCKSDKSGKSHKSKKSMKSNKSRKSSKSUKSVKSWKSYKUAKUBKUCKUDK\
             UGKUHKUKKUMKUNKURKUSKUUKUVKUWKUYKVAKVBKVCKVDKVGKVHKVKKVMKVNKVRKVSKVUKVVKVWKVYKWAKWBKWC\
             KWDKWGKWHKWKKWMKWNKWRKWSKWUKWVKWWKWYKYAKYBKYCKYDKYGKYHKYKKYMKYNKYRKYSKYUKYVKYWKYYMAAMA\
             BMACMADMAGMAHMAKMAMMANMARMASMAUMAVMAWMAYMBAMBBMBCMBDMBGMBHMBKMBMMBNMBRMBSMBUMBVMBWMBYM\
             CAMCBMCCMCDMCGMCHMCKMCMMCNMCRMCSMCUMCVMCWMCYMDAMDBMDCMDDMDGMDHMDKMDMMDNMDRMDSMDUMDVMDW\
             MDYMGAMGBMGCMGDMGGMGHMGKMGMMGNMGRMGSMGUMGVMGWMGYMHAMHBMHCMHDMHGMHHMHKMHMMHNMHRMHSMHUMH\
             VMHWMHYMKAMKBMKCMKDMKGMKHMKKMKMMKNMKRMKSMKUMKVMKWMKYMMAMMBMMCMMDMMGMMHMMKMMMMMNMMRMMSM\
             MUMMVMMWMMYMNAMNBMNCMNDMNGMNHMNKMNMMNNMNRMNSMNUMNVMNWMNYMRAMRBMRCMRDMRGMRHMRKMRMMRNMRR\
             MRSMRUMRVMRWMRYMSAMSBMSCMSDMSGMSHMSKMSMMSNMSRMSSMSUMSVMSWMSYMUAMUBMUCMUDMUGMUHMUKMUMMU\
             NMURMUSMUUMUVMUWMUYMVAMVBMVCMVDMVGMVHMVKMVMMVNMVRMVSMVUMVVMVWMVYMWAMWBMWCMWDMWGMWHMWKM\
             WMMWNMWRMWSMWUMWVMWWMWYMYAMYBMYCMYDMYGMYHMYKMYMMYNMYRMYSMYUMYVMYWMYYNAANABNACNADNAGNAH\
             NAKNAMNANNARNASNAUNAVNAWNAYNBANBBNBCNBDNBGNBHNBKNBMNBNNBRNBSNBUNBVNBWNBYNCANCBNCCNCDNC\
             GNCHNCKNCMNCNNCRNCSNCUNCVNCWNCYNDANDBNDCNDDNDGNDHNDKNDMNDNNDRNDSNDUNDVNDWNDYNGANGBNGCN\
             GDNGGNGHNGKNGMNGNNGRNGSNGUNGVNGWNGYNHANHBNHCNHDNHGNHHNHKNHMNHNNHRNHSNHUNHVNHWNHYNKANKB\
             NKCNKDNKGNKHNKKNKMNKNNKRNKSNKUNKVNKWNKYNMANMBNMCNMDNMGNMHNMKNMMNMNNMRNMSNMUNMVNMWNMYNN\
             ANNBNNCNNDNNGNNHNNKNNMNNNNNRNNSNNUNNVNNWNNYNRANRBNRCNRDNRGNRHNRKNRMNRNNRRNRSNRUNRVNRWN\
             RYNSANSBNSCNSDNSGNSHNSKNSMNSNNSRNSSNSUNSVNSWNSYNUANUBNUCNUDNUGNUHNUKNUMNUNNURNUSNUUNUV\
             NUWNUYNVANVBNVCNVDNVGNVHNVKNVMNVNNVRNVSNVUNVVNVWNVYNWANWBNWCNWDNWGNWHNWKNWMNWNNWRNWSNW\
             UNWVNWWNWYNYANYBNYCNYDNYGNYHNYKNYMNYNNYRNYSNYUNYVNYWNYYRAARABRACRADRAGRAHRAKRAMRANRARR\
             ASRAURAVRAWRAYRBARBBRBCRBDRBGRBHRBKRBMRBNRBRRBSRBURBVRBWRBYRCARCBRCCRCDRCGRCHRCKRCMRCN\
             RCRRCSRCURCVRCWRCYRDARDBRDCRDDRDGRDHRDKRDMRDNRDRRDSRDURDVRDWRDYRGARGBRGCRGDRGGRGHRGKRG\
             MRGNRGRRGSRGURGVRGWRGYRHARHBRHCRHDRHGRHHRHKRHMRHNRHRRHSRHURHVRHWRHYRKARKBRKCRKDRKGRKHR\
             KKRKMRKNRKRRKSRKURKVRKWRKYRMARMBRMCRMDRMGRMHRMKRMMRMNRMRRMSRMURMVRMWRMYRNARNBRNCRNDRNG\
             RNHRNKRNMRNNRNRRNSRNURNVRNWRNYRRARRBRRCRRDRRGRRHRRKRRMRRNRRRRRSRRURRVRRWRRYRSARSBRSCRS\
             DRSGRSHRSKRSMRSNRSRRSSRSURSVRSWRSYRUARUBRUCRUDRUGRUHRUKRUMRUNRURRUSRUURUVRUWRUYRVARVBR\
             VCRVDRVGRVHRVKRVMRVNRVRRVSRVURVVRVWRVYRWARWBRWCRWDRWGRWHRWKRWMRWNRWRRWSRWURWVRWWRWYRYA\
             RYBRYCRYDRYGRYHRYKRYMRYNRYRRYSRYURYVRYWRYYSAASABSACSADSAGSAHSAKSAMSANSARSASSAUSAVSAWSA\
             YSBASBBSBCSBDSBGSBHSBKSBMSBNSBRSBSSBUSBVSBWSBYSCASCBSCCSCDSCGSCHSCKSCMSCNSCRSCSSCUSCVS\
             CWSCYSDASDBSDCSDDSDGSDHSDKSDMSDNSDRSDSSDUSDVSDWSDYSGASGBSGCSGDSGGSGHSGKSGMSGNSGRSGSSGU\
             SGVSGWSGYSHASHBSHCSHDSHGSHHSHKSHMSHNSHRSHSSHUSHVSHWSHYSKASKBSKCSKDSKGSKHSKKSKMSKNSKRSK\
             SSKUSKVSKWSKYSMASMBSMCSMDSMGSMHSMKSMMSMNSMRSMSSMUSMVSMWSMYSNASNBSNCSNDSNGSNHSNKSNMSNNS\
             NRSNSSNUSNVSNWSNYSRASRBSRCSRDSRGSRHSRKSRMSRNSRRSRSSRUSRVSRWSRYSSASSBSSCSSDSSGSSHSSKSSM\
             SSNSSRSSSSSUSSVSSWSSYSUASUBSUCSUDSUGSUHSUKSUMSUNSURSUSSUUSUVSUWSUYSVASVBSVCSVDSVGSVHSV\
             KSVMSVNSVRSVSSVUSVVSVWSVYSWASWBSWCSWDSWGSWHSWKSWMSWNSWRSWSSWUSWVSWWSWYSYASYBSYCSYDSYGS\
             YHSYKSYMSYNSYRSYSSYUSYVSYWSYYUAAUABUACUADUAGUAHUAKUAMUANUARUASUAUUAVUAWUAYUBAUBBUBCUBD\
             UBGUBHUBKUBMUBNUBRUBSUBUUBVUBWUBYUCAUCBUCCUCDUCGUCHUCKUCMUCNUCRUCSUCUUCVUCWUCYUDAUDBUD\
             CUDDUDGUDHUDKUDMUDNUDRUDSUDUUDVUDWUDYUGAUGBUGCUGDUGGUGHUGKUGMUGNUGRUGSUGUUGVUGWUGYUHAU\
             HBUHCUHDUHGUHHUHKUHMUHNUHRUHSUHUUHVUHWUHYUKAUKBUKCUKDUKGUKHUKKUKMUKNUKRUKSUKUUKVUKWUKY\
             UMAUMBUMCUMDUMGUMHUMKUMMUMNUMRUMSUMUUMVUMWUMYUNAUNBUNCUNDUNGUNHUNKUNMUNNUNRUNSUNUUNVUN\
             WUNYURAURBURCURDURGURHURKURMURNURRURSURUURVURWURYUSAUSBUSCUSDUSGUSHUSKUSMUSNUSRUSSUSUU\
             SVUSWUSYUUAUUBUUCUUDUUGUUHUUKUUMUUNUURUUSUUUUUVUUWUUYUVAUVBUVCUVDUVGUVHUVKUVMUVNUVRUVS\
             UVUUVVUVWUVYUWAUWBUWCUWDUWGUWHUWKUWMUWNUWRUWSUWUUWVUWWUWYUYAUYBUYCUYDUYGUYHUYKUYMUYNUY\
             RUYSUYUUYVUYWUYYVAAVABVACVADVAGVAHVAKVAMVANVARVASVAUVAVVAWVAYVBAVBBVBCVBDVBGVBHVBKVBMV\
             BNVBRVBSVBUVBVVBWVBYVCAVCBVCCVCDVCGVCHVCKVCMVCNVCRVCSVCUVCVVCWVCYVDAVDBVDCVDDVDGVDHVDK\
             VDMVDNVDRVDSVDUVDVVDWVDYVGAVGBVGCVGDVGGVGHVGKVGMVGNVGRVGSVGUVGVVGWVGYVHAVHBVHCVHDVHGVH\
             HVHKVHMVHNVHRVHSVHUVHVVHWVHYVKAVKBVKCVKDVKGVKHVKKVKMVKNVKRVKSVKUVKVVKWVKYVMAVMBVMCVMDV\
             MGVMHVMKVMMVMNVMRVMSVMUVMVVMWVMYVNAVNBVNCVNDVNGVNHVNKVNMVNNVNRVNSVNUVNVVNWVNYVRAVRBVRC\
             VRDVRGVRHVRKVRMVRNVRRVRSVRUVRVVRWVRYVSAVSBVSCVSDVSGVSHVSKVSMVSNVSRVSSVSUVSVVSWVSYVUAVU\
             BVUCVUDVUGVUHVUKVUMVUNVURVUSVUUVUVVUWVUYVVAVVBVVCVVDVVGVVHVVKVVMVVNVVRVVSVVUVVVVVWVVYV\
             WAVWBVWCVWDVWGVWHVWKVWMVWNVWRVWSVWUVWVVWWVWYVYAVYBVYCVYDVYGVYHVYKVYMVYNVYRVYSVYUVYVVYW\
             VYYWAAWABWACWADWAGWAHWAKWAMWANWARWASWAUWAVWAWWAYWBAWBBWBCWBDWBGWBHWBKWBMWBNWBRWBSWBUWB\
             VWBWWBYWCAWCBWCCWCDWCGWCHWCKWCMWCNWCRWCSWCUWCVWCWWCYWDAWDBWDCWDDWDGWDHWDKWDMWDNWDRWDSW\
             DUWDVWDWWDYWGAWGBWGCWGDWGGWGHWGKWGMWGNWGRWGSWGUWGVWGWWGYWHAWHBWHCWHDWHGWHHWHKWHMWHNWHR\
             WHSWHUWHVWHWWHYWKAWKBWKCWKDWKGWKHWKKWKMWKNWKRWKSWKUWKVWKWWKYWMAWMBWMCWMDWMGWMHWMKWMMWM\
             NWMRWMSWMUWMVWMWWMYWNAWNBWNCWNDWNGWNHWNKWNMWNNWNRWNSWNUWNVWNWWNYWRAWRBWRCWRDWRGWRHWRKW\
             RMWRNWRRWRSWRUWRVWRWWRYWSAWSBWSCWSDWSGWSHWSKWSMWSNWSRWSSWSUWSVWSWWSYWUAWUBWUCWUDWUGWUH\
             WUKWUMWUNWURWUSWUUWUVWUWWUYWVAWVBWVCWVDWVGWVHWVKWVMWVNWVRWVSWVUWVVWVWWVYWWAWWBWWCWWDWW\
             GWWHWWKWWMWWNWWRWWSWWUWWVWWWWWYWYAWYBWYCWYDWYGWYHWYKWYMWYNWYRWYSWYUWYVWYWWYYYAAYABYACY\
             ADYAGYAHYAKYAMYANYARYASYAUYAVYAWYAYYBAYBBYBCYBDYBGYBHYBKYBMYBNYBRYBSYBUYBVYBWYBYYCAYCB\
             YCCYCDYCGYCHYCKYCMYCNYCRYCSYCUYCVYCWYCYYDAYDBYDCYDDYDGYDHYDKYDMYDNYDRYDSYDUYDVYDWYDYYG\
             AYGBYGCYGDYGGYGHYGKYGMYGNYGRYGSYGUYGVYGWYGYYHAYHBYHCYHDYHGYHHYHKYHMYHNYHRYHSYHUYHVYHWY\
             HYYKAYKBYKCYKDYKGYKHYKKYKMYKNYKRYKSYKUYKVYKWYKYYMAYMBYMCYMDYMGYMHYMKYMMYMNYMRYMSYMUYMV\
             YMWYMYYNAYNBYNCYNDYNGYNHYNKYNMYNNYNRYNSYNUYNVYNWYNYYRAYRBYRCYRDYRGYRHYRKYRMYRNYRRYRSYR\
             UYRVYRWYRYYSAYSBYSCYSDYSGYSHYSKYSMYSNYSRYSSYSUYSVYSWYSYYUAYUBYUCYUDYUGYUHYUKYUMYUNYURY\
             USYUUYUVYUWYUYYVAYVBYVCYVDYVGYVHYVKYVMYVNYVRYVSYVUYVVYVWYVYYWAYWBYWCYWDYWGYWHYWKYWMYWN\
             YWRYWSYWUYWVYWWYWYYYAYYBYYCYYDYYGYYHYYKYYMYYNYYRYYSYYUYYVYYWYYY")?;
        let protein = rna.convert(Kind::Protein)?;
        assert_eq!(
            protein,
            Seq::protein_iupac(
                "KXNXKXXXXKXNXXNXXXXXXXXXXXXXXXTTTTTTTTTTTTTTTXXXXXXXXXXXXXXXRXSXRXXXXRXSXXSXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XIXIXMIXIXXXIXIIXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXQXHXQXXXXQXHXXHXXXXXXXXXXXXXXXPPPPPPPPPPPP\
                 PPPXXXXXXXXXXXXXXXRRRRRRRRRRRRRRRXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXLLLLLLLLLLLLLLLXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXEX\
                 DXEXXXXEXDXXDXXXXXXXXXXXXXXXAAAAAAAAAAAAAAAXXXXXXXXXXXXXXXGGGGGGGGGGGGGGGXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXV\
                 VVVVVVVVVVVVVVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXRXXXR\
                 XXXXRXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXBXXXXXXXXBXXBXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZXXXZXXXXZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXX*XYX*XXXX*XYXXYXXXXXXXXXXXXXXXSSSSSSSSSSSSSSSXXXXXXXXXXXXXXX*XCXWXX\
                 XXXXCXXCXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX*XXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXLXFXLXXXXLXFXXFXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXLXXXLXXXXLXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\
                 XXXXXXXXXXXXX")?,
        );
        Ok(())
    }

    #[test]
    fn dna_to_protein() -> Result<(), Error> {
        let dna = Seq::dna("ATGGCCATGGCGCCCAGAACTGAGATCAATAGTACCCGTATTAACGGGTGA")?;
        let protein = dna.convert(Kind::Protein)?;
        assert_eq!(protein, Seq::protein("MAMAPRTEINSTRING*")?);
        Ok(())
    }

    #[test]
    fn dna_lowercase_to_protein() -> Result<(), Error> {
        let dna = Seq::dna("atggccatggcgcccagaactgagatcaatagtacccgtattaacgggtga")?;
        let protein = dna.convert(Kind::Protein)?;
        assert_eq!(protein, Seq::protein("MAMAPRTEINSTRING*")?);
        Ok(())
    }

    #[test]
    fn rna_ambiguous_to_protein() -> Result<(), Error> {
        let rna = Seq::rna_n("AUGGCNAUGGCNCCNAGAACUGAGAUCAAUAGUACNCGUAUUAACGGNUGA")?;
        let protein = rna.convert(Kind::Protein)?;
        assert_eq!(protein, Seq::protein("MAMAPRTEINSTRING*")?);
        let rna = Seq::rna_iupac("AUGGCVAUGGCNCCNAGAACUGARAUCAAYAGUACNCGUAUHAAYGGNUAR")?;
        let protein = rna.convert(Kind::Protein)?;
        assert_eq!(protein, Seq::protein("MAMAPRTEINSTRING*")?);
        Ok(())
    }

    #[test]
    fn rna_ambiguous_to_ambiguous_protein() -> Result<(), Error> {
        let rna = Seq::rna_n("AUGGCCAUGGCGNCCAGAACUGAGAUCAAUNGUACCCGUAUNAACGGGNGA")?;
        let protein = rna.convert(Kind::Protein)?;
        assert_eq!(protein, Seq::protein_iupac("MAMAXRTEINXTRXNGX")?);
        let rna = Seq::rna_iupac("AUGRAYAUBGCGNCCAGAACUGAGAUCAAUWGUACCCGUAUNASCSARNGA")?;
        let protein = rna.convert(Kind::Protein)?;
        assert_eq!(protein, Seq::protein_iupac("MBXAXRTEINXTRXXZX")?);
        Ok(())
    }

    #[test]
    fn invalid_conversions() -> Result<(), Error> {
        let protein = Seq::protein("MAMAPRTEINSTRING")?;
        assert_eq!(
            protein.convert(Kind::Dna),
            Err(Error::InvalidConversion(Kind::Protein, Kind::Dna))
        );
        assert_eq!(
            protein.convert(Kind::Rna),
            Err(Error::InvalidConversion(Kind::Protein, Kind::Rna))
        );
        Ok(())
    }

    // ===== Reverse Complement Tool Tests =========================================================

    #[test]
    fn reverse_complement_dna() -> Result<(), Error> {
        let dna = Seq::dna("AAAACCCGGT")?;
        assert_eq!(dna.reverse_complement()?.bytes, b"ACCGGGTTTT");
        Ok(())
    }

    #[test]
    fn reverse_complement_dna_keep_case() -> Result<(), Error> {
        let dna = Seq::dna("aaaacCCGGT")?;
        assert_eq!(dna.reverse_complement()?.bytes, b"ACCGGgtttt");
        Ok(())
    }

    #[test]
    fn reverse_complement_rna() -> Result<(), Error> {
        let rna = Seq::rna("AAAACCCGGU")?;
        assert_eq!(rna.reverse_complement()?.bytes, b"ACCGGGUUUU");
        Ok(())
    }

    #[test]
    fn reverse_complement_rna_keep_case() -> Result<(), Error> {
        let rna = Seq::rna("aaaacCCGGU")?;
        assert_eq!(rna.reverse_complement()?.bytes, b"ACCGGguuuu");
        Ok(())
    }

    #[test]
    fn reverse_complement_protein() -> Result<(), Error> {
        let protein = Seq::protein("MAMAPRTEINSTRING")?;
        assert_eq!(
            protein.reverse_complement(),
            Err(Error::ReverseComplement(Kind::Protein))
        );
        Ok(())
    }

    // ===== Error Formatting Tests ================================================================

    #[test]
    fn format_errors() {
        assert_eq!(
            &Error::FindOrf(Kind::Protein).to_string(),
            "Cannot find ORFs in a Protein"
        );
        assert_eq!(
            &Error::InvalidConversion(Kind::Protein, Kind::Dna).to_string(),
            "Cannot convert Protein to DNA"
        );
        assert_eq!(
            &Error::InvalidConversion(Kind::Protein, Kind::Rna).to_string(),
            "Cannot convert Protein to RNA"
        );
        assert_eq!(
            Error::InvalidSeq(vec![
                (Kind::Dna, Alphabet::Base),
                (Kind::Rna, Alphabet::Base),
                (Kind::Dna, Alphabet::N),
                (Kind::Rna, Alphabet::N),
                (Kind::Protein, Alphabet::Base),
                (Kind::Dna, Alphabet::Iupac),
                (Kind::Rna, Alphabet::Iupac),
                (Kind::Protein, Alphabet::Iupac)
            ])
            .to_string(),
            "The provided sequence was not valid DNA (Base), RNA (Base), DNA (N), RNA (N), Protein (Base), DNA (IUPAC), RNA (IUPAC), or Protein (IUPAC)"
        );
        assert_eq!(
            Error::InvalidSeq(vec![
                (Kind::Dna, Alphabet::Base),
                (Kind::Dna, Alphabet::N),
                (Kind::Dna, Alphabet::Iupac),
            ])
            .to_string(),
            "The provided sequence was not valid DNA (Base), DNA (N), or DNA (IUPAC)"
        );
        assert_eq!(
            Error::InvalidSeq(vec![
                (Kind::Rna, Alphabet::Base),
                (Kind::Rna, Alphabet::N),
                (Kind::Rna, Alphabet::Iupac),
            ])
            .to_string(),
            "The provided sequence was not valid RNA (Base), RNA (N), or RNA (IUPAC)"
        );
        assert_eq!(
            Error::InvalidSeq(vec![
                (Kind::Protein, Alphabet::Base),
                (Kind::Protein, Alphabet::Iupac)
            ])
            .to_string(),
            "The provided sequence was not valid Protein (Base), or Protein (IUPAC)"
        );
        assert_eq!(
            &Error::ReverseComplement(Kind::Protein).to_string(),
            "Cannot reverse complement Protein"
        );
        assert_eq!(
            &Error::GcContent(Kind::Protein).to_string(),
            "Cannot provide GC content for Protein"
        );
        assert_eq!(
            &Error::HammingDistance(4, 9).to_string(),
            "Cannot compute Hamming distance between sequences of different lengths (4 and 9)"
        );
        assert_eq!(
            &Error::DistanceKindMismatch(Kind::Dna, Kind::Protein).to_string(),
            "Cannot compute distance between DNA and Protein"
        );
        assert_eq!(
            &Error::DistanceKindMismatch(Kind::Dna, Kind::Rna).to_string(),
            "Cannot compute distance between DNA and RNA"
        );
        assert_eq!(
            &Error::DistanceKindMismatch(Kind::Protein, Kind::Rna).to_string(),
            "Cannot compute distance between Protein and RNA"
        );
    }
}
