    {-# LANGUAGE GADTs                #-} -- for ghc < 7.8, TypeFamilies covers GADT patten mathcing in > 7.8
    {-# LANGUAGE FlexibleContexts     #-}
    {-# LANGUAGE FlexibleInstances    #-}
    {-# LANGUAGE TypeFamilies         #-}
    {-# LANGUAGE UndecidableInstances #-}

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Diagrams.Tangent
    -- Copyright   :  (c) 2013 diagrams-lib team (see LICENSE)
    -- License     :  BSD-style (see LICENSE)
    -- Maintainer  :  diagrams-discuss@googlegroups.com
    --
    -- Computing tangent and normal vectors for segments and trails.
    --
    -----------------------------------------------------------------------------
    module Diagrams.Tangent
        ( -- ** Tangents
          tangentAtParam
        , tangentAtStart
        , tangentAtEnd

          -- ** Normals
        , normalAtParam
        , normalAtStart
        , normalAtEnd

          -- ** Tangent newtype
        , Tangent(..)
        )
        where

    import           Diagrams.Core
    import           Diagrams.Located
    import           Diagrams.Parametric
    import           Diagrams.Segment

    import           Linear.Vector
    import           Linear.Metric
    import           Linear.V2

    ------------------------------------------------------------
    -- Tangent
    ------------------------------------------------------------

    -- | A newtype wrapper used to give different instances of
    --   'Parametric' and 'EndValues' that compute tangent vectors.
    newtype Tangent t = Tangent t

    type instance V (Tangent t) = V t
    type instance N (Tangent t) = N t
    type instance Codomain (Tangent t) = V t

    instance DomainBounds t => DomainBounds (Tangent t) where
      domainLower (Tangent t) = domainLower t
      domainUpper (Tangent t) = domainUpper t

    instance Parametric (Tangent t) => Parametric (Tangent (Located t)) where
      Tangent l `atParam` p = Tangent (unLoc l) `atParam` p

    instance (DomainBounds t, EndValues (Tangent t))
        => EndValues (Tangent (Located t)) where
      atStart (Tangent l) = atStart (Tangent (unLoc l))
      atEnd   (Tangent l) = atEnd   (Tangent (unLoc l))

    -- | Compute the tangent vector to a segment or trail at a particular
    --   parameter.
    --
    --   Examples of more specific types this function can have include
    --
    --   * @Segment Closed V2 -> Double -> V2 Double@
    --
    --   * @Trail' Line V2 -> Double -> V2 Double@
    --
    --   * @Located (Trail V2) -> Double -> V2 Double@
    --
    --   See the instances listed for the 'Tangent' newtype for more.
    tangentAtParam :: Parametric (Tangent t) => t -> N t -> Vn t
    tangentAtParam t p = Tangent t `atParam` p

    -- | Compute the tangent vector at the start of a segment or trail.
    tangentAtStart :: EndValues (Tangent t) => t -> Vn t
    tangentAtStart = atStart . Tangent

    -- | Compute the tangent vector at the end of a segment or trail.
    tangentAtEnd :: EndValues (Tangent t) => t -> Vn t
    tangentAtEnd = atEnd . Tangent

    --------------------------------------------------
    -- Segment

    instance (Additive v, Num n)
        => Parametric (Tangent (Segment Closed v n)) where
      Tangent (Linear (OffsetClosed v)) `atParam` _ = v
      Tangent (Cubic c1 c2 (OffsetClosed x2)) `atParam` p
        = (3*(3*p*p-4*p+1))*^c1 ^+^ (3*(2-3*p)*p)*^c2 ^+^ (3*p*p)*^x2

    instance (Additive v, Num n)
        => EndValues (Tangent (Segment Closed v n)) where
      atStart (Tangent (Linear (OffsetClosed v)))      = v
      atStart (Tangent (Cubic c1 _ _))                 = c1
      atEnd   (Tangent (Linear (OffsetClosed v)))      = v
      atEnd   (Tangent (Cubic _ c2 (OffsetClosed x2))) = x2 ^-^ c2

    instance (Additive v, Num n)
        => Parametric (Tangent (FixedSegment v n)) where
      atParam (Tangent fSeg) = atParam $ Tangent (fromFixedSeg fSeg)

    instance (Additive v, Num n)
        => EndValues (Tangent (FixedSegment v n)) where
      atStart (Tangent fSeg) = atStart $ Tangent (fromFixedSeg fSeg)
      atEnd (Tangent fSeg)   = atEnd $ Tangent (fromFixedSeg fSeg)

    ------------------------------------------------------------
    -- Normal
    ------------------------------------------------------------

    -- | Compute the (unit) normal vector to a segment or trail at a
    --   particular parameter.
    --
    --   Examples of more specific types this function can have include
    --
    --   * @Segment Closed V2 Double -> Double -> V2 Double@
    --
    --   * @Trail' Line V2 Double -> Double -> V2 Double@
    --
    --   * @Located (Trail V2 Double) -> Double -> V2 Double@
    --
    --   See the instances listed for the 'Tangent' newtype for more.
    normalAtParam
      :: (InSpace V2 n t, Parametric (Tangent t), Floating n)
      => t -> n -> V2 n
    normalAtParam t p = normize (t `tangentAtParam` p)

    -- | Compute the normal vector at the start of a segment or trail.
    normalAtStart
      :: (InSpace V2 n t, EndValues (Tangent t), Floating n)
      => t -> V2 n
    normalAtStart = normize . tangentAtStart

    -- | Compute the normal vector at the end of a segment or trail.
    normalAtEnd
      :: (InSpace V2 n t, EndValues (Tangent t), Floating n)
      => t -> V2 n
    normalAtEnd = normize . tangentAtEnd

    -- | Construct a normal vector from a tangent.
    normize :: Floating n => V2 n -> V2 n
    normize = negated . perp . signorm