1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
//mod domain;
//mod commute;
//use domain::*;
use super::{Design, Domain, Helix, Strand};

#[derive(Clone, Debug)]

/// An atomic modification of an existing design. Patches have operation to permute them or undo
/// them.
pub enum Patch {
    /// ElongateDomain(h, b1, n, b2, n2) elongate the b2 end of domain whose b2 end is (h,
    /// b1, n1) by n2 nucleotides
    ElongateDomain(usize, bool, isize, bool, usize),

    /// ShortenDomain(h, b1, n, b2, n2) shorten the b2 end of domain whose b2 end is (h,
    /// b1, n1) by n2 nucleotides
    ShortenDomain(usize, bool, isize, bool, usize),

    /// AddHelix(h) add helix h
    AddHelix(Helix),

    /// DeleteHelix(n) delete helix with identifier n
    DeleteHelix(usize),

    /// Add Strand (h, b, n) add a strand on helix b at position n on the right strand if b on the
    /// left strand otherwise
    AddStrand(usize, bool, isize),

    /// DeleteStrand(h, b, n) delete the strand containing only the nucleotide (h, b, n)
    DeleteStrand(usize, bool, isize),

    /// CrossOver(s, b1, h, n, b2) the b1 (5' if true, 3' if false) end strand s jumps to helix h
    /// at position n on the right strand if b, on the left strand otherwise
    CrossOver(usize, bool, usize, isize, bool),

    /*
    /// Merge(s1, s2) merge the strands s1 and s2. The end of strand s1 must be adjacent to the
    /// begining of strand s2. The strand at index s2 is deleted, the strand at index s1 is
    /// modified: all the domains of s2 are appenend to the domains of s1. The index s1 is
    /// unchanged if s1 < s2, and decremented by 1 if s1 > s2.
    Merge(usize, usize),
    */

    /*
    /// Split(s, n) split the strand s at its n-th nucleotide
    Split(usize, usize),
    */
    /// MakeBound(h1, b1, n1, h2, b2, n2) Make a bound between the nucleotides (h1, b1, n1)
    /// and (h2, b2, n2)
    MakeBound(usize, bool, isize, usize, bool, isize),

    /// CutBound(h1, b1, n1, h2, b2, n2) Cut the bound between the nucleotides (h1, b1, n1)
    /// and (h2, b2, n2)
    CutBound(usize, bool, isize, usize, bool, isize),
}

impl Patch {
    /*
    pub fn in_domain<S, T>(&self, design: &Design<S, T>) -> bool {
        match self {
            Patch::InsertNucl(s, b, n) => domain_insert_nucl(design, *s, *b, *n),
            Patch::SuppNucl(s, b, n) => domain_suppr_nucl(design, *s, *b, *n),
            Patch::InsertHelix(_) => true, // TODO check intersection of helices
            Patch::DeleteHelix(h) => domain_suppr_helix(design, *h),
            Patch::AddStrand(h, b, n) => domain_add_strand(design, *h, *b, *n),
            Patch::DeleteStrand(s) => domain_delete_strand(design, *s),
            Patch::CrossOver(s, b1, h, n, b2) => domain_cross_over(design, *s, *h, *n, *b2),
            Patch::Merge(s1, s2) => domain_merge(design, *s1, *s2),
            Patch::Split(s, n) => domain_split(design, *s, *n),
        }
    }

    pub fn commute<S, T>(&self, patch: &Patch, design: &Design<S, T>,
                         context: &Design<S, T>) -> Option<(Patch, Patch)> {
        match self {
            Patch::InsertNucl(s, b, n) => commute::insert_nucl(patch, design, context, *s, *b, *n),
            Patch::SuppNucl(s, b, n) => commute::supp_nucl(patch, design, context, *s, *b, *n),
            Patch::InsertHelix(_) => Some((patch.clone(), self.clone())),
            Patch::DeleteHelix(n) => commute::delete_helix(patch, design, *n),
            _ => None
        }
    }
    */

