use na_seq::{
AminoAcid, CodingResult, Nucleotide, Seq, insert_into_seq,
ligation::{filter_multiple_seqs, filter_unique_cutters, find_common_res},
restriction_enzyme::{ReMatch, RestrictionEnzyme, find_re_matches},
seq_to_str_lower,
};
use crate::{
Selection,
backbones::Backbone,
file_io::GenericData,
gui::navigation::{Page, PageSeq},
misc_types::{Feature, FeatureDirection, FeatureType},
primer::{Primer, make_cloning_primers},
state::State,
util::RangeIncl,
};
pub const RE_INSERT_BUFFER: usize = 22;
pub const RBS_BUFFER: usize = 7;
pub const RBS_BUFFER_MIN: isize = 4;
pub const RBS_BUFFER_MAX: isize = 11;
pub struct CloningState {
pub backbone_selected: BackboneSelected,
pub backbone: Option<Backbone>, pub res_common: Vec<RestrictionEnzyme>,
pub re_matches_vec_common: Vec<ReMatch>,
pub re_matches_insert_common: Vec<ReMatch>,
pub status: CloneStatus, pub insert_loc: usize,
pub data_insert: Option<GenericData>,
pub remove_stop_codons: bool,
pub product_seq: Seq,
pub product_primers: Vec<Primer>,
}
impl Default for CloningState {
fn default() -> Self {
Self {
insert_loc: 1,
backbone_selected: Default::default(),
backbone: Default::default(),
res_common: Default::default(),
re_matches_vec_common: Default::default(),
re_matches_insert_common: Default::default(),
status: Default::default(),
data_insert: Default::default(),
remove_stop_codons: Default::default(),
product_seq: Default::default(),
product_primers: Vec::new(),
}
}
}
impl CloningState {
pub fn sync(
&mut self,
seq_insert: &mut Seq,
backbone_lib: &[Backbone],
re_lib: &[RestrictionEnzyme],
) {
println!("Syncing cloning state...");
let backbone = match self.backbone_selected {
BackboneSelected::Library(i) => {
if i >= backbone_lib.len() {
eprintln!("Invalid index in backbone lib");
None
} else {
Some(&backbone_lib[i])
}
}
BackboneSelected::Opened => match self.backbone.as_ref() {
Some(i) => Some(i),
None => None,
},
};
if let Some(backbone) = backbone {
self.product_seq = backbone.seq.clone();
if seq_insert.len() < 3 {
return;
}
let insert_len = seq_insert.len();
if self.remove_stop_codons
&& AminoAcid::from_codons(seq_insert[insert_len - 3..].try_into().unwrap())
== CodingResult::StopCodon
{
*seq_insert = seq_insert[..insert_len - 3].try_into().unwrap();
}
insert_into_seq(&mut self.product_seq, seq_insert, self.insert_loc).ok();
self.status = CloneStatus::new(
&backbone,
self.insert_loc,
seq_insert.len(),
&self.product_seq,
);
}
}
pub fn get_backbone<'a>(&'a self, lib: &'a [Backbone]) -> Option<&'a Backbone> {
match self.backbone_selected {
BackboneSelected::Library(i) => {
if i >= lib.len() {
eprintln!("Invalid index in backbone lib");
None
} else {
Some(&lib[i])
}
}
BackboneSelected::Opened => Some(self.backbone.as_ref().unwrap()),
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum BackboneSelected {
Library(usize),
Opened,
}
impl Default for BackboneSelected {
fn default() -> Self {
Self::Opened
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum Status {
Pass,
Fail,
NotApplicable,
}
impl Default for Status {
fn default() -> Self {
Self::Fail
}
}
#[derive(Default)]
pub struct CloningInsertData {
pub features_loaded: Vec<Feature>,
pub seq_loaded: Seq,
pub feature_selected: Option<usize>,
pub seq_insert: Seq,
pub seq_input: String,
pub show_insert_picker: bool,
}
pub fn setup_insert_seqs(state: &mut State, features: Vec<Feature>, seq: Seq) {
state.ui.cloning_insert.features_loaded = features;
state.ui.cloning_insert.seq_loaded = seq;
let mut best = None;
let mut best_len = 0;
for (i, feature) in state.ui.cloning_insert.features_loaded.iter().enumerate() {
let len = feature.len(state.generic[state.active].seq.len());
if (feature.feature_type == FeatureType::CodingRegion
|| feature.feature_type == FeatureType::Gene)
&& len > best_len
{
best_len = len;
best = Some(i);
}
}
if let Some(feat_i) = best {
let feature = &state.ui.cloning_insert.features_loaded[feat_i];
if let Some(seq_this_ft) = feature.range.index_seq(&state.ui.cloning_insert.seq_loaded) {
state.ui.cloning_insert.feature_selected = best;
state.ui.cloning_insert.seq_insert = seq_this_ft.to_owned();
state.ui.cloning_insert.seq_input = seq_to_str_lower(seq_this_ft);
}
}
}
pub fn make_product_tab(state: &mut State, generic: Option<GenericData>) {
let generic = match generic {
Some(gen_) => gen_,
None => state.generic[state.active].clone(),
};
state.generic.push(generic);
state.portions.push(Default::default());
state.volatile.push(Default::default());
state.tabs_open.push(Default::default());
state.ab1_data.push(Default::default());
state.active = state.generic.len() - 1;
make_cloning_primers(state);
let mut insert = state.ui.cloning_insert.seq_insert.clone();
state.insert_nucleotides(&insert, state.cloning.insert_loc);
let label = match state.ui.cloning_insert.feature_selected {
Some(i) => state.ui.cloning_insert.features_loaded[i].label.clone(),
None => "Cloning insert".to_owned(),
};
state.generic[state.active].features.push(Feature {
range: RangeIncl::new(
state.cloning.insert_loc,
state.cloning.insert_loc + insert.len() - 1,
),
label,
feature_type: FeatureType::CodingRegion,
direction: FeatureDirection::Forward,
..Default::default()
});
"Cloning product".clone_into(&mut state.generic[state.active].metadata.plasmid_name);
state.ui.page = Page::Map;
state.ui.page_seq = PageSeq::View;
state.ui.selected_item = Selection::Feature(state.generic[state.active].features.len() - 1);
}
fn tag_in_frame(
seq_product: &[Nucleotide],
his_tag: &Option<RangeIncl>,
coding_region_start: usize,
) -> Status {
if seq_product.is_empty() {
eprintln!("Error checking tag frame: Product sequence is empty");
return Status::NotApplicable;
}
match his_tag {
Some(tag_range) => {
let tag_start = tag_range.start;
let cr_start = coding_region_start % seq_product.len();
if cr_start > tag_start {
eprintln!(
"Error with insert loc and tag end. Coding region start: {cr_start}, tag start: {tag_start}"
);
return Status::Fail;
}
println!("Tag: {tag_start}, CR: {coding_region_start}");
for i in (cr_start..tag_start).step_by(3) {
if i + 2 >= seq_product.len() {
eprintln!("Error: Invalid backbone seq in his check");
return Status::Fail;
}
if let CodingResult::StopCodon = AminoAcid::from_codons([
seq_product[i - 1],
seq_product[i + 0],
seq_product[i + 1],
]) {
println!("Stop codon between seq start and his tag found at index {i}"); return Status::Fail;
}
}
let dist_from_start_codon = tag_start - cr_start;
if dist_from_start_codon % 3 == 0 {
Status::Pass
} else {
Status::Fail
}
}
None => Status::NotApplicable,
}
}
pub fn find_re_candidates<'a>(
backbone: &Backbone,
seq_insert: &[Nucleotide],
lib: &'a [RestrictionEnzyme],
) -> (Vec<RestrictionEnzyme>, Vec<ReMatch>, Vec<ReMatch>) {
let matches_insert = find_re_matches(seq_insert, lib);
let matches_backbone = find_re_matches(&backbone.seq, lib);
let re_match_set = [&matches_insert, &matches_backbone];
let mut result = find_common_res(&re_match_set, lib, true);
filter_multiple_seqs(&mut result, &re_match_set, lib);
filter_unique_cutters(&mut result, &re_match_set, lib);
let res = result.into_iter().map(|r| r.clone()).collect();
let mut matches_vector_common = Vec::new();
let mut matches_insert_common = Vec::new();
for match_insert in &matches_insert {
for match_bb in &matches_backbone {
if match_insert.lib_index == match_bb.lib_index {
matches_vector_common.push(match_bb.clone());
matches_insert_common.push(match_insert.clone());
}
}
}
(res, matches_vector_common, matches_insert_common)
}
#[derive(Default)]
pub struct CloneStatus {
pub rbs_dist: Status,
pub downstream_of_promoter: Status,
pub upstream_of_terminator: Status,
pub direction: Status,
pub tag_frame: Status,
}
impl CloneStatus {
pub fn new(
backbone: &Backbone,
insert_loc: usize,
insert_len: usize,
seq_product: &[Nucleotide],
) -> Self {
let rbs_dist = match backbone.rbs {
Some(rbs) => {
let dist = insert_loc as isize - rbs.end as isize;
if dist >= RBS_BUFFER_MIN && dist <= RBS_BUFFER_MAX {
Status::Pass
} else {
Status::Fail
}
}
None => Status::NotApplicable,
};
let downstream_of_promoter = match backbone.promoter {
Some(p) => {
if insert_loc > p.end {
Status::Pass
} else {
Status::Fail
}
}
None => Status::NotApplicable,
};
let upstream_of_terminator = match backbone.terminator {
Some(p) => {
if insert_loc < p.end {
Status::Pass
} else {
Status::Fail
}
}
None => Status::NotApplicable,
};
let direction = Status::Pass;
let his_tag_shifted = match backbone.his_tag {
Some(tag) => Some(RangeIncl::new(tag.start + insert_len, tag.end + insert_len)),
None => None,
};
let tag_frame = tag_in_frame(seq_product, &his_tag_shifted, insert_loc);
Self {
rbs_dist,
downstream_of_promoter,
upstream_of_terminator,
direction,
tag_frame,
}
}
}