//! Tagged coordinate types

use std::marker::PhantomData;

#[cfg(feature = "derive_serdes")]
use serde::{Deserialize, Serialize};

use crate::num;
use crate::traits::*;
use super::*;

macro_rules! impl_dimension {
  ( $point:ident, $vector:ident, $unit:ident, $nonzero:ident, $matrix:ident,
    $position:ident, $displacement:ident, $direction:ident, $posnorm:ident,
    $ortho_fun:ident,
    [$($displacement_lower:ident, $patch:ident)?],
    [$($projective:ident, $translation_column:ident)?],
    $transform:ident, $ndims:expr, $dimension:expr
  ) => {
    #[doc = $dimension]
    #[doc = "linear transformation between two coordinate systems tagged with units"]
    #[derive(Debug, Default, Eq, Display)]
    #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
    #[display("{}", _0)]
    #[repr(C)]
    pub struct $transform <S, U, V> (pub $matrix <S>, PhantomData <(U, V)>) where
      S : num::Zero + num::One;

    #[doc = $dimension]
    #[doc = "point coordinates tagged with unit"]
    #[derive(Debug, Default, Eq, Display)]
    #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
    #[display("{}", _0)]
    #[repr(C)]
    pub struct $position <S, U> (pub $point <S>, PhantomData <U>);

    #[doc = $dimension]
    #[doc = "linear displacement vector coordinates tagged with unit"]
    #[derive(Debug, Default, Eq, Display)]
    #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
    #[display("{}", _0)]
    #[repr(C)]
    pub struct $displacement <S, U> (pub $vector <S>, PhantomData <U>);

    #[doc = $dimension]
    #[doc = "unit displacement vector tagged with unit"]
    #[derive(Debug, Eq, Display)]
    #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
    #[display("{}", _0)]
    #[repr(C)]
    pub struct $direction <S, U> (pub $unit <S>, PhantomData <U>);

    #[doc = $dimension]
    #[doc = "non-zero vector tagged with unit"]
    #[derive(Debug, Eq, Display)]
    #[cfg_attr(feature = "derive_serdes", derive(Serialize, Deserialize))]
    #[display("{}", _0)]
    #[repr(C)]
    pub struct $posnorm <S, U> (pub $nonzero <S>, PhantomData <U>);

    ////////////////////////////////////////////////////////////////////////////////////
    //  impls                                                                         //
    ////////////////////////////////////////////////////////////////////////////////////

    //
    //  TransformN
    //
    impl <S, U, V> LinearMap <S, $displacement <S, U>, $displacement <S, V>> for
      $transform <S, U, V>
    where
      S : Ring,
      U : std::fmt::Debug,
      V : std::fmt::Debug
    {
      fn determinant (self) -> S {
        self.0.determinant()
      }
      fn transpose (self) -> Self {
        $transform (self.0.transpose(), PhantomData)
      }
    }
    impl <S, U, V> num::One for $transform <S, U, V> where S : Ring {
      fn one() -> Self {
        $matrix::one().into()
      }
    }
    impl <S, U, V> num::Zero for $transform <S, U, V> where S : Ring {
      fn zero() -> Self {
        $matrix::zero().into()
      }
      fn is_zero (&self) -> bool {
        self.0.is_zero()
      }
    }
    impl <S, U, V> std::iter::Sum for $transform <S, U, V> where S : Ring {
      fn sum <I> (iter : I) -> Self where I : Iterator <Item = Self> {
        use num::Zero;
        iter.fold ($transform::zero(), |acc, item| acc + item)
      }
    }
    impl <S, U, V> num::MulAddAssign for $transform <S, U, V> where S : Ring {
      fn mul_add_assign (&mut self, a : Self, b : Self) {
        self.0.mul_add_assign (a.0, b.0)
      }
    }
    impl <S, U, V> num::MulAdd for $transform <S, U, V> where S : Ring {
      type Output = Self;
      fn mul_add (self, a : Self, b : Self) -> Self {
        (self.0.mul_add (a.0, b.0)).into()
      }
    }
    impl <S, U, V> std::ops::Mul <Self> for $transform <S, U, V> where S : Ring {
      type Output = Self;
      fn mul (self, rhs : Self) -> Self {
        (self.0 * rhs.0).into()
      }
    }
    impl <S, U, V> std::ops::MulAssign <Self> for $transform <S, U, V> where S : Ring {
      fn mul_assign (&mut self, rhs : Self) {
        self.0 *= rhs.0;
      }
    }
    impl <S, U, V> std::ops::Mul <$displacement <S, U>> for $transform <S, U, V> where
      S : Ring
    {
      type Output = $displacement <S, V>;
      fn mul (self, rhs : $displacement <S, U>) -> $displacement <S, V> {
        (self.0 * rhs.0).into()
      }
    }
    impl <S, U, V> std::ops::SubAssign <Self> for $transform <S, U, V> where S : Ring {
      fn sub_assign (&mut self, rhs : Self) {
        self.0 -= rhs.0;
      }
    }
    impl <S, U, V> std::ops::Sub <Self> for $transform <S, U, V> where S : Ring {
      type Output = Self;
      fn sub (self, rhs : Self) -> Self {
        (self.0 - rhs.0).into()
      }
    }
    impl <S, U, V> std::ops::AddAssign <Self> for $transform <S, U, V> where S : Ring {
      fn add_assign (&mut self, rhs : Self) {
        self.0 += rhs.0;
      }
    }
    impl <S, U, V> std::ops::Add <Self> for $transform <S, U, V> where S : Ring {
      type Output = Self;
      fn add (self, rhs : Self) -> Self {
        (self.0 + rhs.0).into()
      }
    }
    impl <S, U, V> std::ops::Neg for $transform <S, U, V> where S : Ring {
      type Output = Self;
      fn neg (self) -> Self {
        (-self.0).into()
      }
    }
    impl <S, U, V> PartialEq for $transform <S, U, V> where
      S : PartialEq + num::Zero + num::One
    {
      fn eq (&self, other : &Self) -> bool {
        self.0 == other.0
      }
    }
    impl <S, U, V> Copy for $transform <S, U, V> where S : Copy + num::Zero + num::One
      { }
    impl <S, U, V> Clone for $transform <S, U, V> where
      S : Clone + num::Zero + num::One
    {
      fn clone (&self) -> Self {
        self.0.clone().into()
      }
    }
    impl <S, U, V> From <$matrix <S>> for $transform <S, U, V> where
      S : num::Zero + num::One
    {
      fn from (matrix : $matrix <S>) -> Self {
        $transform (matrix, PhantomData)
      }
    }

    //
    //  DisplacementN
    //
    impl <S, U> NormedVectorSpace <S> for $displacement <S, U> where
      S : Real,
      U : std::fmt::Debug
    {
      type Unit = $direction <S, U>;
      fn norm_squared (self) -> NonNegative <S> {
        self.self_dot()
      }
      fn norm_max (self) -> NonNegative <S> {
        self.0.norm_max()
      }
      fn normalize (self) -> Self::Unit {
        $direction ($unit::normalize (self.0), PhantomData)
      }
    }
    impl <S, U> InnerProductSpace <S> for $displacement <S, U> where
      S : Field,
      U : std::fmt::Debug
    {
      fn outer_product (self, rhs : Self) -> $transform <S, U, U> {
        let mut cols : $vector <$vector <S>> =
          [$vector::zero(); $ndims].into();
        for col in 0..$ndims {
          for row in 0..$ndims {
            cols[col][row] = self.0[row] * rhs.0[col];
          }
        }
        $transform ($matrix { cols }, PhantomData)
      }
      fn orthogonal (self) -> Self where S : approx::AbsDiffEq <Epsilon = S> {
        Self ($ortho_fun (self.0), PhantomData)
      }
    }
    impl <S, U> Dot <S> for $displacement <S, U> where
      S : Ring,
      U : std::fmt::Debug,
      $vector <S> : Dot <S>
    {
      fn dot (self, other : Self) -> S {
        self.0.dot (other.0)
      }
    }
    impl <S, U> VectorSpace <S> for $displacement <S, U> where
      S : Field,
      U : std::fmt::Debug,
      $vector <S> : VectorSpace <S>
    {
      type NonZero = $posnorm <S, U>;
      fn map <F> (self, f : F) -> Self where F : FnMut (S) -> S {
        self.0.map (f).into()
      }
    }
    impl <S, U> std::ops::Div <S> for $displacement <S, U> where
      $vector <S> : std::ops::Div <S, Output=$vector <S>>
    {
      type Output = Self;
      fn div (self, scalar : S) -> Self {
        (self.0 / scalar).into()
      }
    }
    impl <S, U> Module <S> for $displacement <S, U> where
      S : Ring,
      U : std::fmt::Debug,
      $vector <S> : Module <S>
    {
      type LinearEndo = $transform <S, U, U>;
    }
    impl <S, U> std::ops::Mul <S> for $displacement <S, U> where
      $vector <S> : std::ops::Mul <S, Output=$vector <S>>
    {
      type Output = Self;
      fn mul (self, scalar : S) -> Self {
        (self.0 * scalar).into()
      }
    }
    impl <S, U> GroupAction <$position <S, U>> for $displacement <S, U> where
      S : AdditiveGroup
    {
      fn action (self, position : $position <S, U>) -> $position <S, U> {
        (position.0 + self.0).into()
      }
    }
    impl <S, U> Group for $displacement <S, U> where S : AdditiveGroup {
      fn identity() -> Self {
        use num::Zero;
        Self::zero()
      }
      fn operation (a : Self, b : Self) -> Self {
        a + b
      }
    }
    impl <S, U> std::iter::Sum for $displacement <S, U> where S : AdditiveGroup {
      fn sum <I> (iter : I) -> Self where I : Iterator <Item = Self> {
        use num::Zero;
        iter.fold ($displacement::zero(), |acc, item| acc + item)
      }
    }
    impl <S, U> std::ops::Add <Self> for $displacement <S, U> where
      $vector <S> : std::ops::Add <Output=$vector <S>>
    {
      type Output = Self;
      fn add (self, other : Self) -> Self {
        (self.0 + other.0).into()
      }
    }
    impl <S, U> std::ops::Add <&Self> for $displacement <S, U> where
      $vector <S> : std::ops::Add <Output=$vector <S>>,
      S : Copy
    {
      type Output = Self;
      fn add (self, other : &Self) -> Self {
        (self.0 + other.0).into()
      }
    }
    impl <S, U> std::ops::Sub <Self> for $displacement <S, U> where
      $vector <S> : std::ops::Sub <Output=$vector <S>>
    {
      type Output = Self;
      fn sub (self, other : Self) -> Self {
        (self.0 - other.0).into()
      }
    }
    impl <S, U> std::ops::Sub <&Self> for $displacement <S, U> where
      $vector <S> : std::ops::Sub <Output=$vector <S>>,
      S : Copy
    {
      type Output = Self;
      fn sub (self, other : &Self) -> Self {
        (self.0 - other.0).into()
      }
    }
    impl <S, U> std::ops::AddAssign <Self> for $displacement <S, U> where
      $vector <S> : std::ops::AddAssign
    {
      fn add_assign (&mut self, other : Self) {
        self.0 += other.0
      }
    }
    impl <S, U> std::ops::SubAssign <Self> for $displacement <S, U> where
      $vector <S> : std::ops::SubAssign
    {
      fn sub_assign (&mut self, other : Self) {
        self.0 -= other.0
      }
    }
    impl <S, U> std::ops::Neg for $displacement <S, U> where
      $vector <S> : std::ops::Neg <Output=$vector <S>>
    {
      type Output = Self;
      fn neg (self) -> Self {
        (-self.0).into()
      }
    }
    impl <S, U> num::Zero for $displacement <S, U> where S : AdditiveGroup {
      fn zero() -> Self {
        $vector::zero().into()
      }
      fn is_zero (&self) -> bool {
        self.0 == $vector::zero()
      }
    }
    impl <S, U> PartialEq for $displacement <S, U> where $vector <S> : PartialEq {
      fn eq (&self, other : &Self) -> bool {
        self.0 == other.0
      }
    }
    impl <S, U> Copy for $displacement <S, U> where S : Copy { }
    impl <S, U> Clone for $displacement <S, U> where S : Clone {
      fn clone (&self) -> Self {
        self.0.clone().into()
      }
    }
    impl <S, U> From <$vector <S>> for $displacement <S, U> {
      fn from (vector : $vector <S>) -> Self {
        $displacement (vector, PhantomData)
      }
    }
    impl <S, U> From <$direction <S, U>> for $displacement <S, U> where S : Copy {
      fn from (direction : $direction <S, U>) -> Self {
        $displacement (*direction.0, PhantomData)
      }
    }
    impl <S, U> From <[S; $ndims]> for $displacement <S, U> {
      fn from (array : [S; $ndims]) -> Self {
        $displacement (array.into(), PhantomData)
      }
    }
    $(
    impl <S, U> From <$displacement_lower <S, U>> for $displacement <S, U> where
      S : Field
    {
      fn from (displacement : $displacement_lower <S, U>) -> Self {
        $displacement (displacement.0.into(), PhantomData)
      }
    }
    impl <S, U> From <($displacement_lower <S, U>, S)> for $displacement <S, U> where
      S : Field
    {
      fn from ((displacement, coord) : ($displacement_lower <S, U>, S)) -> Self {
        $displacement ((displacement.0, coord).into(), PhantomData)
      }
    }
    )?

    //
    //  DirectionN
    //
    impl <S, U> PartialEq for $direction <S, U> where $unit <S> : PartialEq {
      fn eq (&self, other : &Self) -> bool {
        self.0 == other.0
      }
    }

    //
    //  PosNormN
    //
    impl <S, U> PartialEq for $posnorm <S, U> where $nonzero <S> : PartialEq {
      fn eq (&self, other : &Self) -> bool {
        self.0 == other.0
      }
    }

    //
    //  PositionN
    //
    $(
    impl <S, U> ProjectiveSpace <S> for $projective <S, U> where
      S : Field,
      U : std::fmt::Debug
    {
      type Patch = $position <S, U>;
    }
    )?
    impl <S, U> AffineSpace <S> for $position <S, U> where
      S : Field,
      U : std::fmt::Debug
    {
      type Translation = $displacement <S, U>;
    }
    impl <S, U> Point <$displacement <S, U>> for $position <S, U> where S : Ring {
      fn to_vector (self) -> $displacement <S, U> {
        self.0.to_vector().into()
      }
      fn from_vector (displacement : $displacement <S, U>) -> Self {
        $point::from_vector (displacement.0).into()
      }
    }
    impl <S, U> std::ops::Sub <Self> for $position <S, U> where
      $vector <S> : std::ops::Sub <Output=$vector <S>>,
      S : AdditiveGroup
    {
      type Output = $displacement <S, U>;
      fn sub (self, other : Self) -> $displacement <S, U> {
        (self.0 - other.0).into()
      }
    }
    impl <S, U> PartialEq for $position <S, U> where $point <S> : PartialEq {
      fn eq (&self, other : &Self) -> bool {
        self.0 == other.0
      }
    }
    impl <S, U> Copy for $position <S, U> where S : Copy { }
    impl <S, U> Clone for $position <S, U> where S : Clone {
      fn clone (&self) -> Self {
        self.0.clone().into()
      }
    }
    impl <S, U> From <$point <S>> for $position <S, U> {
      fn from (point : $point <S>) -> Self {
        $position (point, PhantomData)
      }
    }
    $(
    impl <S, U> From<($patch <S, U>, S)> for $position <S, U> {
      fn from ((patch, scale) : ($patch <S, U>, S)) -> Self {
        $position ((patch.0, scale).into(), PhantomData)
      }
    }
    )?
    impl <S, U> From<[S; $ndims]> for $position <S, U> {
      fn from (array : [S; $ndims]) -> Self {
        $position (array.into(), PhantomData)
      }
    }
  }
}