    /// Apply self on a design
    pub fn apply<S: serde::Serialize, T: serde::Serialize>(&self, design: &mut Design<S, T>) {
        /*
        if !self.in_domain(design) {
            panic!("Patch domain error");
        }
        */
        match self {
            Patch::ElongateDomain(h, b1, n1, b2, n2) => {
                let s_id = design
                    .get_strand_nucl(*h as isize, *n1, *b1)
                    .expect("get nucl elgonate domain");
                let strand = &mut design.strands[s_id];
                let mut dom_num = 0;
                for i in 0..strand.domains.len() {
                    if strand.domains[i].contains(*h as isize, *n1, *b1) {
                        dom_num = i;
                        break;
                    }
                }
                let domain = &mut strand.domains[dom_num];
                if !domain.contains(*h as isize, *n1, *b1) {
                    panic!("domain does not contains");
                }
                if *b2 == domain.forward {
                    domain.end += *n2 as isize
                } else {
                    domain.start -= *n2 as isize
                };
            }
            Patch::ShortenDomain(h, b1, n1, b2, n2) => {
                let s_id = design
                    .get_strand_nucl(*h as isize, *n1, *b1)
                    .expect("get nucl shorten domain");
                let strand = &mut design.strands[s_id];
                let mut dom_num = 0;
                for i in 0..strand.domains.len() {
                    if strand.domains[i].contains(*h as isize, *n1, *b1) {
                        dom_num = i;
                        break;
                    }
                }
                let domain = &mut strand.domains[dom_num];
                if !domain.contains(*h as isize, *n1, *b1) {
                    panic!("domain does not contains");
                }
                if *b2 == domain.forward {
                    domain.end -= *n2 as isize
                } else {
                    domain.start += *n2 as isize
                };
            }
            Patch::AddHelix(h) => {
                design.add_helix(h.position.x, h.position.y, h.position.z, h.roll, h.pitch, h.yaw);
            }
            Patch::DeleteHelix(h) => {
                design.helices.remove(*h);
            }
            Patch::AddStrand(h, b, n) => {
                design.strands.push(Strand {
                    domains: vec![Domain {
                        helix: *h as isize,
                        start: *n,
                        end: *n + 1,
                        forward: *b,
                        label: None,
                        sequence: None,
                    }],
                    label: None,
                    sequence: None,
                    cyclic: false,
                    color: None,
                });
            }
            Patch::DeleteStrand(h, b, n) => {
                if let Some(s_id) = design.get_strand_nucl(*h as isize, *n, *b) {
                    design.strands.remove(s_id);
                }
            }
            Patch::CrossOver(s, b1, h, n, b2) => {
                let new_dom = Domain {
                    helix: *h as isize,
                    start: *n,
                    end: *n + 1,
                    forward: *b2,
                    label: None,
                    sequence: None,
                };
                if *b1 {
                    design.strands[*s].domains.push(new_dom);
                } else {
                    design.strands[*s].domains.insert(0, new_dom);
                }
            }
            Patch::MakeBound(h1, b1, n1, h2, b2, n2) => {
                //let strand1 = &design.strands[*s1];
                let s1 = design
                    .get_strand_nucl(*h1 as isize, *n1, *b1)
                    .expect("bounding unexisting nucl");
                let s2 = design
                    .get_strand_nucl(*h2 as isize, *n2, *b2)
                    .expect("bounding unexisting nucl");
                if s1 != s2 {
                    for i in 0..design.strands[s2].domains.len() {
                        let domain = design.strands[s2].domains[i].pseudo_copy();
                        design.strands[s1].domains.push(domain);
                    }
                    design.strands.remove(s2);
                } else {
                    let builder = design.get_strand_ref(s1);
                    builder.cycle();
                }
            }
            Patch::CutBound(h1, b1, n1, h2, b2, n2) => {
                let h1 = *h1 as isize;
                let s1 = design
                    .get_strand_nucl(h1, *n1, *b1)
                    .expect("cuting on unexisting nucl");
                let strand = &mut design.strands[s1];
                if !strand.cyclic {
                    let mut dom = 0;
                    let mut old_start = 0;
                    let mut old_end = 0;
                    let mut split_dom = true;
                    for i in 0..strand.domains.len() {
                        let domain = &mut strand.domains[i];
                        if domain.contains(h1, *n1, *b1) {
                            //shorten the strand
                            old_start = domain.start;
                            old_end = domain.end;
                            let contains = domain.contains(*h2 as isize, *n2, *b2);
                            if domain.forward {
                                domain.end = *n1 + 1;
                            } else {
                                domain.start = *n1;
                            }

                            // set the start of the new strand
                            if !contains {
                                old_start = strand.domains[i + 1].start;
                                old_end = strand.domains[i + 1].end;
                                dom = i + 1;
                                split_dom = false;
                            } else {
                                dom = i;
                            }
                            break;
                        }
                    }

                    let old_domain = &strand.domains[dom];
                    let new_domain = Domain {
                        start: {
                            if old_domain.forward {
                                *n2
                            } else {
                                old_start
                            }
                        },
                        end: {
                            if old_domain.forward {
                                old_end
                            } else {
                                *n2 + 1
                            }
                        },
                        label: None,
                        sequence: None,
                        ..*old_domain
                    };
                    let nb_dom = strand.domains.len();
                    let mut domains = vec![new_domain];
                    for i in (dom + 1)..nb_dom {
                        let domain = strand.domains[i].pseudo_copy();
                        domains.push(domain);
                    }
                    if !split_dom {
                        dom -= 1;
                    }
                    for _ in (dom + 1)..nb_dom {
                        strand.domains.pop();
                    }
                    design.strands.push(Strand {
                        domains,
                        label: None,
                        sequence: None,
                        cyclic: false,
                        color: None,
                    });
                } else {
                    let mut domains = Vec::new();
                    let mut added_dom = None;
                    for i in 0..strand.domains.len() {
                        if strand.domains[i].contains(*h2 as isize, *n2, *b2) {
                            let contains = strand.domains[i].contains(h1, *n1, *b1);
                            let old_start = strand.domains[i].start;
                            let old_end = strand.domains[i].end;
                            if strand.domains[i].forward {
                                strand.domains[i].start = *n2;
                            } else {
                                strand.domains[i].end = *n2 + 1;
                            }
                            if i == 0 {
                                let last_dom = strand.domains.last_mut().unwrap();
                                if last_dom.forward {
                                    last_dom.end = *n2;
                                } else {
                                    last_dom.start = *n2 + 1;
                                }
                            } else {
                                if contains {
                                    let dom = strand.domains[i].pseudo_copy();
                                    let start = if dom.forward { old_start } else { *n1 };
                                    let end = if dom.forward { *n1 + 1 } else { old_end };
                                    added_dom = Some(Domain { start, end, ..dom });
                                }
                                let last_dom = strand.domains.last_mut().unwrap();
                                if last_dom.length() == 1 {
                                    strand.domains.pop();
                                } else {
                                    if last_dom.forward {
                                        last_dom.end -= 1;
                                    } else {
                                        last_dom.start += 1;
                                    }
                                }
                                for j in i..strand.domains.len() + i {
                                    let dom =
                                        strand.domains[j % strand.domains.len()].pseudo_copy();
                                    domains.push(dom);
                                }
                                if let Some(ref dom) = added_dom {
                                    domains.push(dom.pseudo_copy());
                                }
                            }
                            break;
                        }
                    }
                    if domains.len() > 0 {
                        strand.domains = domains;
                    }
                    strand.cyclic = false;
                }
            }
        }
    }

