use fixed::traits::{Fixed, ToFixed};
use fixed::types::extra::*;

use crate::num;
use crate::traits::*;
use crate::types::*;
use crate::types::coordinate::*;

macro_rules! impl_dimension {
  (
    $point:ident, $vector:ident, $matrix:ident, $position:ident, $displacement:ident,
    $transform:ident, $fixed_point:ident, $fixed_vector:ident, $fixed_matrix:ident,
    $dimension:expr, $mat_dims:expr, [$($component:ident),+]
  ) => {
    #[doc = "Convert to fixed precision"]
    #[doc = $dimension]
    #[doc = "vector"]
    pub trait $fixed_vector <S> {
      fn from_num <N : ToFixed> (num : $vector <N>) -> Self;
      fn wrapping_from_num <N : ToFixed> (num : $vector <N>) -> Self;
    }
    impl <S> $fixed_vector <S> for $vector <S> where S : Fixed {
      fn from_num <N : ToFixed> (num : $vector <N>) -> Self {
        $vector::new ($(num.$component.to_fixed()),+)
      }
      fn wrapping_from_num <N : ToFixed> (num : $vector <N>) -> Self {
        $vector::new ($(num.$component.wrapping_to_fixed()),+)
      }
    }
    impl <S, U> $fixed_vector <S> for $displacement <S, U> where S : Fixed {
      fn from_num <N : ToFixed> (num : $vector <N>) -> Self {
        $vector::from_num (num).into()
      }
      fn wrapping_from_num <N : ToFixed> (num : $vector <N>) -> Self {
        $vector::wrapping_from_num (num).into()
      }
    }

    #[doc = "Convert to fixed precision"]
    #[doc = $dimension]
    #[doc = "point"]
    pub trait $fixed_point <S> {
      fn from_num <N : ToFixed> (num : $point <N>) -> Self;
      fn wrapping_from_num <N : ToFixed> (num : $point <N>) -> Self;
    }
    impl <S> $fixed_point <S> for $point <S> where S : Fixed {
      fn from_num <N : ToFixed> (num : $point <N>) -> Self {
        $vector::from_num (num.0).into()
      }
      fn wrapping_from_num <N : ToFixed> (num : $point <N>) -> Self {
        $vector::wrapping_from_num (num.0).into()
      }
    }
    impl <S, U> $fixed_point <S> for $position <S, U> where S : Fixed {
      fn from_num <N : ToFixed> (num : $point <N>) -> Self {
        $point::from_num (num).into()
      }
      fn wrapping_from_num <N : ToFixed> (num : $point <N>) -> Self {
        $point::wrapping_from_num (num).into()
      }
    }

    #[doc = "Convert to fixed precision"]
    #[doc = $mat_dims]
    #[doc = "matrix"]
    pub trait $fixed_matrix <S> {
      fn from_num <N : ToFixed> (num : $matrix <N>) -> Self;
      fn wrapping_from_num <N : ToFixed> (num : $matrix <N>) -> Self;
    }
    impl <S> $fixed_matrix <S> for $matrix <S> where S : Fixed {
      fn from_num <N : ToFixed> (num : $matrix <N>) -> Self {
        $matrix {
          cols: [
            $($vector::from_num (num.cols.$component)),+
          ].into()
        }
      }
      fn wrapping_from_num <N : ToFixed> (num : $matrix <N>) -> Self {
        $matrix {
          cols: [
            $($vector::wrapping_from_num (num.cols.$component)),+
          ].into()
        }
      }
    }
    impl <S, U, V> $fixed_matrix <S> for $transform <S, U, V> where
      S : Fixed + num::Zero + num::One
    {
      fn from_num <N : ToFixed> (num : $matrix <N>) -> Self {
        $matrix::from_num (num).into()
      }
      fn wrapping_from_num <N : ToFixed> (num : $matrix <N>) -> Self {
        $matrix::wrapping_from_num (num).into()
      }
    }
  }
}

impl_dimension!(
  Point2, Vector2, Matrix2,
  Position2, Displacement2, Transform2,
  FixedPoint2, FixedVector2, FixedMatrix2, "2D",
  "2x2", [x, y]);
impl_dimension!(
  Point3, Vector3, Matrix3,
  Position3, Displacement3, Transform3,
  FixedPoint3, FixedVector3, FixedMatrix3, "3D",
  "3x3", [x, y, z]);
impl_dimension!(
  Point4, Vector4, Matrix4,
  Position4, Displacement4, Transform4,
  FixedPoint4, FixedVector4, FixedMatrix4, "4D",
  "4x4", [x, y, z, w]);

