use num::Float;
use std::num::{Zero, One};
use std::ops::*;
use cplx::{Complex, from_rect};

pub trait Lerp<A> {
    fn lerp(a: A, x: Self, y: Self) -> Self;
}

impl<A: Copy> Lerp<A> for Complex<Complex<A>>
  where A: PartialEq + Zero + Add<Output = A> + AddAssign + Neg<Output = A> +
           Mul<Output = A> + One + Float {
    fn lerp(a: A, x: Self, y: Self) -> Self {
        let s = ::quaternion::to_component_matrix(x);
        let t = ::quaternion::to_component_matrix(y);
        let ω = (|x|x+x)(::linea::dot(s, t).acos());
        let r = if <A as Zero>::zero() == ω {
            s.scale(<A as One>::one() - a) + t.scale(a)
        } else {
            (s.scale(((<A as One>::one()-a)*ω).sin()) + t.scale((a*ω).sin()))
                .unscale(ω.sin())
        };
        from_rect(from_rect(r[0], r[1]), from_rect(r[2], r[3]))
    }
}

impl<A: Copy + PartialEq + Zero + One, B: Lerp<A>> Lerp<A> for Option<B> {
    fn lerp(a: A, x: Self, y: Self) -> Self {
        match (x, y, A::zero() == a, <A as One>::one() == a) {
            (Some(x), Some(y), _, _) => Some(B::lerp(a, x, y)),
            (Some(x), None, true, _) => Some(x),
            (None, Some(y), _, true) => Some(y),
            _ => None,
        }
    }
}

impl<A: Copy, B: Lerp<A>> Lerp<A> for Vec<B> {
    fn lerp(a: A, x: Self, y: Self) -> Self {
        Iterator::zip(x.into_iter(), y.into_iter()).map(|(x, y)| B::lerp(a, x, y)).collect()
    }
}