impl_dimension!(Point2, Vector2, Unit2, NonZero2, Matrix2, Position2, Displacement2,
  Direction2, PosNorm2, ortho_vec2,
  [], [Position3, z], Transform2, 2, "2D");
impl_dimension!(Point3, Vector3, Unit3, NonZero3, Matrix3, Position3, Displacement3,
  Direction3, PosNorm3, ortho_vec3,
  [Displacement2, Position2], [Position4, w], Transform3, 3, "3D");
impl_dimension!(Point4, Vector4, Unit4, NonZero4, Matrix4, Position4, Displacement4,
  Direction4, PosNorm4, ortho_vec4,
  [Displacement3, Position3], [], Transform4, 4, "4D");

//
//  TransformN
//
impl <S, U, V> From <Transform2 <S, U, V>> for Transform3 <S, U, V> where S : Field {
  fn from (transform : Transform2 <S, U, V>) -> Self {
    Transform3 (transform.0.into(), PhantomData)
  }
}
impl <S, U, V> From <Transform3 <S, U, V>> for Transform4 <S, U, V> where S : Field {
  fn from (transform : Transform3 <S, U, V>) -> Self {
    Transform4 (transform.0.into(), PhantomData)
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[derive(Default)]
  struct Foo;
  #[derive(Default)]
  struct Bar;

  #[test]
  fn defaults() {
    assert_eq!(Position3::<f32, Foo>::default().0, Point3::origin());
    assert_eq!(Displacement3::<f32, Foo>::default().0, Vector3::zero());
    assert_eq!(Transform3::<f32, Foo, Bar>::default().0, Matrix3::identity());
  }
}
