use std::path::Path;
use crate::helper::types::DataType;
pub const DNA_STR_UPPERCASE: &str = "?-ACGTNRYSWKMBDHV.";
pub const AA_STR_UPPERCASE: &str = "?-ARNDCQEGHILKMFPSTWYVYXBZJU*.~";
const DNA: &[u8] = b"?-ACGTRYSWKMBDHVNacgtryswkmbdhvn.";
const AA: &[u8] = b"?-ARNDCQEGHILKMFPSTWYVYXBZJU*.~";
pub fn check_valid_seq(input: &Path, datatype: &DataType, id: &str, seq: &str) {
match datatype {
DataType::Dna => check_valid_dna(input, id, seq),
DataType::Aa => check_valid_aa(input, id, seq),
DataType::Ignore => (),
}
}
pub fn check_valid_dna(input: &Path, id: &str, dna: &str) {
if !is_valid_dna(dna) {
panic!(
"Ups... The sequence {} in file {} is not a dna sequence. \
Check whether the sequence is amino acid",
id,
input.display()
);
}
}
fn check_valid_aa(input: &Path, id: &str, aa: &str) {
if !is_valid_aa(aa) {
panic!(
"Ups... The sequence {} in file {} is not an amino acid sequence.",
id,
input.display()
);
}
}
pub fn is_valid_dna(dna: &str) -> bool {
is_valid(dna.as_bytes(), &DNA_TABLE)
}
pub fn is_valid_aa(aa: &str) -> bool {
is_valid(aa.as_bytes(), &AA_TABLE)
}
const DNA_TABLE: [bool; 256] = build_alphabet_table(DNA);
const AA_TABLE: [bool; 256] = build_alphabet_table(AA);
const fn build_alphabet_table(alphabet: &[u8]) -> [bool; 256] {
let mut table = [false; 256];
let mut i = 0;
while i < alphabet.len() {
table[alphabet[i] as usize] = true;
i += 1;
}
table
}
#[inline]
fn is_valid(seq: &[u8], table: &[bool; 256]) -> bool {
seq.chunks(64)
.all(|chunk| chunk.iter().fold(true, |acc, &b| acc & table[b as usize]))
}
const DNA_COMPLEMENT: [u8; 256] = build_dna_complement();
const fn build_dna_complement() -> [u8; 256] {
let mut table = [0u8; 256];
let mut i = 0;
while i < 256 {
table[i] = i as u8;
i += 1;
}
let pairs: [(u8, u8); 11] = [
(b'A', b'T'),
(b'T', b'A'),
(b'U', b'A'),
(b'C', b'G'),
(b'G', b'C'),
(b'R', b'Y'),
(b'Y', b'R'),
(b'K', b'M'),
(b'M', b'K'),
(b'B', b'V'),
(b'V', b'B'),
];
let mut j = 0;
while j < pairs.len() {
let (from, to) = pairs[j];
table[from as usize] = to;
table[from.to_ascii_lowercase() as usize] = to.to_ascii_lowercase();
j += 1;
}
table[b'D' as usize] = b'H';
table[b'H' as usize] = b'D';
table[b'd' as usize] = b'h';
table[b'h' as usize] = b'd';
table
}
pub fn complement_dna(base: u8) -> u8 {
DNA_COMPLEMENT[base as usize]
}
pub fn reverse_complement_dna(seq: &[u8]) -> Vec<u8> {
seq.iter().rev().map(|&base| complement_dna(base)).collect()
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_reverse_complement_dna() {
assert_eq!(reverse_complement_dna(b"ACGTU"), b"AACGT".to_vec());
assert_eq!(reverse_complement_dna(b"acgt"), b"acgt".to_vec());
assert_eq!(reverse_complement_dna(b"RYKMBVDH"), b"DHBVKMRY".to_vec());
assert_eq!(reverse_complement_dna(b"SWN"), b"NWS".to_vec());
assert_eq!(reverse_complement_dna(b"A-?.*"), b"*.?-T".to_vec());
}
#[test]
fn test_check_valid_dna() {
let dna = String::from("agtc?-");
assert!(is_valid_dna(&dna));
}
#[test]
fn test_check_invalid_dna() {
let dna = String::from("agtc?-z");
assert!(!is_valid_dna(&dna));
}
#[test]
#[should_panic]
fn test_check_invalid_dna_panic() {
let sample = Path::new(".");
let id = "ABCD";
let dna = String::from("agta?-z");
check_valid_dna(sample, id, &dna);
}
#[test]
fn test_alphabet_tables_match_alphabets() {
for b in 0..=255u8 {
assert_eq!(DNA.contains(&b), DNA_TABLE[b as usize]);
assert_eq!(AA.contains(&b), AA_TABLE[b as usize]);
}
assert!(!is_valid_dna("acgt-?NX"));
assert!(!is_valid_aa("ARNDa"));
}
}