

use std::{
    borrow::Cow,
    fmt::Debug,
    ops::{Add, Div, Mul, Sub},
};

use anyhow::Context;

pub trait FromFloat {
    fn from_f32(val :f32) -> Self;
    fn from_f64(val :f64) -> Self;
}

impl FromFloat for f32 {
    fn from_f32(val :f32) -> Self {
        val
    }

    fn from_f64(val :f64) -> Self {
        val as f32
    }
}

impl FromFloat for f64 {
    fn from_f32(val :f32) -> Self {
        val as f64
    }

    fn from_f64(val :f64) -> Self {
        val 
    }
}

pub trait HasTanh {
    fn tanh(self) -> Self ;
}

impl HasTanh for f32 {
    fn tanh(self) -> Self {
        self.tanh()
    }
}

impl HasTanh for f64 {
    fn tanh(self) -> Self {
        self.tanh()
    }
}

pub trait HasSigm {
    fn sigm(self) -> Self ;
}

impl HasSigm for f32 {
    fn sigm(self) -> Self {
        let x = self;
        let e = std::f32::consts::E;
        let ex = x.powf(x);
        (ex) / (1.0+ex)
    }
}

impl HasSigm for f64 {
    fn sigm(self) -> Self {
        let x = self;
        let e = std::f64::consts::E;
        let ex = x.powf(x);
        (ex) / (1.0+ex)
    }
}



pub trait TryToUsize: TryInto<usize, Error = Self::TryError> 
where std::result::Result<usize, Self::TryError> : Context<usize, Self::TryError>
{
    type TryError: Debug + Send + Sync + 'static + std::error::Error;
}

impl<T, E> TryToUsize for T 
where 
    T: TryInto<usize, Error = E>,
    E: Debug + Send + Sync + 'static + std::error::Error,
    std::result::Result<usize, E> : Context<usize, E>
{
    type TryError = E;
}


pub trait FromUsize {
    fn from_usize(v: usize) -> Self;
}

impl FromUsize for f32 {
    fn from_usize(v: usize) -> Self { v as f32 }
}

impl FromUsize for f64 {
    fn from_usize(v: usize) -> Self { v as f64 }
}

pub trait UpcastDestTraits<T>:
    Sync
    + Send
    + Sized
    + Clone
    + Copy
    + Debug
    + Add<Output = T>
    + Sub<Output = T>
    + Mul<Output = T>
    + Div<Output = T>
    + PartialEq<T>
    + PartialOrd<T>
    + HasTanh
    + HasSigm
    + FromUsize
{
}

pub trait UpcastSlice<X> {
    fn up<'a>(&'a self) -> Cow<'a, [X]>
    where
        [X]: ToOwned<Owned = Vec<X>>,
        X: UpcastDestTraits<X>;
}

macro_rules! upcast_impl {
    ($from:ty, $to:ty) => {
        impl UpcastSlice<$to> for [$from] {
            fn up<'a>(&'a self) -> Cow<'a, [$to]>
            where
                [$to]: ToOwned<Owned = Vec<$to>>,
            {
                self.iter().map(|&x| x as $to).collect::<Vec<_>>().into()
            }
        }
    };

    ($same:ty) => {
        impl UpcastSlice<$same> for [$same] {
            fn up<'a>(&'a self) -> Cow<'a, [$same]>
            where
                [$same]: ToOwned<Owned = Vec<$same>>,
            {
                self.into()
            }
        }

        impl UpcastDestTraits<$same> for $same {}
    };
}

upcast_impl!(f32, f64);
upcast_impl!(f64, f32);
upcast_impl!(f32);
upcast_impl!(f64);

#[cfg(test)]
mod test {
    use core::panic;
    use std::borrow::Cow;

    use crate::memory_manager::type_traits::{UpcastDestTraits, UpcastSlice};

    #[test]
    fn test_same_type() {
        let elem: [f32; 4] = [1.0, 2.0, 3.0, 4.0];
        let elem_up: Cow<[f32]> = elem.up();
        if let std::borrow::Cow::Owned(_) = elem_up {
            panic!("Allocate when same element")
        }
    }

    #[test]
    fn test_different_type() {
        let elem: [f32; 4] = [1.0, 2.0, 3.0, 4.0];
        let elem_up: Cow<[f64]> = elem.up();
        if let std::borrow::Cow::Borrowed(_) = elem_up {
            panic!("Not allocating with different type")
        }
    }

    fn generic_sum<A, B, T>(a: &[A], b: &[B]) -> Vec<T>
    where
        [A]: UpcastSlice<T>,
        [B]: UpcastSlice<T>,
        T: UpcastDestTraits<T>,
    {
        let a_up = a.up();
        let b_up = b.up();
        let mut ret = Vec::new();
        for (a, b) in a_up.iter().zip(b_up.iter()) {
            ret.push(*a + *b);
        }
        ret
    }

    #[test]
    fn sum_between_different_slice_in_generic() {
        let a: [f32; 4] = [1.0, 2.0, 3.0, 4.0];
        let b: [f64; 4] = [1.0, 2.0, 3.0, 4.0];
        let c: Vec<f64> = generic_sum(&a, &b);
        assert_eq!(c, vec![2.0, 4.0, 6.0, 8.0]);
    }
}
