Raw File
data.rs
/*
ENSnano, a 3d graphical application for DNA nanostructures.
    Copyright (C) 2021  Nicolas Levy <nicolaspierrelevy@gmail.com> and Nicolas Schabanel <nicolas.schabanel@ens-lyon.fr>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/
//! This modules handles internal informations about the scene, such as the selected objects etc..
//! It also communicates with the desgings to get the position of the objects to draw on the scene.

use super::view::RawDnaInstance;
use super::{LetterInstance, SceneElement, View, ViewUpdate};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use std::sync::{Arc, RwLock};

use ultraviolet::{Rotor3, Vec3};

use super::view::Mesh;
use crate::consts::*;
use crate::design::{Design, Nucl, ObjectType, Referential, StrandBuilder};
use crate::mediator::{ActionMode, Selection, SelectionMode};
use crate::utils::PhantomElement;

type ViewPtr = Rc<RefCell<View>>;

/// A module that handles the instantiation of designs as 3D geometric objects
mod design3d;
use design3d::Design3D;

pub struct Data {
    view: ViewPtr,
    /// A `Design3D` is associated to each design.
    designs: Vec<Design3D>,
    /// The set of selected elements
    selected_element: Option<SceneElement>,
    /// The set of candidates elements
    candidate_element: Option<SceneElement>,
    selection: Vec<Selection>,
    candidates: Vec<Selection>,
    /// The kind of selection being perfomed on the scene.
    selection_mode: SelectionMode,
    /// The kind of selection being performed if self.selection_mode is SelectionMode::Nucl.
    ///
    /// Can be toggled by selecting the same element several
    /// time
    sub_selection_mode: SelectionMode,
    /// The kind of action being performed on the scene
    pub action_mode: ActionMode,
    /// A position determined by the current selection. If only one nucleotide is selected, it's
    /// the position of the nucleotide.
    selected_position: Option<Vec3>,
    selection_update: bool,
    candidate_update: bool,
    instance_update: bool,
    matrices_update: bool,
    widget_basis: WidgetBasis,
    /// The element arround which the camera rotates
    pivot_element: Option<SceneElement>,
    pivot_update: bool,
    pivot_position: Option<Vec3>,
    free_xover: Option<FreeXover>,
    free_xover_update: bool,
}

impl Data {
    pub fn new(view: ViewPtr) -> Self {
        Self {
            view,
            designs: Vec::new(),
            selected_element: None,
            candidate_element: None,
            selection: Vec::new(),
            candidates: Vec::new(),
            selection_mode: SelectionMode::default(),
            sub_selection_mode: SelectionMode::Nucleotide,
            action_mode: Default::default(),
            selected_position: None,
            selection_update: false,
            candidate_update: false,
            instance_update: false,
            matrices_update: false,
            widget_basis: WidgetBasis::Object,
            pivot_element: None,
            pivot_update: false,
            pivot_position: None,
            free_xover: None,
            free_xover_update: false,
        }
    }

    /// Add a new design to be drawn
    pub fn add_design(&mut self, design: Arc<RwLock<Design>>) {
        self.clear_designs();
        self.designs.push(Design3D::new(design));
        self.notify_instance_update();
        self.notify_matrices_update();
    }

    /// Remove all designs to be drawn
    pub fn clear_designs(&mut self) {
        self.designs = Vec::new();
        self.selected_element = None;
        self.candidate_element = None;
        self.selection = Vec::new();
        self.candidates = Vec::new();
        self.reset_selection();
        self.reset_candidate();
        self.notify_instance_update();
        self.notify_matrices_update();
        self.pivot_element = None;
        self.pivot_position = None;
        self.pivot_update = true;
        self.candidate_update = true;
        self.selection_update = true;
    }

    /// Forwards all needed update to the view
    pub fn update_view(&mut self) {
        if self.instance_update || self.selection_update || self.candidate_update {
            self.update_discs();
        }
        if self.instance_update {
            self.update_instances();
            self.instance_update = false;
        }

        if self.selection_update {
            self.update_selection();
            self.selection_update = false;
        }
        if self.candidate_update {
            self.update_candidate();
            self.candidate_update = false;
        }
        if self.pivot_update {
            self.update_pivot();
            self.pivot_update = false;
        }
        if self.free_xover_update {
            self.update_free_xover();
            self.free_xover_update = false;
        }

        if self.matrices_update {
            self.update_matrices();
            self.matrices_update = false;
        }
    }

    /// Return the sets of selected designs
    #[allow(dead_code)]
    pub fn get_selected_designs(&self) -> HashSet<u32> {
        self.selection
            .iter()
            .filter_map(|s| s.get_design())
            .collect()
    }

    pub fn set_pivot_element(&mut self, element: Option<SceneElement>) {
        self.pivot_update |= self.pivot_element != element;
        self.pivot_element = element;
        self.update_pivot_position();
    }

    pub fn set_pivot_position(&mut self, position: Vec3) {
        self.pivot_position = Some(position);
        self.pivot_update = true;
    }

    #[allow(dead_code)]
    fn get_element_design(&self, element: &SceneElement) -> u32 {
        match element {
            SceneElement::DesignElement(d_id, _) => *d_id,
            SceneElement::PhantomElement(phantom_element) => phantom_element.design_id,
            SceneElement::Grid(d_id, _) => *d_id,
            _ => unreachable!(),
        }
    }

    /// Convert a selection into a set of elements
    fn expand_selection(
        &self,
        object_type: ObjectType,
        selection: &Selection,
    ) -> Vec<SceneElement> {
        let d_id = selection.get_design();
        if d_id.is_none() {
            return vec![];
        }
        let d_id = d_id.unwrap() as usize;
        let mut ret = Vec::new();
        if let Selection::Nucleotide(d_id, nucl) = selection {
            if !object_type.is_bound() {
                if let Some(n_id) = self.designs[*d_id as usize].get_identifier_nucl(nucl) {
                    ret.push(SceneElement::DesignElement(*d_id, n_id))
                } else {
                    ret.push(SceneElement::PhantomElement(PhantomElement {
                        design_id: *d_id,
                        helix_id: nucl.helix as u32,
                        position: nucl.position as i32,
                        forward: nucl.forward,
                        bound: false,
                    }));
                }
            }
        } else if let Selection::Bound(d_id, n1, n2) = selection {
            if object_type.is_bound() {
                if let Some(b_id) = self.designs[*d_id as usize].get_identifier_bound(n1, n2) {
                    ret.push(SceneElement::DesignElement(*d_id, b_id))
                } else {
                    ret.push(SceneElement::PhantomElement(PhantomElement {
                        design_id: *d_id,
                        helix_id: n1.helix as u32,
                        position: n1.position as i32,
                        forward: n1.forward,
                        bound: true,
                    }));
                }
            }
        } else if let Selection::Xover(d_id, xover_id) = selection {
            if object_type.is_bound() {
                if let Some(b_id) =
                    self.designs[*d_id as usize].get_element_identifier_from_xover_id(*xover_id)
                {
                    ret.push(SceneElement::DesignElement(*d_id, b_id))
                }
            }
        } else {
            let group = self.get_group_member(selection);
            for elt in group.iter() {
                if self.designs[d_id]
                    .get_element_type(*elt)
                    .map(|elt| elt.same_type(object_type))
                    .unwrap_or(false)
                {
                    ret.push(SceneElement::DesignElement(d_id as u32, *elt));
                }
            }
        }
        ret
    }

    /*
    /// Convert `self.candidates` into a set of elements according to `self.selection_mode`
    fn expand_candidate(&self, object_type: ObjectType) -> Vec<SceneElement> {
        let mut ret = Vec::new();
        for element in &self.candidates {
            if let SceneElement::DesignElement(d_id, elt_id) = element {
                let group_id = self.get_group_identifier(*d_id, *elt_id);
                let group = self.get_group_member(*d_id, group_id);
                for elt in group.iter() {
                    if self.designs[*d_id as usize]
                        .get_element_type(*elt)
                        .map(|elt| elt.same_type(object_type))
                        .unwrap_or(false)
                    {
                        ret.push(SceneElement::DesignElement(*d_id, *elt));
                    }
                }
            } else if let SceneElement::PhantomElement(phantom_element) = element {
                if let Some(group_id) = self.get_group_identifier_phantom(*phantom_element) {
                    let d_id = phantom_element.design_id;
                    let group = self.get_group_member(d_id, group_id);
                    for elt in group.iter() {
                        if self.designs[d_id as usize]
                            .get_element_type(*elt)
                            .unwrap()
                            .same_type(object_type)
                        {
                            ret.push(SceneElement::DesignElement(d_id, *elt));
                        }
                    }
                }
                if phantom_element.bound == object_type.is_bound() {
                    ret.push(SceneElement::PhantomElement(*phantom_element));
                }
            }
        }
        ret
    }*/

    /// Return the instances of selected spheres
    pub fn get_selected_spheres(&self) -> Rc<Vec<RawDnaInstance>> {
        let mut ret = Vec::new();
        for selection in self.selection.iter() {
            for element in self
                .expand_selection(ObjectType::Nucleotide(0), selection)
                .iter()
            {
                match element {
                    SceneElement::DesignElement(d_id, id) => {
                        if let Some(instance) = self.designs[*d_id as usize].make_instance(
                            *id,
                            SELECTED_COLOR,
                            SELECT_SCALE_FACTOR,
                        ) {
                            ret.push(instance)
                        }
                    }
                    SceneElement::PhantomElement(phantom_element) => {
                        if let Some(instance) = self
                            .designs
                            .get(phantom_element.design_id as usize)
                            .and_then(|d| {
                                d.make_instance_phantom(
                                    phantom_element,
                                    SELECTED_COLOR,
                                    SELECT_SCALE_FACTOR,
                                )
                            })
                        {
                            ret.push(instance);
                        }
                    }
                    _ => unreachable!(),
                }
            }
        }
        Rc::new(ret)
    }

    /// Return the instances of selected tubes
    pub fn get_selected_tubes(&self) -> Rc<Vec<RawDnaInstance>> {
        let mut ret = Vec::new();
        for selection in self.selection.iter() {
            for element in self
                .expand_selection(ObjectType::Bound(0, 0), selection)
                .iter()
            {
                match element {
                    SceneElement::DesignElement(d_id, id) => {
                        if let Some(instance) = self.designs[*d_id as usize].make_instance(
                            *id,
                            SELECTED_COLOR,
                            SELECT_SCALE_FACTOR,
                        ) {
                            ret.push(instance)
                        }
                    }
                    SceneElement::PhantomElement(phantom_element) => {
                        if let Some(instance) = self
                            .designs
                            .get(phantom_element.design_id as usize)
                            .and_then(|d| {
                                d.make_instance_phantom(
                                    phantom_element,
                                    SELECTED_COLOR,
                                    SELECT_SCALE_FACTOR,
                                )
                            })
                        {
                            ret.push(instance);
                        }
                    }
                    _ => unreachable!(),
                }
            }
        }
        Rc::new(ret)
    }

    /// Return the instances of candidate spheres
    pub fn get_candidate_spheres(&self) -> Rc<Vec<RawDnaInstance>> {
        let mut ret = Vec::new();
        for candidate in self.candidates.iter() {
            for element in self
                .expand_selection(ObjectType::Nucleotide(0), candidate)
                .iter()
            {
                match element {
                    SceneElement::DesignElement(d_id, id) => {
                        if let Some(instance) = self.designs[*d_id as usize].make_instance(
                            *id,
                            CANDIDATE_COLOR,
                            SELECT_SCALE_FACTOR,
                        ) {
                            ret.push(instance)
                        }
                    }
                    SceneElement::PhantomElement(phantom_element) => {
                        if let Some(instance) = self
                            .designs
                            .get(phantom_element.design_id as usize)
                            .and_then(|d| {
                                d.make_instance_phantom(
                                    phantom_element,
                                    CANDIDATE_COLOR,
                                    SELECT_SCALE_FACTOR,
                                )
                            })
                        {
                            ret.push(instance);
                        }
                    }
                    _ => unreachable!(),
                }
            }
        }
        Rc::new(ret)
    }

    /// Return the instances of candidate tubes
    pub fn get_candidate_tubes(&self) -> Rc<Vec<RawDnaInstance>> {
        let mut ret = Vec::new();
        for candidate in self.candidates.iter() {
            for element in self
                .expand_selection(ObjectType::Bound(0, 0), candidate)
                .iter()
            {
                match element {
                    SceneElement::DesignElement(d_id, id) => {
                        if let Some(instance) = self.designs[*d_id as usize].make_instance(
                            *id,
                            CANDIDATE_COLOR,
                            SELECT_SCALE_FACTOR,
                        ) {
                            ret.push(instance)
                        }
                    }
                    SceneElement::PhantomElement(phantom_element) => {
                        if let Some(instance) = self
                            .designs
                            .get(phantom_element.design_id as usize)
                            .and_then(|d| {
                                d.make_instance_phantom(
                                    phantom_element,
                                    CANDIDATE_COLOR,
                                    SELECT_SCALE_FACTOR,
                                )
                            })
                        {
                            ret.push(instance);
                        }
                    }
                    _ => unreachable!(),
                }
            }
        }
        Rc::new(ret)
    }

    /// Return the identifier of the group of the selected element
    pub fn get_selected_group(&self) -> u32 {
        match self.selected_element.as_ref() {
            Some(SceneElement::DesignElement(design_id, element_id)) => {
                let selection_mode = self.get_sub_selection_mode();
                self.get_group_identifier(*design_id, *element_id, selection_mode)
            }
            Some(SceneElement::PhantomElement(phantom_element)) => phantom_element.helix_id,
            Some(SceneElement::Grid(_, g_id)) => *g_id as u32,
            _ => unreachable!(),
        }
    }

    /// Return the group to which an element belongs. The group depends on self.selection_mode.
    fn get_group_identifier(
        &self,
        design_id: u32,
        element_id: u32,
        selection_mode: SelectionMode,
    ) -> u32 {
        match selection_mode {
            SelectionMode::Nucleotide => element_id,
            SelectionMode::Design => design_id,
            SelectionMode::Strand => self.designs[design_id as usize].get_strand(element_id),
            SelectionMode::Helix => self.designs[design_id as usize].get_helix(element_id),
            SelectionMode::Grid => element_id,
        }
    }

    /// Return the group to which a phantom element belongs. The group depends on self.selection_mode.
    #[allow(dead_code)]
    fn get_group_identifier_phantom(
        &self,
        phantom_element: PhantomElement,
        selection_mode: SelectionMode,
    ) -> Option<u32> {
        let nucl = Nucl {
            helix: phantom_element.helix_id as usize,
            forward: phantom_element.forward,
            position: phantom_element.position as isize,
        };

        let design_id = phantom_element.design_id;
        let element_id = self.designs[design_id as usize].get_identifier_nucl(&nucl);

        match selection_mode {
            SelectionMode::Nucleotide => element_id,
            SelectionMode::Design => Some(design_id),
            SelectionMode::Strand => {
                element_id.map(|e| self.designs[design_id as usize].get_strand(e))
            }
            SelectionMode::Helix => Some(phantom_element.helix_id),
            SelectionMode::Grid => None,
        }
    }

    fn get_helix_identifier(&self, design_id: u32, element_id: u32) -> u32 {
        self.designs[design_id as usize].get_helix(element_id)
    }

    /// Return the set of elements in a given group
    fn get_group_member(&self, element: &Selection) -> HashSet<u32> {
        match element {
            Selection::Nucleotide(d_id, nucl) => self.designs[*d_id as usize]
                .get_identifier_nucl(nucl)
                .iter()
                .cloned()
                .collect(),
            Selection::Bound(d_id, n1, n2) => self.designs[*d_id as usize]
                .get_identifier_bound(n1, n2)
                .iter()
                .cloned()
                .collect(),
            Selection::Xover(d_id, xover_id) => self.designs[*d_id as usize]
                .get_element_identifier_from_xover_id(*xover_id)
                .iter()
                .cloned()
                .collect(),
            Selection::Helix(d_id, h_id) => self.designs[*d_id as usize].get_helix_elements(*h_id),
            Selection::Strand(d_id, s_id) => {
                self.designs[*d_id as usize].get_strand_elements(*s_id)
            }
            Selection::Grid(_, _) => HashSet::new(), // A grid is not made of atomic elements
            Selection::Phantom(_) => HashSet::new(),
            Selection::Nothing => HashSet::new(),
            Selection::Design(d_id) => self.designs[*d_id as usize].get_all_elements(),
        }
    }

    /// Return the postion of a given element, either in the world pov or in the model pov
    fn get_element_position(
        &self,
        element: &SceneElement,
        referential: Referential,
        selection_mode: SelectionMode,
    ) -> Option<Vec3> {
        let design_id = element.get_design()?;
        let design = self.designs.get(design_id as usize)?;
        match selection_mode {
            SelectionMode::Helix => design
                .get_element_axis_position(element, referential)
                .or(design.get_element_position(element, referential)),
            SelectionMode::Nucleotide
            | SelectionMode::Strand
            | SelectionMode::Design
            | SelectionMode::Grid => design.get_element_position(element, referential),
        }
    }

    pub fn get_selected_position(&self) -> Option<Vec3> {
        self.selected_position
    }

    pub fn try_update_pivot_position(&mut self) {
        if self.pivot_element.is_none() {
            self.pivot_element = self.selected_element;
            self.pivot_update = true;
            self.update_pivot_position();
        }
    }

    pub fn get_pivot_position(&self) -> Option<Vec3> {
        self.pivot_position.or(self.selected_position)
    }

    /// Update the selection by selecting the group to which a given nucleotide belongs. Return the
    /// selected group
    pub fn set_selection(&mut self, element: Option<SceneElement>) -> Option<Selection> {
        if let Some(SceneElement::WidgetElement(_)) = element {
            return None;
        }
        println!("selected {:?}", element);
        let future_selection = element;
        if self.selected_element == future_selection {
            self.sub_selection_mode = toggle_selection(self.sub_selection_mode);
        } else {
            self.sub_selection_mode = SelectionMode::Nucleotide;
        }
        self.selected_element = future_selection;
        self.update_selected_position();
        println!("selected position: {:?}", self.selected_position);
        let selection_mode = if self.selection_mode == SelectionMode::Nucleotide {
            self.sub_selection_mode
        } else {
            self.selection_mode
        };
        let selection = if let Some(element) = element.as_ref() {
            self.element_to_selection(element, selection_mode)
        } else {
            Selection::Nothing
        };
        let future_selection = if selection != Selection::Nothing {
            vec![selection]
        } else {
            vec![]
        };
        self.selection_update |= self.selected_element != element;
        self.selection_update |= self.selection != future_selection;
        self.selection = future_selection;

        Some(selection)
    }

    pub fn to_selection(&self, element: Option<SceneElement>) -> Option<Selection> {
        if let Some(SceneElement::WidgetElement(_)) = element {
            return None;
        }
        let selection = if let Some(element) = element.as_ref() {
            self.element_to_selection(element, self.selection_mode)
        } else {
            Selection::Nothing
        };
        Some(selection).filter(|s| *s != Selection::Nothing)
    }

    pub fn add_to_selection(&mut self, element: Option<SceneElement>) -> Option<Vec<Selection>> {
        if let Some(SceneElement::WidgetElement(_)) = element {
            return None;
        }
        self.sub_selection_mode = SelectionMode::Nucleotide;
        let selection = if let Some(element) = element.as_ref() {
            self.element_to_selection(element, self.selection_mode)
        } else {
            Selection::Nothing
        };
        if let Some(element) = element.clone() {
            self.selected_element = Some(element);
        }
        if selection == Selection::Nothing {
            None
        } else {
            if let Some(pos) = self.selection.iter().position(|x| *x == selection) {
                self.selection.remove(pos);
            } else {
                self.selection.push(selection);
            }
            self.selection_update = true;
            Some(self.selection.clone())
        }
    }

    /// This function must be called when the current movement ends.
    pub fn end_movement(&mut self) {
        self.update_selected_position()
    }

    /// If source is some nucleotide, target is some nucleotide and both nucleotides are
    /// on the same design, return the pair of nucleotides. Otherwise return None
    pub fn attempt_xover(
        &self,
        source: &Option<SceneElement>,
        target: &Option<SceneElement>,
    ) -> Option<(Nucl, Nucl, usize)> {
        let design_id;
        let source_nucl = if let Some(SceneElement::DesignElement(d_id, e_id)) = source {
            design_id = *d_id;
            self.designs[*d_id as usize].get_nucl_relax(*e_id)
        } else {
            design_id = 0;
            None
        }?;
        let target_nucl = if let Some(SceneElement::DesignElement(d_id, e_id)) = target {
            if design_id != *d_id {
                return None;
            }
            self.designs[design_id as usize].get_nucl_relax(*e_id)
        } else {
            None
        }?;
        Some((source_nucl, target_nucl, design_id as usize))
    }

    fn update_selected_position(&mut self) {
        let selection_mode = self.get_sub_selection_mode();
        self.selected_position = {
            if let Some(element) = self.selected_element.as_ref() {
                self.get_element_position(element, Referential::World, selection_mode)
            } else {
                None
            }
        };
    }

    fn update_pivot_position(&mut self) {
        self.pivot_position = {
            if let Some(element) = self.pivot_element.as_ref() {
                self.get_element_position(element, Referential::World, self.selection_mode)
            } else {
                None
            }
        };
    }

    /// Clear self.selected
    pub fn reset_selection(&mut self) {
        self.selection_update |= self.selected_element.is_some();
        self.selected_position = None;
        self.selected_element = None;
    }

    /// Notify the view that the selected elements have been modified
    fn update_selection(&mut self) {
        self.view.borrow_mut().update(ViewUpdate::RawDna(
            Mesh::SelectedTube,
            self.get_selected_tubes(),
        ));
        self.view.borrow_mut().update(ViewUpdate::RawDna(
            Mesh::SelectedSphere,
            self.get_selected_spheres(),
        ));
        let (sphere, vec) = self.get_phantom_instances();
        self.view
            .borrow_mut()
            .update(ViewUpdate::RawDna(Mesh::PhantomSphere, sphere));
        self.view
            .borrow_mut()
            .update(ViewUpdate::RawDna(Mesh::PhantomTube, vec));
        let mut grids = if let Some(SceneElement::Grid(d_id, g_id)) = self.selected_element.as_ref()
        {
            vec![(*d_id as usize, *g_id)]
        } else {
            vec![]
        };
        for s in self.selection.iter() {
            if let Selection::Grid(d_id, g_id) = s {
                grids.push((*d_id as usize, *g_id));
            }
        }
        self.view.borrow_mut().set_selected_grid(grids);
    }

    /// Return the sets of elements of the phantom helix
    pub fn get_phantom_instances(&self) -> (Rc<Vec<RawDnaInstance>>, Rc<Vec<RawDnaInstance>>) {
        let phantom_map = self.get_phantom_helices_set();
        let mut ret_sphere = Vec::new();
        let mut ret_tube = Vec::new();
        for (d_id, set) in phantom_map.iter() {
            let (spheres, tubes) =
                self.designs[*d_id as usize].make_phantom_helix_instances_raw(set);
            for sphere in spheres.iter().cloned() {
                ret_sphere.push(sphere);
            }
            for tube in tubes.iter().cloned() {
                ret_tube.push(tube);
            }
        }
        (Rc::new(ret_sphere), Rc::new(ret_tube))
    }

    /// Return a hashmap, mapping designs identifier to the set of helices whose phantom must be
    /// drawn.
    fn get_phantom_helices_set(&self) -> HashMap<u32, HashMap<u32, bool>> {
        let mut ret = HashMap::new();

        for (d_id, design) in self.designs.iter().enumerate() {
            let new_helices = design.get_persistent_phantom_helices();
            let set = ret.entry(d_id as u32).or_insert_with(HashMap::new);
            for h_id in new_helices.iter() {
                set.insert(*h_id, true);
            }
        }
        if self.must_draw_phantom() {
            for element in self.selected_element.iter() {
                match element {
                    SceneElement::DesignElement(d_id, elt_id) => {
                        let set = ret.entry(*d_id).or_insert_with(HashMap::new);
                        set.insert(self.get_helix_identifier(*d_id, *elt_id), false);
                    }
                    SceneElement::PhantomElement(phantom_element) => {
                        let set = ret
                            .entry(phantom_element.design_id)
                            .or_insert_with(HashMap::new);
                        set.insert(phantom_element.helix_id, false);
                    }
                    SceneElement::Grid(d_id, g_id) => {
                        let new_helices = self.designs[*d_id as usize]
                            .get_helices_grid(*g_id)
                            .unwrap_or_default();
                        let set = ret.entry(*d_id).or_insert_with(HashMap::new);
                        for h_id in new_helices.iter() {
                            set.insert(*h_id as u32, true);
                        }
                    }
                    SceneElement::GridCircle(d_id, g_id, x, y) => {
                        if let Some(h_id) =
                            self.designs[*d_id as usize].get_helix_grid(*g_id, *x, *y)
                        {
                            let set = ret.entry(*d_id).or_insert_with(HashMap::new);
                            set.insert(h_id, false);
                        }
                    }
                    SceneElement::WidgetElement(_) => unreachable!(),
                }
            }
        }
        ret
    }

    fn must_draw_phantom(&self) -> bool {
        let ret = self.selection_mode == SelectionMode::Helix;
        if ret {
            true
        } else {
            for element in self.selected_element.iter() {
                if let SceneElement::PhantomElement(_) = element {
                    return true;
                }
            }
            false
        }
    }

    pub fn element_to_selection(
        &self,
        element: &SceneElement,
        selection_mode: SelectionMode,
    ) -> Selection {
        match element {
            SceneElement::DesignElement(design_id, element_id) => {
                let group_id = self.get_group_identifier(*design_id, *element_id, selection_mode);
                match selection_mode {
                    SelectionMode::Design => Selection::Design(*design_id),
                    SelectionMode::Strand => Selection::Strand(*design_id, group_id),
                    SelectionMode::Nucleotide => {
                        let nucl = self.designs[*design_id as usize].get_nucl(group_id);
                        let bound = self.designs[*design_id as usize].get_bound(group_id);
                        let xover_id = bound.as_ref().and_then(|xover| {
                            self.designs[*design_id as usize].get_xover_id(xover)
                        });
                        if let Some(nucl) = nucl {
                            Selection::Nucleotide(*design_id, nucl)
                        } else if let Some(id) = xover_id {
                            Selection::Xover(*design_id, id)
                        } else if let Some((n1, n2)) = bound {
                            Selection::Bound(*design_id, n1, n2)
                        } else {
                            Selection::Nothing
                        }
                    }
                    SelectionMode::Helix => Selection::Helix(*design_id, group_id),
                    SelectionMode::Grid => Selection::Grid(*design_id, group_id as usize),
                }
            }
            SceneElement::Grid(d_id, g_id) => Selection::Grid(*d_id, *g_id),
            SceneElement::GridCircle(d_id, g_id, _, _) => Selection::Grid(*d_id, *g_id),
            SceneElement::PhantomElement(phantom) if phantom.bound => Selection::Bound(
                phantom.design_id,
                phantom.to_nucl(),
                phantom.to_nucl().left(),
            ),
            SceneElement::PhantomElement(phantom) => {
                if selection_mode == SelectionMode::Helix {
                    Selection::Helix(phantom.design_id, phantom.to_nucl().helix as u32)
                } else {
                    Selection::Nucleotide(phantom.design_id, phantom.to_nucl())
                }
            }
            _ => Selection::Nothing,
        }
    }

    /// Set the set of candidates to a given nucleotide
    pub fn set_candidate(&mut self, element: Option<SceneElement>) {
        let future_candidates = if let Some(element) = element.as_ref() {
            let selection = self.element_to_selection(element, self.selection_mode);
            if selection != Selection::Nothing {
                vec![selection]
            } else {
                vec![]
            }
        } else {
            vec![]
        };
        self.candidate_update |= self.candidate_element != element;
        self.candidate_update |= self.candidates != future_candidates;
        self.candidates = future_candidates;
        self.candidate_element = element;
    }

    pub fn get_candidate(&self) -> Vec<Selection> {
        self.candidates.clone()
    }

    pub fn notify_candidate(&mut self, candidate: Vec<Selection>) {
        let future_candidates = candidate
            .iter()
            .filter(|c| c.get_design().is_some())
            .cloned()
            .collect();
        self.candidate_update |= self.candidates != future_candidates;
        self.candidates = future_candidates;
    }

    pub fn notify_selection(&mut self, selection: Vec<Selection>) {
        let future_selection = selection
            .iter()
            .filter(|s| s.get_design().is_some())
            .cloned()
            .collect();
        self.selection_update |= self.selection != future_selection;
        self.selection = future_selection;
        if selection.len() == 1 {
            match selection[0] {
                Selection::Nucleotide(d_id, nucl) => {
                    self.selected_position = self.designs[d_id as usize].get_nucl_position(nucl);
                }
                Selection::Bound(d_id, n1, n2) => {
                    let pos1 = self.designs[d_id as usize].get_nucl_position(n1);
                    let pos2 = self.designs[d_id as usize].get_nucl_position(n2);
                    self.selected_position = pos1.zip(pos2).map(|(a, b)| (a + b) / 2.);
                }
                Selection::Xover(d_id, xover_id) => {
                    if let Some((n1, n2)) = self.designs[d_id as usize].get_xover_with_id(xover_id)
                    {
                        let pos1 = self.designs[d_id as usize].get_nucl_position(n1);
                        let pos2 = self.designs[d_id as usize].get_nucl_position(n2);
                        self.selected_position = pos1.zip(pos2).map(|(a, b)| (a + b) / 2.);
                    }
                }
                _ => (),
            }
        }
    }

    /// Clear the set of candidates to a given nucleotide
    pub fn reset_candidate(&mut self) {
        self.candidate_update |= !self.candidates.is_empty();
        self.candidates = Vec::new();
        self.candidate_element = None;
    }

    /// Notify the view that the instances of candidates have changed
    fn update_candidate(&mut self) {
        self.view.borrow_mut().update(ViewUpdate::RawDna(
            Mesh::CandidateTube,
            self.get_candidate_tubes(),
        ));
        self.view.borrow_mut().update(ViewUpdate::RawDna(
            Mesh::CandidateSphere,
            self.get_candidate_spheres(),
        ));
        let mut grids =
            if let Some(SceneElement::Grid(d_id, g_id)) = self.candidate_element.as_ref() {
                vec![(*d_id as usize, *g_id)]
            } else {
                vec![]
            };
        for c in self.candidates.iter() {
            if let Selection::Grid(d_id, g_id) = c {
                grids.push((*d_id as usize, *g_id));
            }
        }
        self.view.borrow_mut().set_candidate_grid(grids);
    }

    fn update_pivot(&mut self) {
        let spheres = if let Some(pivot) = self.pivot_position {
            vec![Design3D::pivot_sphere(pivot)]
        } else {
            vec![]
        };
        self.view
            .borrow_mut()
            .update(ViewUpdate::RawDna(Mesh::PivotSphere, Rc::new(spheres)));
    }

    fn update_free_xover(&mut self) {
        let mut spheres = vec![];
        let mut tubes = vec![];
        let mut pos1 = None;
        let mut pos2 = None;
        if let Some(xover) = self.free_xover.as_ref() {
            if let Some((pos, sphere)) = self.convert_free_end(&xover.source, xover.design_id) {
                pos1 = Some(pos);
                if let Some(s) = sphere {
                    spheres.push(s);
                }
            }
            if let Some((pos, sphere)) = self.convert_free_end(&xover.target, xover.design_id) {
                pos2 = Some(pos);
                if let Some(s) = sphere {
                    spheres.push(s);
                }
            }
            if let Some((pos1, pos2)) = pos1.zip(pos2) {
                tubes.push(Design3D::free_xover_tube(pos1, pos2))
            }
        }
        self.view
            .borrow_mut()
            .update(ViewUpdate::RawDna(Mesh::XoverSphere, Rc::new(spheres)));
        self.view
            .borrow_mut()
            .update(ViewUpdate::RawDna(Mesh::XoverTube, Rc::new(tubes)));
    }

    fn convert_free_end(
        &self,
        free_end: &FreeXoverEnd,
        design_id: usize,
    ) -> Option<(Vec3, Option<RawDnaInstance>)> {
        match free_end {
            FreeXoverEnd::Nucl(nucl) => {
                let position = self.get_nucl_position(*nucl, design_id)?;
                Some((position, Some(Design3D::free_xover_sphere(position))))
            }
            FreeXoverEnd::Free(position) => Some((*position, None)),
        }
    }

    /// This function must be called when the designs have been modified
    pub fn notify_instance_update(&mut self) {
        self.candidates = vec![];
        self.instance_update = true;
    }

    /// Notify the view that the set of instances have been modified.
    fn update_instances(&mut self) {
        let mut spheres = Vec::with_capacity(self.get_number_spheres());
        let mut tubes = Vec::with_capacity(self.get_number_tubes());
        let mut suggested_spheres = Vec::with_capacity(1000);
        let mut suggested_tubes = Vec::with_capacity(1000);
        let mut pasted_spheres = Vec::with_capacity(1000);
        let mut pasted_tubes = Vec::with_capacity(1000);

        let mut letters = Vec::new();
        let mut grids = Vec::new();
        let mut cones = Vec::new();
        for design in self.designs.iter() {
            for sphere in design.get_spheres_raw().iter() {
                spheres.push(*sphere);
            }
            for tube in design.get_tubes_raw().iter() {
                tubes.push(*tube);
            }
            letters = design.get_letter_instances();
            for grid in design.get_grid().iter().filter(|g| g.visible) {
                grids.push(grid.clone());
            }
            for sphere in design.get_suggested_spheres() {
                suggested_spheres.push(sphere)
            }
            for tube in design.get_suggested_tubes() {
                suggested_tubes.push(tube)
            }
            let (spheres, tubes) = design.get_pasted_strand();
            for sphere in spheres {
                pasted_spheres.push(sphere);
            }
            for tube in tubes {
                pasted_tubes.push(tube);
            }
            for cone in design.get_all_prime3_cone() {
                cones.push(cone);
            }
        }
        self.update_free_xover();
        self.view
            .borrow_mut()
            .update(ViewUpdate::RawDna(Mesh::Tube, Rc::new(tubes)));
        self.view
            .borrow_mut()
            .update(ViewUpdate::RawDna(Mesh::Sphere, Rc::new(spheres)));
        self.view.borrow_mut().update(ViewUpdate::RawDna(
            Mesh::SuggestionSphere,
            Rc::new(suggested_spheres),
        ));
        self.view.borrow_mut().update(ViewUpdate::RawDna(
            Mesh::SuggestionTube,
            Rc::new(suggested_tubes),
        ));
        self.view.borrow_mut().update(ViewUpdate::RawDna(
            Mesh::PastedSphere,
            Rc::new(pasted_spheres),
        ));
        self.view
            .borrow_mut()
            .update(ViewUpdate::RawDna(Mesh::PastedTube, Rc::new(pasted_tubes)));
        self.view.borrow_mut().update(ViewUpdate::Letter(letters));
        self.view
            .borrow_mut()
            .update(ViewUpdate::Grids(Rc::new(grids)));
        self.view
            .borrow_mut()
            .update(ViewUpdate::RawDna(Mesh::Prime3Cone, Rc::new(cones)));
        self.selection_update = true;
    }

    fn update_discs(&mut self) {
        let mut discs = Vec::new();
        let mut letters: Vec<Vec<LetterInstance>> = vec![vec![]; 10];
        let right = self.view.borrow().get_camera().borrow().right_vec();
        let up = self.view.borrow().get_camera().borrow().up_vec();
        for (d_id, design) in self.designs.iter().enumerate() {
            for grid in design.get_grid().iter().filter(|g| g.visible) {
                for (x, y) in design.get_helices_grid_coord(grid.id) {
                    let element = Some(SceneElement::GridCircle(d_id as u32, grid.id, x, y));
                    if self.selected_element.as_ref() != element.as_ref()
                        && self.candidate_element.as_ref() != element.as_ref()
                    {
                        let (d1, d2) = grid.disc(x, y, 0xAA_FF_FF_FF, d_id as u32);
                        discs.push(d1);
                        discs.push(d2);
                    }
                }
                for ((x, y), h_id) in design.get_helices_grid_key_coord(grid.id) {
                    grid.letter_instance(x, y, h_id, &mut letters, right, up);
                }
            }
        }
        if let Some(SceneElement::GridCircle(d_id, g_id, x, y)) = self.selected_element.as_ref() {
            if let Some(grid) = self.designs[*d_id as usize].get_grid().get(*g_id as usize) {
                let (d1, d2) = grid.disc(*x, *y, 0xAA_FF_00_00, *d_id as u32);
                discs.push(d1);
                discs.push(d2);
            }
        }
        if let Some(SceneElement::GridCircle(d_id, g_id, x, y)) = self.candidate_element.as_ref() {
            if let Some(grid) = self.designs[*d_id as usize].get_grid().get(*g_id as usize) {
                let (d1, d2) = grid.disc(*x, *y, 0xAA_00_FF_00, *d_id as u32);
                discs.push(d1);
                discs.push(d2);
            }
        }
        self.view.borrow_mut().update(ViewUpdate::GridDiscs(discs));
        self.view
            .borrow_mut()
            .update(ViewUpdate::GridLetter(letters));
    }
    /// This fuction must be called when the model matrices have been modfied
    pub fn notify_matrices_update(&mut self) {
        self.matrices_update = true;
    }

    /// Notify the view of an update of the model matrices
    fn update_matrices(&mut self) {
        let mut matrices = Vec::new();
        for design in self.designs.iter() {
            matrices.push(design.get_model_matrix());
        }
        self.view
            .borrow_mut()
            .update(ViewUpdate::ModelMatrices(matrices));
    }

    pub fn get_fitting_camera_position(&self) -> Option<Vec3> {
        let view = self.view.borrow();
        let basis = view.get_camera().borrow().get_basis();
        let fovy = view.get_projection().borrow().get_fovy();
        let ratio = view.get_projection().borrow().get_ratio();
        self.designs
            .get(0)
            .and_then(|d| d.get_fitting_camera_position(basis, fovy, ratio))
    }

    /// Return the point in the middle of the selected design
    pub fn get_middle_point(&self, design_id: u32) -> Vec3 {
        self.designs[design_id as usize].middle_point()
    }

    fn get_number_spheres(&self) -> usize {
        self.designs.iter().map(|d| d.get_spheres_raw().len()).sum()
    }

    fn get_number_tubes(&self) -> usize {
        self.designs.iter().map(|d| d.get_tubes_raw().len()).sum()
    }

    #[allow(dead_code)]
    pub fn toggle_selection_mode(&mut self) {
        self.selection_mode = match self.selection_mode {
            SelectionMode::Nucleotide => SelectionMode::Design,
            SelectionMode::Design => SelectionMode::Strand,
            SelectionMode::Strand => SelectionMode::Helix,
            SelectionMode::Helix => SelectionMode::Grid,
            SelectionMode::Grid => SelectionMode::Nucleotide,
        }
    }

    pub fn change_selection_mode(&mut self, selection_mode: SelectionMode) {
        self.selection_mode = selection_mode;
    }

    pub fn get_action_mode(&self) -> ActionMode {
        self.action_mode
    }

    pub fn change_action_mode(&mut self, action_mode: ActionMode) {
        self.action_mode = action_mode;
        self.instance_update = true;
        self.update_matrices();
    }

    pub fn toggle_widget_basis(&mut self, axis_aligned: bool) {
        self.widget_basis.toggle(axis_aligned)
    }

    pub fn get_widget_basis(&self) -> Option<Rotor3> {
        self.get_selected_basis().map(|b| {
            if let WidgetBasis::Object = self.widget_basis {
                b
            } else {
                Rotor3::identity()
            }
        })
    }

    fn get_selected_basis(&self) -> Option<Rotor3> {
        match self.selected_element.as_ref() {
            Some(SceneElement::DesignElement(d_id, _)) => match self.get_sub_selection_mode() {
                SelectionMode::Nucleotide | SelectionMode::Design | SelectionMode::Strand => None,
                SelectionMode::Grid => Some(self.designs[*d_id as usize].get_basis()),
                SelectionMode::Helix => {
                    let h_id = self.get_selected_group();
                    self.designs[*d_id as usize].get_helix_basis(h_id)
                }
            },
            Some(SceneElement::PhantomElement(phantom_element)) => {
                let d_id = phantom_element.design_id;
                match self.get_sub_selection_mode() {
                    SelectionMode::Nucleotide | SelectionMode::Design | SelectionMode::Strand => {
                        None
                    }
                    SelectionMode::Grid => Some(self.designs[d_id as usize].get_basis()),
                    SelectionMode::Helix => {
                        let h_id = phantom_element.helix_id;
                        self.designs[d_id as usize].get_helix_basis(h_id)
                    }
                }
            }
            Some(SceneElement::Grid(d_id, g_id)) => {
                self.designs[*d_id as usize].get_grid_basis(*g_id)
            }
            Some(SceneElement::GridCircle(d_id, g_id, _, _)) => {
                self.designs[*d_id as usize].get_grid_basis(*g_id)
            }
            _ => None,
        }
    }

    pub fn selection_can_rotate_freely(&self) -> bool {
        match self.selected_element.as_ref() {
            Some(SceneElement::DesignElement(d_id, _)) => match self.get_sub_selection_mode() {
                SelectionMode::Nucleotide
                | SelectionMode::Design
                | SelectionMode::Strand
                | SelectionMode::Grid => true,
                SelectionMode::Helix => {
                    let h_id = self.get_selected_group();
                    !self.designs[*d_id as usize].helix_is_on_grid(h_id)
                }
            },
            Some(SceneElement::PhantomElement(phantom_element)) => {
                let d_id = phantom_element.design_id;
                match self.get_sub_selection_mode() {
                    SelectionMode::Nucleotide
                    | SelectionMode::Design
                    | SelectionMode::Strand
                    | SelectionMode::Grid => true,
                    SelectionMode::Helix => {
                        let h_id = phantom_element.helix_id;
                        !self.designs[d_id as usize].helix_is_on_grid(h_id)
                    }
                }
            }
            Some(SceneElement::Grid(_, _)) => true,
            _ => true,
        }
    }

    #[allow(dead_code)]
    pub fn select_5prime(&mut self) {
        let selected = self.selected_element.as_ref();
        if let Some(SceneElement::DesignElement(d_id, e_id)) = selected {
            let new_selection = self
                .designs
                .get(*d_id as usize)
                .and_then(|d| d.get_element_5prime(*e_id));
            if new_selection.is_some() {
                self.set_selection(new_selection);
            }
        }
    }

    #[allow(dead_code)]
    pub fn select_3prime(&mut self) {
        let selected = self.selected_element.as_ref();
        if let Some(SceneElement::DesignElement(d_id, e_id)) = selected {
            let new_selection = self
                .designs
                .get(*d_id as usize)
                .and_then(|d| d.get_element_3prime(*e_id));
            if new_selection.is_some() {
                self.set_selection(new_selection);
            }
        }
    }

    pub fn get_strand_builder(
        &self,
        element: Option<SceneElement>,
        stick: bool,
    ) -> Option<StrandBuilder> {
        let selected = element.as_ref()?;
        let design = selected.get_design()?;
        self.designs[design as usize].get_builder(selected, stick)
    }

    pub fn element_to_nucl(
        &self,
        element: &Option<SceneElement>,
        non_phantom: bool,
    ) -> Option<(Nucl, usize)> {
        match element {
            Some(SceneElement::DesignElement(d_id, n_id)) => self.designs[*d_id as usize]
                .get_nucl(*n_id)
                .zip(Some(*d_id as usize)),
            Some(SceneElement::PhantomElement(pe)) => {
                let nucl = pe.to_nucl();
                if non_phantom {
                    Some((nucl, pe.design_id as usize))
                        .filter(|n| self.designs[pe.design_id as usize].has_nucl(&n.0))
                } else {
                    Some((nucl, pe.design_id as usize))
                }
            }
            _ => None,
        }
    }

    pub fn get_nucl_position(&self, nucl: Nucl, design_id: usize) -> Option<Vec3> {
        let design = self.designs.get(design_id)?;
        design.get_nucl_position(nucl)
    }

    /// Set the selection to a given nucleotide if it exists in the design.
    pub fn select_nucl(&mut self, nucl: Nucl, design_id: usize) {
        let e_id = self.designs[design_id].get_identifier_nucl(&nucl);
        if let Some(id) = e_id {
            self.set_selection(Some(SceneElement::DesignElement(design_id as u32, id)));
        }
    }

    #[allow(dead_code)]
    pub fn get_candidate_nucl(&self) -> Option<Nucl> {
        match self.candidate_element.as_ref() {
            None => None,
            Some(SceneElement::DesignElement(d_id, n_id)) => {
                self.designs[*d_id as usize].get_nucl(*n_id)
            }
            Some(SceneElement::PhantomElement(pe)) => Some(pe.to_nucl()),
            _ => None,
        }
    }

    pub fn init_free_xover(&mut self, nucl: Nucl, position: Vec3, design_id: usize) {
        self.free_xover_update = true;
        self.free_xover = Some(FreeXover {
            source: FreeXoverEnd::Nucl(nucl),
            target: FreeXoverEnd::Free(position),
            design_id,
        });
    }

    pub fn update_free_xover_target(&mut self, element: Option<SceneElement>, position: Vec3) {
        self.free_xover_update = true;
        let nucl = self.element_to_nucl(&element, true);
        if let Some(free_xover) = self.free_xover.as_mut() {
            free_xover.target = FreeXoverEnd::Free(position);
            if let FreeXoverEnd::Nucl(origin_nucl) = free_xover.source {
                if let Some((nucl, _)) = nucl.filter(|n| n.1 == free_xover.design_id) {
                    if nucl.helix != origin_nucl.helix
                        && !self.designs[free_xover.design_id].both_prime3(origin_nucl, nucl)
                        && !self.designs[free_xover.design_id].both_prime5(origin_nucl, nucl)
                    {
                        free_xover.target = FreeXoverEnd::Nucl(nucl);
                    }
                }
            }
        }
    }

    pub fn end_free_xover(&mut self) {
        self.free_xover_update = true;
        self.free_xover = None;
    }

    fn get_sub_selection_mode(&self) -> SelectionMode {
        if self.selection_mode == SelectionMode::Nucleotide {
            self.sub_selection_mode
        } else {
            self.selection_mode
        }
    }

    pub fn get_selected_element(&self) -> Selection {
        if let Some(selection) = self.selected_element.as_ref() {
            self.element_to_selection(selection, self.get_sub_selection_mode())
        } else {
            Selection::Nothing
        }
    }
}

