use std::ffi::CString;
use std::os::raw::c_int;
use crate::conditions::SolutionConditions;
use crate::error::Result;
use crate::init::ensure_initialized;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum TmMethod {
Breslauer,
#[default]
SantaLucia,
SantaLucia2004,
}
impl TmMethod {
pub(crate) fn to_c(self) -> u32 {
match self {
Self::Breslauer => primer3_sys::tm_method_type_breslauer_auto,
Self::SantaLucia => primer3_sys::tm_method_type_santalucia_auto,
Self::SantaLucia2004 => primer3_sys::tm_method_type_santalucia_2004,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub enum SaltCorrectionMethod {
Schildkraut,
#[default]
SantaLucia,
Owczarzy,
}
impl SaltCorrectionMethod {
pub(crate) fn to_c(self) -> u32 {
match self {
Self::Schildkraut => primer3_sys::salt_correction_type_schildkraut,
Self::SantaLucia => primer3_sys::salt_correction_type_santalucia,
Self::Owczarzy => primer3_sys::salt_correction_type_owczarzy,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TmParams {
pub conditions: SolutionConditions,
pub annealing_temp_c: f64,
pub max_nn_length: usize,
pub tm_method: TmMethod,
pub salt_correction_method: SaltCorrectionMethod,
}
impl Default for TmParams {
fn default() -> Self {
Self {
conditions: SolutionConditions::default(),
annealing_temp_c: -10.0,
max_nn_length: 60,
tm_method: TmMethod::default(),
salt_correction_method: SaltCorrectionMethod::default(),
}
}
}
pub fn calc_tm(seq: &str) -> Result<f64> {
calc_tm_with(seq, &TmParams::default())
}
pub fn calc_oligodg(seq: &str) -> Result<f64> {
calc_oligodg_with(seq, TmMethod::default())
}
pub fn calc_oligodg_with(seq: &str, tm_method: TmMethod) -> Result<f64> {
if seq.is_empty() {
return Err(crate::error::Primer3Error::InvalidSequence("sequence is empty".into()));
}
let c_seq = CString::new(seq.to_ascii_uppercase()).map_err(|_| {
crate::error::Primer3Error::InvalidSequence("sequence contains null byte".into())
})?;
let result = unsafe { primer3_sys::oligodg(c_seq.as_ptr(), tm_method.to_c() as c_int) };
Ok(result)
}
pub fn calc_end_oligodg(seq: &str, len: usize) -> Result<f64> {
calc_end_oligodg_with(seq, len, TmMethod::default())
}
pub fn calc_end_oligodg_with(seq: &str, len: usize, tm_method: TmMethod) -> Result<f64> {
if seq.is_empty() {
return Err(crate::error::Primer3Error::InvalidSequence("sequence is empty".into()));
}
let c_seq = CString::new(seq.to_ascii_uppercase()).map_err(|_| {
crate::error::Primer3Error::InvalidSequence("sequence contains null byte".into())
})?;
let result = unsafe {
primer3_sys::end_oligodg(c_seq.as_ptr(), len as c_int, tm_method.to_c() as c_int)
};
Ok(result)
}
pub fn calc_tm_with(seq: &str, params: &TmParams) -> Result<f64> {
ensure_initialized()?;
if seq.is_empty() {
return Err(crate::error::Primer3Error::InvalidSequence("sequence is empty".into()));
}
let c_seq = CString::new(seq.to_ascii_uppercase()).map_err(|_| {
crate::error::Primer3Error::InvalidSequence("sequence contains null byte".into())
})?;
let result = unsafe {
primer3_sys::seqtm(
c_seq.as_ptr(),
params.conditions.dna_conc,
params.conditions.mv_conc,
params.conditions.dv_conc,
params.conditions.dntp_conc,
params.conditions.dmso_conc,
params.conditions.dmso_fact,
params.conditions.formamide_conc,
params.max_nn_length as c_int,
params.tm_method.to_c(),
params.salt_correction_method.to_c(),
params.annealing_temp_c,
)
};
if result.Tm < -999_999.0 {
return Err(crate::error::Primer3Error::InvalidSequence(
"Tm calculation failed (invalid sequence or parameters)".into(),
));
}
Ok(result.Tm)
}