    {-# LANGUAGE DeriveFunctor        #-}
    {-# LANGUAGE FlexibleContexts     #-}
    {-# LANGUAGE TypeFamilies         #-}
    {-# LANGUAGE UndecidableInstances #-}
    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Diagrams.Direction
    -- Copyright   :  (c) 2014 diagrams-lib team (see LICENSE)
    -- License     :  BSD-style (see LICENSE)
    -- Maintainer  :  diagrams-discuss@googlegroups.com
    --
    -- Type for representing directions, polymorphic in vector space
    --
    -----------------------------------------------------------------------------

    module Diagrams.Direction
           ( Direction
           , _Dir
           , direction, dir, fromDirection, fromDir
           , angleBetweenDirs
           , dirBetween
           ) where

    import           Control.Lens   (Iso', iso)

    import           Diagrams.Angle
    import           Diagrams.Core

    import           Linear.Affine
    import           Linear.Metric
    import           Linear.Vector

    --------------------------------------------------------------------------------
    -- Direction

    -- | A vector is described by a @Direction@ and a magnitude.  So we
    -- can think of a @Direction@ as a vector that has forgotten its
    -- magnitude.  @Direction@s can be used with 'fromDirection' and the
    -- lenses provided by its instances.
    newtype Direction v n = Dir (v n)
      deriving (Read, Show, Eq, Ord, Functor) -- todo: special instances

    type instance V (Direction v n) = v
    type instance N (Direction v n) = n

    instance (V (v n) ~ v, N (v n) ~ n, Transformable (v n)) => Transformable (Direction v n) where
      transform t (Dir v) = Dir (transform t v)

    instance HasTheta v => HasTheta (Direction v) where
      _theta = _Dir . _theta

    instance HasPhi v => HasPhi (Direction v) where
      _phi = _Dir . _phi

    -- | _Dir is provided to allow efficient implementations of functions
    --   in particular vector-spaces, but should be used with care as it
    --   exposes too much information.
    _Dir :: Iso' (Direction v n) (v n)
    _Dir = iso (\(Dir v) -> v) Dir

    -- | @direction v@ is the direction in which @v@ points.  Returns an
    --   unspecified value when given the zero vector as input.
    direction :: v n -> Direction v n
    direction = Dir

    -- | Synonym for 'direction'.
    dir :: v n -> Direction v n
    dir = Dir

    -- | @fromDirection d@ is the unit vector in the direction @d@.
    fromDirection :: (Metric v, Floating n) => Direction v n -> v n
    fromDirection (Dir v) = signorm v

    -- | Synonym for 'fromDirection'.
    fromDir :: (Metric v, Floating n) => Direction v n -> v n
    fromDir (Dir v) = signorm v

    -- | compute the positive angle between the two directions in their common plane
    angleBetweenDirs :: (Metric v, Floating n, Ord n)
      => Direction v n -> Direction v n -> Angle n
    angleBetweenDirs d1 d2 = angleBetween (fromDirection d1) (fromDirection d2)

    -- | @dirBetween p q@ returns the direction from @p@ to @q@.
    dirBetween :: (Additive v, Num n) => Point v n -> Point v n -> Direction v n
    dirBetween p q = dir $ q .-. p