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
use crate::sketchbook::ids::{DatasetId, DynPropertyId, ObservationId};
use crate::sketchbook::properties::dynamic_props;
use crate::sketchbook::JsonSerde;
use dynamic_props::{DynProperty, DynPropertyType};
use serde::{Deserialize, Serialize};

/// Simplified variant to carry data regarding [dynamic_props::GenericDynProp] dynamic property.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GenericDynPropData {
    pub formula: String,
}

/// Simplified variant to carry data regarding [dynamic_props::ExistsFixedPoint] dynamic property.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExistsFixedPointData {
    pub dataset: Option<String>,
    pub observation: Option<String>,
}

/// Simplified variant to carry data regarding [dynamic_props::ExistsTrapSpace] dynamic property.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExistsTrapSpaceData {
    pub dataset: Option<String>,
    pub observation: Option<String>,
    pub minimal: bool,
    pub nonpercolable: bool,
}

/// Simplified variant to carry data regarding [dynamic_props::ExistsTrajectory] dynamic property.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExistsTrajectoryData {
    pub dataset: Option<String>,
}

/// Simplified variant to carry data regarding [dynamic_props::AttractorCount] dynamic property.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct AttractorCountData {
    pub minimal: usize,
    pub maximal: usize,
}

/// Simplified variant to carry data regarding [dynamic_props::HasAttractor] dynamic property.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct HasAttractorData {
    pub dataset: Option<String>,
    pub observation: Option<String>,
}

/// Enum covering all variants of dynamic properties and their necessary data.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "variant")]
pub enum DynPropertyTypeData {
    GenericDynProp(GenericDynPropData),
    ExistsFixedPoint(ExistsFixedPointData),
    ExistsTrapSpace(ExistsTrapSpaceData),
    ExistsTrajectory(ExistsTrajectoryData),
    AttractorCount(AttractorCountData),
    HasAttractor(HasAttractorData),
}

/// Structure for sending data about dynamic properties to the frontend.
///
/// Some fields simplified compared to original typesafe versions (e.g., pure `Strings` are used
/// instead of more complex typesafe structs) to allow for easier (de)serialization.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct DynPropertyData {
    pub id: String,
    pub name: String,
    pub annotation: String,
    #[serde(flatten)]
    pub variant: DynPropertyTypeData,
}

impl<'de> JsonSerde<'de> for DynPropertyData {}

impl DynPropertyData {
    /// Shorthand to create new generic `DynPropertyData` instance given a properties
    /// `id`, `name`, `formula`, and `annotation`.
    pub fn new_generic(id: &str, name: &str, formula: &str, annotation: &str) -> DynPropertyData {
        let variant = DynPropertyTypeData::GenericDynProp(GenericDynPropData {
            formula: formula.to_string(),
        });
        Self::new_raw(id, name, variant, annotation)
    }

    /// Create new `DynPropertyData` object given a reference to a property and its `id`.
    pub fn from_property(id: &DynPropertyId, property: &DynProperty) -> DynPropertyData {
        let name = property.get_name();
        let annot = property.get_annotation();
        let variant = match property.get_prop_data() {
            DynPropertyType::GenericDynProp(p) => {
                DynPropertyTypeData::GenericDynProp(GenericDynPropData {
                    formula: p.raw_formula.to_string(),
                })
            }
            DynPropertyType::ExistsFixedPoint(p) => {
                DynPropertyTypeData::ExistsFixedPoint(ExistsFixedPointData {
                    dataset: p.dataset.as_ref().map(|i| i.to_string()),
                    observation: p.observation.as_ref().map(|i| i.to_string()),
                })
            }
            DynPropertyType::ExistsTrapSpace(p) => {
                DynPropertyTypeData::ExistsTrapSpace(ExistsTrapSpaceData {
                    dataset: p.dataset.clone().map(|i| i.to_string()),
                    observation: p.observation.clone().map(|i| i.to_string()),
                    minimal: p.minimal,
                    nonpercolable: p.nonpercolable,
                })
            }
            DynPropertyType::ExistsTrajectory(p) => {
                DynPropertyTypeData::ExistsTrajectory(ExistsTrajectoryData {
                    dataset: p.dataset.as_ref().map(|i| i.to_string()),
                })
            }
            DynPropertyType::HasAttractor(p) => {
                DynPropertyTypeData::HasAttractor(HasAttractorData {
                    dataset: p.dataset.as_ref().map(|i| i.to_string()),
                    observation: p.observation.as_ref().map(|o| o.to_string()),
                })
            }
            DynPropertyType::AttractorCount(p) => {
                DynPropertyTypeData::AttractorCount(AttractorCountData {
                    minimal: p.minimal,
                    maximal: p.maximal,
                })
            }
        };
        Self::new_raw(id.as_str(), name, variant, annot)
    }

    /// Extract the corresponding `DynProperty` instance from this `DynPropertyData`.
    pub fn to_property(&self) -> Result<DynProperty, String> {
        let name = self.name.as_str();
        let annot = self.annotation.as_str();
        let property = match &self.variant {
            DynPropertyTypeData::GenericDynProp(p) => {
                DynProperty::try_mk_generic(name, &p.formula, annot)?
            }
            DynPropertyTypeData::ExistsFixedPoint(p) => DynProperty::mk_fixed_point(
                name,
                p.dataset.as_ref().and_then(|t| DatasetId::new(t).ok()),
                p.observation
                    .as_ref()
                    .and_then(|t| ObservationId::new(t).ok()),
                annot,
            ),
            DynPropertyTypeData::ExistsTrapSpace(p) => DynProperty::mk_trap_space(
                name,
                p.dataset.as_ref().and_then(|t| DatasetId::new(t).ok()),
                p.observation
                    .as_ref()
                    .and_then(|t| ObservationId::new(t).ok()),
                p.minimal,
                p.nonpercolable,
                annot,
            ),
            DynPropertyTypeData::ExistsTrajectory(p) => {
                let dataset = p.dataset.as_ref().and_then(|t| DatasetId::new(t).ok());
                DynProperty::mk_trajectory(name, dataset, annot)
            }
            DynPropertyTypeData::HasAttractor(p) => DynProperty::mk_has_attractor(
                name,
                p.dataset.as_ref().and_then(|t| DatasetId::new(t).ok()),
                p.observation
                    .as_ref()
                    .and_then(|t| ObservationId::new(t).ok()),
                annot,
            ),
            DynPropertyTypeData::AttractorCount(p) => {
                DynProperty::try_mk_attractor_count(name, p.minimal, p.maximal, annot)?
            }
        };
        Ok(property)
    }

    /// **(internal)** Shorthand to create new `DynPropertyData` instance given all its fields.
    fn new_raw(
        id: &str,
        name: &str,
        variant: DynPropertyTypeData,
        annotation: &str,
    ) -> DynPropertyData {
        DynPropertyData {
            id: id.to_string(),
            name: name.to_string(),
            annotation: annotation.to_string(),
            variant,
        }
    }
}