    /// Return the inverse of `self`
    pub fn undo(&self) -> Self {
        match self {
            Patch::ElongateDomain(h, b1, n1, b2, n2) if *b1 == *b2 => {
                Patch::ShortenDomain(*h, *b1, *n1 + *n2 as isize, *b2, *n2)
            }
            Patch::ElongateDomain(h, b1, n1, b2, n2) => {
                Patch::ShortenDomain(*h, *b1, *n1 - *n2 as isize, *b2, *n2)
            }
            Patch::ShortenDomain(h, b1, n1, b2, n2) if *b1 == *b2 => {
                Patch::ElongateDomain(*h, *b1, *n1 - *n2 as isize, *b2, *n2)
            }
            Patch::ShortenDomain(h, b1, n1, b2, n2) => {
                Patch::ElongateDomain(*h, *b1, *n1 + *n2 as isize, *b2, *n2)
            }
            Patch::AddStrand(h, b, n) => Patch::DeleteStrand(*h, *b, *n),
            Patch::DeleteStrand(h, b, n) => Patch::AddStrand(*h, *b, *n),
            Patch::MakeBound(h1, b1, n1, h2, b2, n2) => {
                Patch::CutBound(*h1, *b1, *n1, *h2, *b2, *n2)
            }
            Patch::CutBound(h1, b1, n1, h2, b2, n2) => {
                Patch::MakeBound(*h1, *b1, *n1, *h2, *b2, *n2)
            }
            _ => unimplemented!(),
        }
    }
}

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

    #[test]
}
*/