{-# LANGUAGE ConstraintKinds       #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE TypeFamilies          #-}


module Diagrams.Test.Transform where

import           Test.Tasty
import           Test.Tasty.QuickCheck
import           Diagrams.Prelude
import           Diagrams.Direction
import Instances

tests :: TestTree
tests = testGroup "Transform" [

        testProperty "rotating a vector by a number then its additive inverse will yield the original vector" $
          \θ a -> rotate ((θ * (-1)) @@ deg) (rotate ((θ :: Double) @@ deg) (a ::  V2 Double)) =~ a
        , testProperty "under rotated allows scaling along an angle" $
          \θ f a -> under (rotated ((θ :: Double) @@ deg)) (scaleX (f :: Double)) (a ::  V2 Double) =~ (rotate (negated (θ @@ deg)) . (scaleX f) . rotate (θ @@ deg)) a
        , testProperty "a rotation of 0 does nothing" $
          \a -> rotate (0 @@ deg) (a ::  V2 Double) =~ a
        , testProperty "adding 360 degrees to a turn does nothing" $
          \c a -> rotate (((c :: Double) + 360) @@ deg) (a ::  V2 Double) =~ rotate (c @@ deg) a
        , testProperty "over rotated allows scaling along x of a rotated shape" $
          \θ f a -> over (rotated ((θ :: Double) @@ deg)) (scaleX (f :: Double)) (a ::  V2 Double) =~ (rotate (θ @@ deg) . (scaleX f) . rotate (negated (θ @@ deg))) a
        , testProperty "scaleX" $
          \f a b -> (scaleX (f :: Double)) (V2 (a ::  Double) b) =~ V2 (a * f) b
        , testProperty "scaleY" $
          \f a b -> (scaleY (f :: Double)) (V2 (a ::  Double) b) =~ V2 a  (f * b)
        , testProperty "reflectX" $
          \a b -> reflectX (V2 (a ::  Double) b) =~ V2 (a * (-1))  b
        , testProperty "reflectY" $
          \a b -> reflectY (V2 (a ::  Double) b) =~ V2 a  ((-1) * b)
        , testProperty "reflectXY" $
          \a b -> reflectXY (V2 (a ::  Double) b) =~ V2 b a
        , testProperty "translate" $
          \a b c d -> translateX (a :: Double) (translateY b (P (V2 c d ))) =~ P (V2 (a + c) (b + d))
        , testProperty "shear" $
          \a b c d -> shearX (a :: Double) (shearY b (V2 c d)) =~ V2 ((c*b + d) * a + c) (c*b + d)
        , testProperty "(1,0) rotateTo some dir will return normalised dir" $
          \(NonZero a) b -> rotateTo  (dir (V2 (a :: Double) b)) (V2 1 0) =~ signorm (V2 a b)
        , testProperty "rotates" $
          \a c -> rotate ((a :: Double)@@ deg) (c :: V2 Double)   =~ rotate'' ((a :: Double)@@ deg) (c :: V2 Double) && rotate ((a :: Double)@@ deg) (c :: V2 Double)   =~ rotate' ((a :: Double)@@ deg) (c :: V2 Double)
        , testProperty "reflectAbout works for a vector" $
          \a b c d e f -> reflectAbout (P (V2 (a :: Double) b)) (dir (V2 c d)) (V2 e f) =~  over (rotated (atan2A' d c)) reflectY (V2 e f)
        , testProperty "reflectAbout works for a point" $
          \a b c d e f -> reflectAbout (P (V2 (a :: Double) b)) (dir (V2 c d)) (P (V2 e f)) =~ translate (V2 a b)  ((over (rotated (atan2A' d c)) reflectY) ((translate (V2 (-a) (-b)) )  (P (V2 e f))))


        ]

--the original " '' " and a secondary " ' " rotate function for testing

rotation'' :: Floating n => Angle n -> T2 n
rotation'' theta = fromLinear r (linv r)
          where
            r               = rot theta <-> rot (negated theta)
            rot th (V2 x y) = V2 (cosA th * x - sinA th * y)
                                 (sinA th * x + cosA th * y)

rotate'' :: (InSpace V2 n t, Transformable t, Floating n) => Angle n -> t -> t
rotate'' = transform . rotation''

rotation' :: Floating n => Angle n -> T2 n
rotation' theta = fromLinear r (linv r)
            where
            r               = rot theta <-> rot (negated theta)
            rot th (V2 x y) = V2 (c * x - s * y)
                                 (s * x + c * y)
                                  where
                                    c = cosA th
                                    s = sinA th

rotate' :: (InSpace V2 n t, Transformable t, Floating n) => Angle n -> t -> t
rotate'  = transform . rotation'
