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};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct GenericDynPropData {
    pub formula: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExistsFixedPointData {
    pub dataset: Option<String>,
    pub observation: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExistsTrapSpaceData {
    pub dataset: Option<String>,
    pub observation: Option<String>,
    pub minimal: bool,
    pub nonpercolable: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ExistsTrajectoryData {
    pub dataset: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct AttractorCountData {
    pub minimal: usize,
    pub maximal: usize,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct HasAttractorData {
    pub dataset: Option<String>,
    pub observation: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "variant")]
pub enum DynPropertyTypeData {
    GenericDynProp(GenericDynPropData),
    ExistsFixedPoint(ExistsFixedPointData),
    ExistsTrapSpace(ExistsTrapSpaceData),
    ExistsTrajectory(ExistsTrajectoryData),
    AttractorCount(AttractorCountData),
    HasAttractor(HasAttractorData),
}
#[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 {
    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)
    }
    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)
    }
    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)
    }
    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,
        }
    }
}