use crate::float_trait::Float;
use crate::nl_fit::{data::NormalizedData, CurveFitAlgorithm, LikeFloat, LnPrior};
use crate::time_series::TimeSeries;
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
pub trait FitModelTrait<T, U, const NPARAMS: usize>
where
    T: Float + Into<U>,
    U: LikeFloat,
{
    fn model(t: T, param: &[U; NPARAMS]) -> U
    where
        T: Float + Into<U>,
        U: LikeFloat;
}
pub trait FitFunctionTrait<T: Float, const NPARAMS: usize>:
    FitModelTrait<T, T, NPARAMS> + FitParametersInternalDimlessTrait<T, NPARAMS>
{
    fn f(t: T, values: &[T]) -> T {
        let internal = Self::dimensionless_to_internal(
            values[..NPARAMS]
                .try_into()
                .expect("values slice's length is too small"),
        );
        Self::model(t, &internal)
    }
}
pub trait FitDerivalivesTrait<T: Float, const NPARAMS: usize> {
    fn derivatives(t: T, param: &[T; NPARAMS], jac: &mut [T; NPARAMS]);
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(
    into = "FitArraySerde<T>",
    try_from = "FitArraySerde<T>",
    bound = "T: Debug + Clone + Serialize + DeserializeOwned + JsonSchema"
)]
pub struct FitArray<T, const NPARAMS: usize>(pub [T; NPARAMS]);
impl<T, const NPARAMS: usize> JsonSchema for FitArray<T, NPARAMS>
where
    T: schemars::JsonSchema,
{
    fn is_referenceable() -> bool {
        false
    }
    fn schema_name() -> String {
        FitArraySerde::<T>::schema_name()
    }
    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
        FitArraySerde::<T>::json_schema(gen)
    }
}
impl<T, const NPARAMS: usize> From<[T; NPARAMS]> for FitArray<T, NPARAMS> {
    fn from(item: [T; NPARAMS]) -> Self {
        Self(item)
    }
}
impl<T, const NPARAMS: usize> std::ops::Deref for FitArray<T, NPARAMS> {
    type Target = [T; NPARAMS];
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl<T, const NPARAMS: usize> From<FitArray<T, NPARAMS>> for FitArray<Option<T>, NPARAMS>
where
    T: Copy,
{
    fn from(item: FitArray<T, NPARAMS>) -> Self {
        let mut opt = [None; NPARAMS];
        for (&x, y) in item.0.iter().zip(opt.iter_mut()) {
            *y = Some(x);
        }
        opt.into()
    }
}
impl<T, const NPARAMS: usize> FitArray<Option<T>, NPARAMS>
where
    T: Clone,
{
    fn unwrap_with(&self, with: &FitArray<T, NPARAMS>) -> FitArray<T, NPARAMS> {
        let mut a = with.clone();
        for (opt, x) in self.0.iter().zip(a.0.iter_mut()) {
            match opt {
                Some(value) => *x = value.clone(),
                None => {}
            }
        }
        a
    }
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(
    rename = "FitArray",
    bound = "T: Debug + Clone + Serialize + DeserializeOwned + JsonSchema"
)]
struct FitArraySerde<T>(Vec<T>);
impl<T, const NPARAMS: usize> From<FitArray<T, NPARAMS>> for FitArraySerde<T> {
    fn from(item: FitArray<T, NPARAMS>) -> Self {
        Self(item.0.into())
    }
}
impl<T, const NPARAMS: usize> TryFrom<FitArraySerde<T>> for FitArray<T, NPARAMS> {
    type Error = &'static str;
    fn try_from(item: FitArraySerde<T>) -> Result<Self, Self::Error> {
        Ok(Self(
            item.0
                .try_into()
                .map_err(|_| "wrong size of the FitArray object")?,
        ))
    }
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
pub struct FitInitsBoundsArrays<const NPARAMS: usize> {
    pub init: FitArray<f64, NPARAMS>,
    pub lower: FitArray<f64, NPARAMS>,
    pub upper: FitArray<f64, NPARAMS>,
}
impl<const NPARAMS: usize> FitInitsBoundsArrays<NPARAMS> {
    pub fn new(init: [f64; NPARAMS], lower: [f64; NPARAMS], upper: [f64; NPARAMS]) -> Self {
        Self {
            init: init.into(),
            lower: lower.into(),
            upper: upper.into(),
        }
    }
}
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
pub struct OptionFitInitsBoundsArrays<const NPARAMS: usize> {
    pub init: FitArray<Option<f64>, NPARAMS>,
    pub lower: FitArray<Option<f64>, NPARAMS>,
    pub upper: FitArray<Option<f64>, NPARAMS>,
}
impl<const NPARAMS: usize> OptionFitInitsBoundsArrays<NPARAMS> {
    pub fn new(
        init: [Option<f64>; NPARAMS],
        lower: [Option<f64>; NPARAMS],
        upper: [Option<f64>; NPARAMS],
    ) -> Self {
        Self {
            init: init.into(),
            lower: lower.into(),
            upper: upper.into(),
        }
    }
    pub fn unwrap_with(&self, x: &FitInitsBoundsArrays<NPARAMS>) -> FitInitsBoundsArrays<NPARAMS> {
        FitInitsBoundsArrays {
            init: self.init.unwrap_with(&x.init),
            lower: self.lower.unwrap_with(&x.lower),
            upper: self.upper.unwrap_with(&x.upper),
        }
    }
}
impl<const NPARAMS: usize> From<FitInitsBoundsArrays<NPARAMS>>
    for OptionFitInitsBoundsArrays<NPARAMS>
{
    fn from(item: FitInitsBoundsArrays<NPARAMS>) -> Self {
        Self {
            init: item.init.into(),
            lower: item.lower.into(),
            upper: item.upper.into(),
        }
    }
}
pub trait FitInitsBoundsTrait<T: Float, const NPARAMS: usize> {
    fn init_and_bounds_from_ts(&self, ts: &mut TimeSeries<T>) -> FitInitsBoundsArrays<NPARAMS>;
}
pub trait FitParametersInternalDimlessTrait<U: LikeFloat, const NPARAMS: usize> {
    fn dimensionless_to_internal(params: &[U; NPARAMS]) -> [U; NPARAMS];
    fn internal_to_dimensionless(params: &[U; NPARAMS]) -> [U; NPARAMS];
}
pub trait FitParametersOriginalDimLessTrait<const NPARAMS: usize> {
    fn orig_to_dimensionless(
        norm_data: &NormalizedData<f64>,
        orig: &[f64; NPARAMS],
    ) -> [f64; NPARAMS];
    fn dimensionless_to_orig(
        norm_data: &NormalizedData<f64>,
        norm: &[f64; NPARAMS],
    ) -> [f64; NPARAMS];
}
pub trait FitParametersInternalExternalTrait<const NPARAMS: usize>:
    FitParametersInternalDimlessTrait<f64, NPARAMS> + FitParametersOriginalDimLessTrait<NPARAMS>
{
    fn convert_to_internal(
        norm_data: &NormalizedData<f64>,
        orig: &[f64; NPARAMS],
    ) -> [f64; NPARAMS] {
        Self::dimensionless_to_internal(&Self::orig_to_dimensionless(norm_data, orig))
    }
    fn convert_to_external(
        norm_data: &NormalizedData<f64>,
        params: &[f64; NPARAMS],
    ) -> [f64; NPARAMS] {
        Self::dimensionless_to_orig(norm_data, &Self::internal_to_dimensionless(params))
    }
}
pub trait FitFeatureEvaluatorGettersTrait<const NPARAMS: usize> {
    fn get_algorithm(&self) -> &CurveFitAlgorithm;
    fn ln_prior_from_ts<T: Float>(&self, ts: &mut TimeSeries<T>) -> LnPrior<NPARAMS>;
}