    {-# LANGUAGE DeriveFunctor              #-}
    {-# LANGUAGE FlexibleContexts           #-}
    {-# LANGUAGE FlexibleInstances          #-}
    {-# LANGUAGE GeneralizedNewtypeDeriving #-}
    {-# LANGUAGE MultiParamTypeClasses      #-}
    {-# LANGUAGE RankNTypes                 #-}
    {-# LANGUAGE TypeFamilies               #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Diagrams.Angle
    -- Copyright   :  (c) 2013 diagrams-lib team (see LICENSE)
    -- License     :  BSD-style (see LICENSE)
    -- Maintainer  :  diagrams-discuss@googlegroups.com
    --
    -- Type for representing angles.
    --
    -----------------------------------------------------------------------------

    module Diagrams.Angle
           ( -- * Angle type
             Angle

             -- ** Using angles
           , (@@), rad, turn, deg

             -- ** Common angles
           , fullTurn, halfTurn, quarterTurn

             -- ** Trigonometric functions
           , sinA, cosA, tanA
           , asinA, acosA, atanA, atan2A, atan2A'

             -- ** Angle utilities
           , angleBetween, angleRatio, normalizeAngle

             -- ** Classes
           , HasTheta(..)
           , HasPhi(..)

             -- * Rotation
           , rotation, rotate
           ) where

    import           Control.Applicative
    import           Control.Lens            (AReview, Iso', Lens', iso, over,
                                              review, (^.))
    import           Data.Fixed
    import           Data.Monoid             hiding ((<>))
    import           Data.Monoid.Action
    import           Data.Semigroup
    import           Prelude
    import           Text.Read

    import           Diagrams.Core           (OrderedField)
    import           Diagrams.Core.Transform
    import           Diagrams.Core.V
    import           Diagrams.Points
    import           Linear.V2               (V2 (..))

    import           Linear.Metric
    import           Linear.Vector

    -- | Angles can be expressed in a variety of units.  Internally,
    --   they are represented in radians.
    newtype Angle n = Radians n
      deriving (Eq, Ord, Enum, Functor)

    instance Show n => Show (Angle n) where
      showsPrec d (Radians a) = showParen (d > 5) $
        showsPrec 6 a . showString " @@ rad"

    instance Read n => Read (Angle n) where
      readPrec = parens . prec 5 $ do
        x <- readPrec
        Symbol "@@" <- lexP
        Ident "rad" <- lexP
        pure (Radians x)

    type instance N (Angle n) = n

    instance Applicative Angle where
      pure = Radians
      {-# INLINE pure #-}
      Radians f <*> Radians x = Radians (f x)
      {-# INLINE (<*>) #-}

    instance Additive Angle where
      zero = pure 0
      {-# INLINE zero #-}

    instance Num n => Semigroup (Angle n) where
      (<>) = (^+^)
      {-# INLINE (<>) #-}

    instance Num n => Monoid (Angle n) where
      mappend = (<>)
      mempty  = Radians 0

    -- | The radian measure of an 'Angle' @a@ can be accessed as @a '^.'
    --   rad@. A new 'Angle' can be defined in radians as @pi \@\@
    --   rad@.
    rad :: Iso' (Angle n) n
    rad = iso (\(Radians r) -> r) Radians
    {-# INLINE rad #-}

    -- | The measure of an 'Angle' @a@ in full circles can be accessed as
    --   @a '^.' turn@.  A new 'Angle' of one-half circle can be defined in as
    --   @1/2 \@\@ turn@.
    turn :: Floating n => Iso' (Angle n) n
    turn = iso (\(Radians r) -> r / (2*pi)) (Radians . (*(2*pi)))
    {-# INLINE turn #-}

    -- | The degree measure of an 'Angle' @a@ can be accessed as @a
    --   '^.' deg@. A new 'Angle' can be defined in degrees as @180 \@\@
    --   deg@.
    deg :: Floating n => Iso' (Angle n) n
    deg = iso (\(Radians r) -> r / (2*pi/360)) (Radians . ( * (2*pi/360)))
    {-# INLINE deg #-}

    -- | An angle representing one full turn.
    fullTurn :: Floating v => Angle v
    fullTurn = 1 @@ turn

    -- | An angle representing a half turn.
    halfTurn :: Floating v => Angle v
    halfTurn = 0.5 @@ turn

    -- | An angle representing a quarter turn.
    quarterTurn :: Floating v => Angle v
    quarterTurn = 0.25 @@ turn

    -- | Calculate ratio between two angles.
    angleRatio :: Floating n => Angle n -> Angle n -> n
    angleRatio a b = (a ^. rad) / (b ^. rad)

    -- | The sine of the given @Angle@.
    sinA :: Floating n => Angle n -> n
    sinA (Radians r) = sin r

    -- | The cosine of the given @Angle@.
    cosA :: Floating n => Angle n -> n
    cosA (Radians r) = cos r

    -- | The tangent function of the given @Angle@.
    tanA :: Floating n => Angle n -> n
    tanA (Radians r) = tan r

    -- | The @Angle@ with the given sine.
    asinA :: Floating n => n -> Angle n
    asinA = Radians . asin

    -- | The @Angle@ with the given cosine.
    acosA :: Floating n => n -> Angle n
    acosA = Radians . acos

    -- | The @Angle@ with the given tangent.
    atanA :: Floating n => n -> Angle n
    atanA = Radians . atan

    -- | @atan2A y x@ is the angle between the positive x-axis and the vector given
    --   by the coordinates (x, y). The 'Angle' returned is in the [-pi,pi] range.
    atan2A :: RealFloat n => n -> n -> Angle n
    atan2A y x = Radians $ atan2 y x

    -- | Similar to 'atan2A' but without the 'RealFloat' constraint. This means it
    --   doesn't handle negative zero cases. However, for most geometric purposes,
    --   the outcome will be the same.
    atan2A' :: OrderedField n => n -> n -> Angle n
    atan2A' y x = atan2' y x @@ rad

    -- atan2 without negative zero tests
    atan2' :: OrderedField n => n -> n -> n
    atan2' y x
      | x > 0            =  atan (y/x)
      | x == 0 && y > 0  =  pi/2
      | x <  0 && y > 0  =  pi + atan (y/x)
      | x <= 0 && y < 0  = -atan2' (-y) x
      | y == 0 && x < 0  =  pi    -- must be after the previous test on zero y
      | x==0 && y==0     =  y     -- must be after the other double zero tests
      | otherwise        =  x + y -- x or y is a NaN, return a NaN (via +)

    -- | @30 \@\@ deg@ is an 'Angle' of the given measure and units.
    --
    -- >>> pi @@ rad
    -- 3.141592653589793 @@ rad
    --
    -- >>> 1 @@ turn
    -- 6.283185307179586 @@ rad
    --
    -- >>> 30 @@ deg
    -- 0.5235987755982988 @@ rad
    --
    --   For 'Iso''s, ('@@') reverses the 'Iso'' on its right, and applies
    --   the 'Iso'' to the value on the left. 'Angle's are the motivating
    --   example where this order improves readability.
    --
    --   This is the same as a flipped 'review'.
    --
    -- @
    -- ('@@') :: a -> 'Iso''      s a -> s
    -- ('@@') :: a -> 'Prism''    s a -> s
    -- ('@@') :: a -> 'Review'    s a -> s
    -- ('@@') :: a -> 'Equality'' s a -> s
    -- @
    (@@) :: b -> AReview a b -> a
    a @@ i = review i a

    infixl 5 @@

    -- | Compute the positive angle between the two vectors in their common
    --   plane in the [0,pi] range. For a signed angle see
    --   'Diagrams.TwoD.Vector.signedAngleBetween'.
    --
    --   Returns NaN if either of the vectors are zero.
    angleBetween  :: (Metric v, Floating n, Ord n) => v n -> v n -> Angle n
    angleBetween v1 v2 = acosA (min 1 . max (-1) $ signorm v1 `dot` signorm v2)
    -- N.B.: Currently discards the common plane information.

    -- | Normalize an angle so that it lies in the [0,tau) range.
    normalizeAngle :: (Floating n, Real n) => Angle n -> Angle n
    normalizeAngle = over rad (`mod'` (2 * pi))

    ------------------------------------------------------------
    -- Rotation

    -- These functions are defined here (instead of in
    -- Diagrams.TwoD.Transform) because the Action instance needs to go
    -- here.

    -- | Create a transformation which performs a rotation about the local
    --   origin by the given angle.  See also 'rotate'.
    rotation :: Floating n => Angle n -> Transformation V2 n
    rotation theta = fromLinear r (linv r)
        where
        c = cosA theta
        s = sinA theta
        r               = rot c s <-> rot c (-s)
        rot co si (V2 x y) = V2 (co * x - si * y)
                                (si * x + co * y)

    -- | Rotate about the local origin by the given angle. Positive angles
    --   correspond to counterclockwise rotation, negative to
    --   clockwise. The angle can be expressed using any of the 'Iso's on
    --   'Angle'.  For example, @rotate (1\/4 \@\@ 'turn')@, @rotate
    --   (tau\/4 \@\@ rad)@, and @rotate (90 \@\@ deg)@ all
    --   represent the same transformation, namely, a counterclockwise
    --   rotation by a right angle.  To rotate about some point other than
    --   the local origin, see 'rotateAbout'.
    --
    --   Note that writing @rotate (1\/4)@, with no 'Angle' constructor,
    --   will yield an error since GHC cannot figure out which sort of
    --   angle you want to use.  In this common situation you can use
    --   'rotateBy', which interprets its argument as a number of turns.

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

    -- | Angles act on other things by rotation.
    instance (V t ~ V2, N t ~ n, Transformable t, Floating n)
      => Action (Angle n) t where
      act = rotate

    ------------------------------------------------------------
    -- Polar Coordinates

    -- | The class of types with at least one angle coordinate, called '_theta'.
    class HasTheta t where
      _theta :: RealFloat n => Lens' (t n) (Angle n)

    -- | The class of types with at least two angle coordinates, the second called
    --   '_phi'. '_phi' is the positive angle measured from the z axis.
    class HasTheta t => HasPhi t where
      _phi :: RealFloat n => Lens' (t n) (Angle n)

    -- Point instances
    instance HasTheta v => HasTheta (Point v) where
      _theta = lensP . _theta
      {-# INLINE _theta #-}

    instance HasPhi v => HasPhi (Point v) where
      _phi = lensP . _phi
      {-# INLINE _phi #-}