use std::ffi::CStr;
use std::sync::Mutex;
use crate::error::{Primer3Error, Result};
static ALIGN_MUTEX: Mutex<()> = Mutex::new(());
pub const MAX_ALIGN_LENGTH: usize = primer3_sys::DPAL_MAX_ALIGN;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AlignmentMode {
#[default]
Local,
GlobalEnd,
Global,
LocalEnd,
}
impl AlignmentMode {
fn to_c(self) -> std::os::raw::c_int {
match self {
Self::Local => primer3_sys::DPAL_LOCAL,
Self::GlobalEnd => primer3_sys::DPAL_GLOBAL_END,
Self::Global => primer3_sys::DPAL_GLOBAL,
Self::LocalEnd => primer3_sys::DPAL_LOCAL_END,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AlignmentOutput {
#[default]
ScoreOnly,
Full,
Structure,
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct AlignmentArgs {
pub mode: AlignmentMode,
pub output: AlignmentOutput,
pub gap: i32,
pub gap_ext: i32,
pub max_gap: i32,
pub score_max: i32,
pub use_ambiguity_codes: bool,
}
impl Default for AlignmentArgs {
fn default() -> Self {
Self {
mode: AlignmentMode::default(),
output: AlignmentOutput::default(),
gap: -100,
gap_ext: -100,
max_gap: 3,
score_max: 0,
use_ambiguity_codes: false,
}
}
}
#[derive(Debug, Clone)]
pub struct AlignmentResult {
score: f64,
path_length: usize,
align_end_1: i32,
align_end_2: i32,
secondary_structure: Option<String>,
}
impl AlignmentResult {
pub fn score(&self) -> f64 {
self.score
}
pub fn path_length(&self) -> usize {
self.path_length
}
pub fn align_end_1(&self) -> i32 {
self.align_end_1
}
pub fn align_end_2(&self) -> i32 {
self.align_end_2
}
pub fn secondary_structure(&self) -> Option<&str> {
self.secondary_structure.as_deref()
}
}
pub fn align(seq1: &str, seq2: &str, args: &AlignmentArgs) -> Result<AlignmentResult> {
if seq1.is_empty() || seq2.is_empty() {
return Err(Primer3Error::InvalidSequence("sequence is empty".into()));
}
let max_len = seq1.len().max(seq2.len());
if max_len > MAX_ALIGN_LENGTH {
return Err(Primer3Error::SequenceTooLong {
length: max_len,
max: MAX_ALIGN_LENGTH,
operation: "alignment",
});
}
let mut c_args: Box<primer3_sys::dpal_args> = Box::new(unsafe { std::mem::zeroed() });
unsafe {
primer3_sys::set_dpal_args(c_args.as_mut());
primer3_sys::dpal_set_default_nt_args(c_args.as_mut());
}
if args.use_ambiguity_codes {
let ok = unsafe { primer3_sys::dpal_set_ambiguity_code_matrix(c_args.as_mut()) };
if ok == 0 {
return Err(Primer3Error::Library("failed to set ambiguity code matrix".into()));
}
}
c_args.flag = args.mode.to_c();
c_args.gap = args.gap;
c_args.gapl = args.gap_ext;
c_args.max_gap = args.max_gap;
c_args.score_max = args.score_max;
let mode = match args.output {
AlignmentOutput::ScoreOnly => primer3_sys::DPM_FAST,
AlignmentOutput::Full => primer3_sys::DPM_GENERAL,
AlignmentOutput::Structure => primer3_sys::DPM_STRUCT,
};
let mut s1: Vec<u8> = seq1.bytes().map(|b| b.to_ascii_uppercase()).collect();
s1.push(0);
let mut s2: Vec<u8> = seq2.bytes().map(|b| b.to_ascii_uppercase()).collect();
s2.push(0);
let mut results: Box<primer3_sys::dpal_results> = Box::new(unsafe { std::mem::zeroed() });
let _lock = ALIGN_MUTEX.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
unsafe {
primer3_sys::dpal(s1.as_ptr(), s2.as_ptr(), c_args.as_ref(), mode, results.as_mut());
}
if (results.score as i64) == i64::from(i32::MIN) {
let msg = if results.msg.is_null() {
"alignment failed".to_string()
} else {
unsafe { CStr::from_ptr(results.msg) }.to_string_lossy().into_owned()
};
return Err(Primer3Error::Library(msg.into_boxed_str()));
}
let secondary_structure = if results.sec_struct.is_null() {
None
} else {
let s = unsafe { CStr::from_ptr(results.sec_struct) }.to_string_lossy().into_owned();
unsafe { libc::free(results.sec_struct.cast::<std::ffi::c_void>()) };
Some(s)
};
Ok(AlignmentResult {
score: results.score,
path_length: results.path_length as usize,
align_end_1: results.align_end_1,
align_end_2: results.align_end_2,
secondary_structure,
})
}