use super::{AccelModels, Dynamics, ForceModels, PropagatorConfig, SpacecraftSequence, Thruster};
#[cfg(feature = "python")]
use crate::dynamics::{Drag, SolarPressure};
use crate::propagators::{IntegratorMethod, IntegratorOptions};
use crate::{dynamics::PointMasses, io::gravity::GravityFieldConfig};
use pyo3::exceptions::PyException;
use {
    crate::Spacecraft,
    anise::almanac::Almanac,
    pyo3::prelude::*,
    std::{collections::HashMap, sync::Arc},
};

#[cfg(feature = "python")]
#[pymethods]
impl SpacecraftSequence {
    #[new]
    fn py_new() -> Self {
        SpacecraftSequence::default()
    }

    #[classmethod]
    #[pyo3(name = "from_dhall")]
    fn py_from_dhall(_cls: &Bound<'_, pyo3::types::PyType>, dhall_str: &str) -> PyResult<Self> {
        serde_dhall::from_str(dhall_str)
            .parse()
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
    }

    #[classmethod]
    #[pyo3(name = "from_yaml")]
    fn py_from_yaml(_cls: &Bound<'_, pyo3::types::PyType>, yaml_str: &str) -> PyResult<Self> {
        serde_yml::from_str(yaml_str)
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
    }

    #[pyo3(name = "setup")]
    fn py_setup(&mut self, py: Python<'_>, almanac: Py<Almanac>) -> PyResult<()> {
        let almanac_ref = almanac.borrow(py);
        self.setup(Arc::new(almanac_ref.clone()))
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))
    }

    #[pyo3(name = "propagate")]
    fn py_propagate(
        &self,
        py: Python<'_>,
        state: Spacecraft,
        until_phase: Option<String>,
        almanac: Py<Almanac>,
    ) -> PyResult<Vec<(Option<String>, Vec<Spacecraft>)>> {
        let almanac_ref = almanac.borrow(py);
        let trajs = self
            .propagate(state, until_phase, Arc::new(almanac_ref.clone()))
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;

        let result = trajs
            .into_iter()
            .map(|traj| (traj.name, traj.states))
            .collect();
        Ok(result)
    }

    #[getter]
    fn get_thruster_sets(&self) -> HashMap<String, Thruster> {
        self.thruster_sets.clone()
    }

    fn thruster_set_insert(&mut self, name: String, thruster: Thruster) {
        self.thruster_sets.insert(name, thruster);
    }
    fn thruster_set_remove(&mut self, name: String) -> PyResult<()> {
        if self.thruster_sets.remove(&name).is_none() {
            Err(PyException::new_err(format!("{name} not in thruster set")))
        } else {
            Ok(())
        }
    }
}

#[cfg(feature = "python")]
#[cfg_attr(feature = "python", pymethods)]
impl AccelModels {
    #[pyo3(signature=(point_masses=None, gravity_field=None))]
    #[new]
    fn py_new(
        point_masses: Option<PointMasses>,
        gravity_field: Option<GravityFieldConfig>,
    ) -> Self {
        Self {
            point_masses,
            gravity_field,
        }
    }

    fn __str__(&self) -> String {
        format!("{self:?}")
    }

    fn __repr__(&self) -> String {
        format!("{self:?} @ {self:p}")
    }
}

#[cfg(feature = "python")]
#[cfg_attr(feature = "python", pymethods)]
impl ForceModels {
    #[pyo3(signature=(solar_pressure=None, drag=None))]
    #[new]
    fn py_new(solar_pressure: Option<SolarPressure>, drag: Option<Drag>) -> Self {
        Self {
            solar_pressure,
            drag,
        }
    }

    fn __str__(&self) -> String {
        format!("{self:?}")
    }

    fn __repr__(&self) -> String {
        format!("{self:?} @ {self:p}")
    }
}

#[cfg(feature = "python")]
#[cfg_attr(feature = "python", pymethods)]
impl Dynamics {
    #[pyo3(signature=(accel_models=AccelModels::default(), force_models=ForceModels::default()))]
    #[new]
    fn py_new(accel_models: AccelModels, force_models: ForceModels) -> Self {
        Self {
            accel_models,
            force_models,
        }
    }

    fn __str__(&self) -> String {
        format!("{self:?}")
    }

    fn __repr__(&self) -> String {
        format!("{self:?} @ {self:p}")
    }
}

#[cfg(feature = "python")]
#[cfg_attr(feature = "python", pymethods)]
impl PropagatorConfig {
    #[new]
    fn py_new(dynamics: Dynamics, method: IntegratorMethod, options: IntegratorOptions) -> Self {
        Self {
            dynamics,
            method,
            options,
        }
    }

    fn __str__(&self) -> String {
        format!("{self:?}")
    }

    fn __repr__(&self) -> String {
        format!("{self:?} @ {self:p}")
    }
}
