//! Implementation of the linear function

use alg_tools::bisection_tree::Support;
use alg_tools::bounds::{Bounded, Bounds, GlobalAnalysis, LocalAnalysis};
use alg_tools::loc::Loc;
use alg_tools::norms::*;
use alg_tools::sets::Cube;
use alg_tools::types::*;
use numeric_literals::replace_float_literals;
use serde::Serialize;

use alg_tools::euclidean::Euclidean;
use alg_tools::mapping::{Instance, Mapping};
use alg_tools::maputil::array_init;

/// Representation of the hat function $f(x)=1-\\|x\\|\_1/ε$ of `width` $ε$ on $ℝ^N$.
#[derive(Copy, Clone, Serialize, Debug, Eq, PartialEq)]
pub struct Linear<const N: usize, F: Float = f64> {
    /// The parameter $ε>0$.
    pub v: Loc<N, F>,
}

#[replace_float_literals(F::cast_from(literal))]
impl<F: Float, const N: usize> Mapping<Loc<N, F>> for Linear<N, F> {
    type Codomain = F;

    #[inline]
    fn apply<I: Instance<Loc<N, F>>>(&self, x: I) -> Self::Codomain {
        x.eval(|x| self.v.dot(x))
    }
}

#[replace_float_literals(F::cast_from(literal))]
impl<'a, F: Float, const N: usize> Support<N, F> for Linear<N, F> {
    #[inline]
    fn support_hint(&self) -> Cube<N, F> {
        array_init(|| [F::NEG_INFINITY, F::INFINITY]).into()
    }

    #[inline]
    fn in_support(&self, _x: &Loc<N, F>) -> bool {
        true
    }

    /*fn fully_in_support(&self, _cube : &Cube<F,N>) -> bool {
        todo!("Not implemented, but not used at the moment")
    }*/

    #[inline]
    fn bisection_hint(&self, _cube: &Cube<N, F>) -> [Option<F>; N] {
        [None; N]
    }
}

#[replace_float_literals(F::cast_from(literal))]
impl<'a, F: Float, const N: usize> GlobalAnalysis<F, Bounds<F>> for Linear<N, F> {
    #[inline]
    fn global_analysis(&self) -> Bounds<F> {
        Bounds(F::NEG_INFINITY, F::INFINITY)
    }
}

impl<'a, F: Float, const N: usize> LocalAnalysis<F, Bounds<F>, N> for Linear<N, F> {
    #[inline]
    fn local_analysis(&self, cube: &Cube<N, F>) -> Bounds<F> {
        let (lower, upper) = cube
            .iter_corners()
            .map(|x| self.apply(x))
            .fold((F::INFINITY, F::NEG_INFINITY), |(lower, upper), v| {
                (lower.min(v), upper.max(v))
            });
        Bounds(lower, upper)
    }
}

#[replace_float_literals(F::cast_from(literal))]
impl<'a, F: Float, const N: usize> Norm<Linfinity, F> for Linear<N, F> {
    #[inline]
    fn norm(&self, _: Linfinity) -> F {
        self.bounds().upper()
    }
}