macro impl_real_fixed ($fixed:ident,
  $cordic_bound_1:ident, $cordic_bound_2:ident, $cordic_bound_3:ident,
  $fixed_sqrt_bound:path
) {
  impl <U> VectorSpace <Self> for fixed::$fixed <U> where
    U : Unsigned +
      IsLessOrEqual<$cordic_bound_2, Output = True> +
      IsLessOrEqual<$cordic_bound_1, Output = True> +
      $fixed_sqrt_bound
  {
    type NonZero = NonZero <fixed::$fixed <U>>;
    fn map <F> (self, mut f : F) -> Self where F : FnMut (Self) -> Self {
      f (self)
    }
  }
  impl <U> Module <Self> for fixed::$fixed <U> where
    U : Unsigned +
      IsLessOrEqual<$cordic_bound_2, Output = True> +
      IsLessOrEqual<$cordic_bound_1, Output = True> +
      $fixed_sqrt_bound
  {
    type LinearEndo = Self;
  }
  impl <U> LinearMap <Self, Self, Self> for fixed::$fixed <U> where
    U : Unsigned +
      IsLessOrEqual<$cordic_bound_2, Output = True> +
      IsLessOrEqual<$cordic_bound_1, Output = True> +
      $fixed_sqrt_bound
  {
    fn determinant (self) -> Self {
      self
    }
    fn transpose (self) -> Self {
      self
    }
  }
  impl <U> Real for fixed::$fixed <U> where
    U : Unsigned +
      IsLessOrEqual<$cordic_bound_2, Output = True> +
      IsLessOrEqual<$cordic_bound_1, Output = True> +
      $fixed_sqrt_bound
  {
    fn pi() -> Self {
      Self::from_num (fixed::consts::PI)
    }
    fn frac_pi_3() -> Self {
      Self::from_num (fixed::consts::FRAC_PI_3)
    }
    fn sqrt_3() -> Self {
      Self::from_num (fixed::consts::SQRT_3)
    }
    fn frac_1_sqrt_3() -> Self {
      Self::from_num (fixed::consts::FRAC_1_SQRT_3)
    }
  }
  impl <U> Rational for fixed::$fixed <U> where U : Unsigned + $fixed_sqrt_bound {
    fn floor (self) -> Self {
      self.floor()
    }
    fn ceil (self) -> Self {
      self.ceil()
    }
    fn trunc (self) -> Self {
      self.int()
    }
    fn fract (self) -> Self {
      self.frac()
    }
  }
  impl <U> MinMax       for fixed::$fixed <U> where U : Unsigned + $fixed_sqrt_bound {
    fn min (self, other : Self) -> Self {
      Ord::min (self, other)
    }
    fn max (self, other : Self) -> Self {
      Ord::max (self, other)
    }
    fn clamp (self, min : Self, max : Self) -> Self {
      Ord::clamp (self, min, max)
    }
  }
  impl <U> Sqrt for fixed::$fixed <U> where U : $fixed_sqrt_bound {
    fn sqrt (self) -> Self {
      fixed_sqrt::FastSqrt::fast_sqrt (self)
    }
  }
  impl <U> Exp for fixed::$fixed <U> where
    U : 'static + Unsigned + IsLessOrEqual<$cordic_bound_3, Output = True> +
      IsLessOrEqual<$cordic_bound_2, Output = True> +
      IsLessOrEqual<$cordic_bound_1, Output = True>
  {
    fn exp (self) -> Self {
      cordic::exp (self)
    }
  }
  impl <U> Powi for fixed::$fixed <U> where
    U : Unsigned + IsLessOrEqual<$cordic_bound_2, Output = True> + $fixed_sqrt_bound
  {
    fn powi (self, _n : i32) -> Self {
      unimplemented!("TODO: fixed integer powers")
    }
  }
  impl <U> Powf for fixed::$fixed <U> where
    U : Unsigned + IsLessOrEqual<$cordic_bound_2, Output = True> + $fixed_sqrt_bound
  {
    fn powf (self, _n : Self) -> Self {
      unimplemented!("TODO: fixed fractional powers")
    }
  }

  impl <U> Trig for fixed::$fixed <U> where
    U : 'static + Unsigned + IsLessOrEqual<$cordic_bound_3, Output = True> +
      IsLessOrEqual<$cordic_bound_2, Output = True> +
      IsLessOrEqual<$cordic_bound_1, Output = True>
  {
    fn sin (self) -> Self {
      cordic::sin (self)
    }
    fn sin_cos (self) -> (Self, Self) {
      cordic::sin_cos (self)
    }
    fn cos (self) -> Self {
      cordic::cos (self)
    }
    fn tan (self) -> Self {
      cordic::tan (self)
    }
    fn asin (self) -> Self {
      cordic::asin (self)
    }
    fn acos (self) -> Self {
      cordic::acos (self)
    }
    fn atan (self) -> Self {
      cordic::atan (self)
    }
    fn atan2 (self, other : Self) -> Self {
      cordic::atan2 (self, other)
    }
  }
}

impl_real_fixed!(FixedI8, U5, U6, U8, fixed_sqrt::traits::LtU8);
impl_real_fixed!(FixedI16, U13, U14, U16, fixed_sqrt::traits::LtU16);
impl_real_fixed!(FixedI32, U29, U30, U32, fixed_sqrt::traits::LtU32);
impl_real_fixed!(FixedI64, U61, U62, U64, fixed_sqrt::traits::LtU64);