impl ActionMode {
    pub const ALL: [ActionMode; 5] = [
        ActionMode::Normal,
        ActionMode::Translate,
        ActionMode::Rotate,
        ActionMode::Build(false),
        ActionMode::Cut,
    ];

    pub fn wants_rotation(&self) -> bool {
        match self {
            ActionMode::Rotate => true,
            _ => false,
        }
    }

    pub fn wants_handle(&self) -> bool {
        match self {
            ActionMode::Translate => true,
            _ => false,
        }
    }
}

#[derive(Clone, Copy)]
enum WidgetBasis {
    World,
    Object,
}

impl WidgetBasis {
    pub fn toggle(&mut self, axis_aligned: bool) {
        if axis_aligned {
            *self = WidgetBasis::World
        } else {
            *self = WidgetBasis::Object
        };
    }
}

struct FreeXover {
    source: FreeXoverEnd,
    target: FreeXoverEnd,
    design_id: usize,
}

enum FreeXoverEnd {
    Free(Vec3),
    Nucl(Nucl),
}

fn toggle_selection(mode: SelectionMode) -> SelectionMode {
    match mode {
        SelectionMode::Nucleotide => SelectionMode::Strand,
        SelectionMode::Strand => SelectionMode::Helix,
        SelectionMode::Helix => SelectionMode::Nucleotide,
        mode => mode,
    }
}
back to top