content: {-# LANGUAGE ConstraintKinds #-}
  {-# LANGUAGE BangPatterns               #-}
  {-# LANGUAGE DeriveFunctor              #-}
  {-# LANGUAGE EmptyDataDecls             #-}
  {-# LANGUAGE FlexibleContexts           #-}
  {-# LANGUAGE FlexibleInstances          #-}
  {-# LANGUAGE GADTs                      #-}
  {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  {-# LANGUAGE MultiParamTypeClasses      #-}
  {-# LANGUAGE StandaloneDeriving         #-}
  {-# LANGUAGE TemplateHaskell            #-}
  {-# LANGUAGE TypeFamilies               #-}
  {-# LANGUAGE TypeOperators              #-}
  {-# LANGUAGE UndecidableInstances       #-}
  
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  Diagrams.Segment
  -- Copyright   :  (c) 2011-2013 diagrams-lib team (see LICENSE)
  -- License     :  BSD-style (see LICENSE)
  -- Maintainer  :  diagrams-discuss@googlegroups.com
  --
  -- A /segment/ is a translation-invariant, atomic path.  Currently,
  -- there are two types: linear (/i.e./ just a straight line to the
  -- endpoint) and cubic Bézier curves (/i.e./ a curve to an endpoint
  -- with two control points).  This module contains tools for creating
  -- and manipulating segments, as well as a definition of segments with
  -- a fixed location (useful for backend implementors).
  --
  -- Generally speaking, casual users of diagrams should not need this
  -- module; the higher-level functionality provided by
  -- "Diagrams.Trail", "Diagrams.TrailLike", and "Diagrams.Path" should
  -- usually suffice.  However, directly manipulating segments can
  -- occasionally be useful.
  --
  -----------------------------------------------------------------------------
  
  module Diagrams.Segment
         ( -- * Open/closed tags
  
           Open, Closed
  
           -- * Segment offsets
  
         , Offset(..) , segOffset
  
           -- * Constructing and modifying segments
  
         , Segment(..), straight, bezier3, bézier3, reverseSegment, mapSegmentVectors
         , openLinear, openCubic
  
           -- * Fixed (absolutely located) segments
         , FixedSegment(..)
         , mkFixedSeg, fromFixedSeg
         , fixedSegIso
  
           -- * Segment measures
           -- $segmeas
  
         , SegCount(..)
         , ArcLength(..)
         , getArcLengthCached, getArcLengthFun, getArcLengthBounded
         , TotalOffset(..)
         , OffsetEnvelope(..), oeOffset, oeEnvelope
         , SegMeasure
  
         ) where
  
  import           Control.Lens              hiding (at, transform)
  import           Data.FingerTree
  import           Data.Monoid.MList
  import           Data.Semigroup
  import           Numeric.Interval.Kaucher  (Interval (..))
  import qualified Numeric.Interval.Kaucher  as I
  
  import           Linear.Affine
  import           Linear.Metric
  import           Linear.Vector             hiding (lerp)
  import           Linear.Vector.Compat      (lerp)
  
  import           Control.Applicative
  import           Diagrams.Core             hiding (Measured)
  import           Diagrams.Located
  import           Diagrams.Parametric
  import           Diagrams.Solve.Polynomial
  
  import           Data.Serialize            (Serialize)
  import qualified Data.Serialize            as Serialize
  
  ------------------------------------------------------------
  --  Open/closed type tags  ---------------------------------
  ------------------------------------------------------------
  
  -- Eventually we should use DataKinds for this, but not until we drop
  -- support for GHC 7.4.
  
  -- | Type tag for open segments.
  data Open
  
  -- | Type tag for closed segments.
  data Closed
  
  ------------------------------------------------------------
  --  Segment offsets  ---------------------------------------
  ------------------------------------------------------------
  
  -- | The /offset/ of a segment is the vector from its starting point
  --   to its end.  The offset for an /open/ segment is determined by
  --   the context, /i.e./ its endpoint is not fixed.  The offset for a
  --   /closed/ segment is stored explicitly, /i.e./ its endpoint is at
  --   a fixed offset from its start.
  data Offset c v n where
    OffsetOpen   :: Offset Open v n
    OffsetClosed :: v n -> Offset Closed v n
  
  deriving instance Show (v n) => Show (Offset c v n)
  deriving instance Eq   (v n) => Eq   (Offset c v n)
  deriving instance Ord  (v n) => Ord  (Offset c v n)
  
  instance Functor v => Functor (Offset c v) where
    fmap _ OffsetOpen       = OffsetOpen
    fmap f (OffsetClosed v) = OffsetClosed (fmap f v)
  
  instance Each (Offset c v n) (Offset c v' n') (v n) (v' n') where
    each f (OffsetClosed v) = OffsetClosed <$> f v
    each _ OffsetOpen       = pure OffsetOpen
    {-# INLINE each #-}
  
  -- | Reverses the direction of closed offsets.
  instance (Additive v, Num n) => Reversing (Offset c v n) where
    reversing (OffsetClosed off) = OffsetClosed $ negated off
    reversing a@OffsetOpen       = a
  
  type instance V (Offset c v n) = v
  type instance N (Offset c v n) = n
  
  instance Transformable (Offset c v n) where
    transform _ OffsetOpen       = OffsetOpen
    transform t (OffsetClosed v) = OffsetClosed (apply t v)
  
  ------------------------------------------------------------
  --  Constructing segments  ---------------------------------
  ------------------------------------------------------------
  
  -- | The atomic constituents of the concrete representation currently
  --   used for trails are /segments/, currently limited to
  --   single straight lines or cubic Bézier curves.  Segments are
  --   /translationally invariant/, that is, they have no particular
  --   \"location\" and are unaffected by translations.  They are,
  --   however, affected by other transformations such as rotations and
  --   scales.
  data Segment c v n
      = Linear !(Offset c v n)
        -- ^ A linear segment with given offset.
  
      | Cubic !(v n) !(v n) !(Offset c v n)
        -- ^ A cubic Bézier segment specified by
        --   three offsets from the starting
        --   point to the first control point,
        --   second control point, and ending
        --   point, respectively.
  
    deriving (Functor, Eq, Ord)
  
  instance Show (v n) => Show (Segment c v n) where
    showsPrec d seg = case seg of
      Linear (OffsetClosed v)       -> showParen (d > 10) $
        showString "straight " . showsPrec 11 v
      Cubic v1 v2 (OffsetClosed v3) -> showParen (d > 10) $
        showString "bézier3  " . showsPrec 11 v1 . showChar ' '
                               . showsPrec 11 v2 . showChar ' '
                               . showsPrec 11 v3
      Linear OffsetOpen             -> showString "openLinear"
      Cubic v1 v2 OffsetOpen        -> showParen (d > 10) $
        showString "openCubic " . showsPrec 11 v1 . showChar ' '
                                . showsPrec 11 v2
  
  
  instance Each (Segment c v n) (Segment c v' n') (v n) (v' n') where
    each f (Linear offset)      = Linear <$> each f offset
    each f (Cubic v1 v2 offset) = Cubic  <$> f v1 <*> f v2 <*> each f offset
    {-# INLINE each #-}
  
  -- | Reverse the direction of a segment.
  instance (Additive v, Num n) => Reversing (Segment Closed v n) where
    reversing = reverseSegment
  
  -- | Map over the vectors of each segment.
  mapSegmentVectors :: (v n -> v' n') -> Segment c v n -> Segment c v' n'
  mapSegmentVectors = over each
  
  -- Note, can't yet have Haddock comments on GADT constructors; see
  -- http://trac.haskell.org/haddock/ticket/43. For now we don't need
  -- Segment to be a GADT but we might in the future. (?)
  
  type instance V (Segment c v n) = v
  type instance N (Segment c v n) = n
  
  instance Transformable (Segment c v n) where
    transform = mapSegmentVectors . apply
  
  instance Renderable (Segment c v n) NullBackend where
    render _ _ = mempty
  
  -- | @'straight' v@ constructs a translationally invariant linear
  --   segment with direction and length given by the vector @v@.
  straight :: v n -> Segment Closed v n
  straight = Linear . OffsetClosed
  
  -- Note, if we didn't have a Linear constructor we could also create
  -- linear segments with @Cubic (v ^/ 3) (2 *^ (v ^/ 3)) v@.  Those
  -- would not be precisely the same, however, since we can actually
  -- observe how segments are parametrized.
  
  -- | @bezier3 c1 c2 x@ constructs a translationally invariant cubic
  --   Bézier curve where the offsets from the first endpoint to the
  --   first and second control point and endpoint are respectively
  --   given by @c1@, @c2@, and @x@.
  bezier3 :: v n -> v n -> v n -> Segment Closed v n
  bezier3 c1 c2 x = Cubic c1 c2 (OffsetClosed x)
  
  -- | @bézier3@ is the same as @bezier3@, but with more snobbery.
  bézier3 :: v n -> v n -> v n -> Segment Closed v n
  bézier3 = bezier3
  
  type instance Codomain (Segment Closed v n) = v
  
  -- | 'atParam' yields a parametrized view of segments as continuous
  --   functions @[0,1] -> v@, which give the offset from the start of
  --   the segment for each value of the parameter between @0@ and @1@.
  --   It is designed to be used infix, like @seg ``atParam`` 0.5@.
  instance (Additive v, Num n) => Parametric (Segment Closed v n) where
    atParam (Linear (OffsetClosed x)) t       = t *^ x
    atParam (Cubic c1 c2 (OffsetClosed x2)) t =     (3 * t'*t'*t ) *^ c1
                                                ^+^ (3 * t'*t *t ) *^ c2
                                                ^+^ (    t *t *t ) *^ x2
      where t' = 1-t
  
  instance Num n => DomainBounds (Segment Closed v n)
  
  instance (Additive v, Num n) => EndValues (Segment Closed v n) where
    atStart                            = const zero
    atEnd (Linear (OffsetClosed v))    = v
    atEnd (Cubic _ _ (OffsetClosed v)) = v
  
  -- | Compute the offset from the start of a segment to the
  --   end.  Note that in the case of a Bézier segment this is /not/ the
  --   same as the length of the curve itself; for that, see 'arcLength'.
  segOffset :: Segment Closed v n -> v n
  segOffset (Linear (OffsetClosed v))    = v
  segOffset (Cubic _ _ (OffsetClosed v)) = v
  
  -- | An open linear segment. This means the trail makes a straight line
  -- from the last segment the beginning to form a loop.
  openLinear :: Segment Open v n
  openLinear = Linear OffsetOpen
  
  -- | An open cubic segment. This means the trail makes a cubic bézier
  -- with control vectors @v1@ and @v2@ to form a loop.
  openCubic :: v n -> v n -> Segment Open v n
  openCubic v1 v2 = Cubic v1 v2 OffsetOpen
  
  ------------------------------------------------------------
  --  Computing segment envelope  ------------------------------
  ------------------------------------------------------------
  
  {- 3 (1-t)^2 t c1 + 3 (1-t) t^2 c2 + t^3 x2
  
     Can we compute the projection of B(t) onto a given vector v?
  
     u.v = |u||v| cos th
  
     |proj_v u| = cos th * |u|
                = (u.v/|v|)
  
     so B_v(t) = (B(t).v/|v|)
  
     Then take the derivative of this wrt. t, get a quadratic, solve.
  
     B_v(t) = (1/|v|) *     -- note this does not affect max/min, can solve for t first
              3 (1-t)^2 t (c1.v) + 3 (1-t) t^2 (c2.v) + t^3 (x2.v)
            = t^3 ((3c1 - 3c2 + x2).v) + t^2 ((-6c1 + 3c2).v) + t (3c1.v)
  
     B_v'(t) = t^2 (3(3c1 - 3c2 + x2).v) + t (6(-2c1 + c2).v) + 3c1.v
  
     Set equal to zero, use quadratic formula.
  -}
  
  -- | The envelope for a segment is based at the segment's start.
  instance (Metric v, OrderedField n) => Enveloped (Segment Closed v n) where
  
    getEnvelope (s@(Linear {})) = mkEnvelope $ \v ->
      maximum (map (\t -> (s `atParam` t) `dot` v) [0,1]) / quadrance v
  
    getEnvelope (s@(Cubic c1 c2 (OffsetClosed x2))) = mkEnvelope $ \v ->
      maximum .
      map (\t -> ((s `atParam` t) `dot` v) / quadrance v) $
      [0,1] ++
      filter (liftA2 (&&) (>0) (<1))
        (quadForm (3 * ((3 *^ c1 ^-^ 3 *^ c2 ^+^ x2) `dot` v))
                  (6 * (((-2) *^ c1 ^+^ c2) `dot` v))
                  ((3 *^ c1) `dot` v))
  
  ------------------------------------------------------------
  --  Manipulating segments
  ------------------------------------------------------------
  
  instance (Additive v, Fractional n) => Sectionable (Segment Closed v n) where
    splitAtParam (Linear (OffsetClosed x1)) t = (left, right)
      where left  = straight p
            right = straight (x1 ^-^ p)
            p = lerp t zero x1
    splitAtParam (Cubic c1 c2 (OffsetClosed x2)) t = (left, right)
      where left  = bezier3 a b e
            right = bezier3 (c ^-^ e) (d ^-^ e) (x2 ^-^ e)
            p = lerp t c1 c2
            a = lerp t zero c1
            b = lerp t a p
            d = lerp t c2 x2
            c = lerp t p d
            e = lerp t b c
  
    reverseDomain = reverseSegment
  
  -- | Reverse the direction of a segment.
  reverseSegment :: (Num n, Additive v) => Segment Closed v n -> Segment Closed v n
  reverseSegment (Linear (OffsetClosed v))       = straight (negated v)
  reverseSegment (Cubic c1 c2 (OffsetClosed x2)) = bezier3 (c2 ^-^ x2) (c1 ^-^ x2) (negated x2)
  
  -- Imitates I.elem for intervals<0.8 and I.member for intervals>=0.8
  member :: Ord a => a -> I.Interval a -> Bool
  member x (I.I a b) = x >= a && x <= b
  {-# INLINE member #-}
  
  instance (Metric v, OrderedField n)
        => HasArcLength (Segment Closed v n) where
  
    arcLengthBounded _ (Linear (OffsetClosed x1)) = I.singleton $ norm x1
    arcLengthBounded m s@(Cubic c1 c2 (OffsetClosed x2))
      | ub - lb < m = I lb ub
      | otherwise   = arcLengthBounded (m/2) l + arcLengthBounded (m/2) r
     where (l,r) = s `splitAtParam` 0.5
           ub    = sum (map norm [c1, c2 ^-^ c1, x2 ^-^ c2])
           lb    = norm x2
  
    arcLengthToParam m s _ | arcLength m s == 0 = 0.5
    arcLengthToParam m s@(Linear {}) len = len / arcLength m s
    arcLengthToParam m s@(Cubic {})  len
      | len `member` I (-m/2) (m/2) = 0
      | len < 0              = - arcLengthToParam m (fst (splitAtParam s (-1))) (-len)
      | len `member` slen    = 1
      | len > I.sup slen     = 2 * arcLengthToParam m (fst (splitAtParam s 2)) len
      | len < I.sup llen     = (*0.5) $ arcLengthToParam m l len
      | otherwise            = (+0.5) . (*0.5)
                             $ arcLengthToParam (9*m/10) r (len - I.midpoint llen)
      where (l,r) = s `splitAtParam` 0.5
            llen  = arcLengthBounded (m/10) l
            slen  = arcLengthBounded m s
  
    -- Note, the above seems to be quite slow since it duplicates a lot of
    -- work.  We could trade off some time for space by building a tree of
    -- parameter values (up to a certain depth...)
  
  ------------------------------------------------------------
  --  Fixed segments
  ------------------------------------------------------------
  
  -- | @FixedSegment@s are like 'Segment's except that they have
  --   absolute locations.  @FixedSegment v@ is isomorphic to @Located
  --   (Segment Closed v)@, as witnessed by 'mkFixedSeg' and
  --   'fromFixedSeg', but @FixedSegment@ is convenient when one needs
  --   the absolute locations of the vertices and control points.
  data FixedSegment v n = FLinear (Point v n) (Point v n)
                        | FCubic (Point v n) (Point v n) (Point v n) (Point v n)
    deriving (Eq, Ord, Show)
  
  type instance V (FixedSegment v n) = v
  type instance N (FixedSegment v n) = n
  
  instance Each (FixedSegment v n) (FixedSegment v' n') (Point v n) (Point v' n') where
    each f (FLinear p0 p1)      = FLinear <$> f p0 <*> f p1
    each f (FCubic p0 p1 p2 p3) = FCubic  <$> f p0 <*> f p1 <*> f p2 <*> f p3
    {-# INLINE each #-}
  
  -- | Reverses the control points.
  instance Reversing (FixedSegment v n) where
    reversing (FLinear p0 p1)      = FLinear p1 p0
    reversing (FCubic p0 p1 p2 p3) = FCubic p3 p2 p1 p0
  
  instance (Additive v, Num n) => Transformable (FixedSegment v n) where
    transform t = over each (papply t)
  
  instance (Additive v, Num n) => HasOrigin (FixedSegment v n) where
    moveOriginTo o = over each (moveOriginTo o)
  
  instance (Metric v, OrderedField n) => Enveloped (FixedSegment v n) where
    getEnvelope f = moveTo p (getEnvelope s)
      where (p, s) = viewLoc $ fromFixedSeg f
  
      -- Eventually we might decide it's cleaner/more efficient (?) to
      -- have all the computation in the FixedSegment instance of
      -- Envelope, and implement the Segment instance in terms of it,
      -- instead of the other way around
  
  instance (Metric v, OrderedField n)
        => HasArcLength (FixedSegment v n) where
    arcLengthBounded m s = arcLengthBounded m (fromFixedSeg s)
    arcLengthToParam m s = arcLengthToParam m (fromFixedSeg s)
  
  -- | Create a 'FixedSegment' from a located 'Segment'.
  mkFixedSeg :: (Num n, Additive v) => Located (Segment Closed v n) -> FixedSegment v n
  mkFixedSeg ls =
    case viewLoc ls of
      (p, Linear (OffsetClosed v))       -> FLinear p (p .+^ v)
      (p, Cubic c1 c2 (OffsetClosed x2)) -> FCubic  p (p .+^ c1) (p .+^ c2) (p .+^ x2)
  
  -- | Convert a 'FixedSegment' back into a located 'Segment'.
  fromFixedSeg :: (Num n, Additive v) => FixedSegment v n -> Located (Segment Closed v n)
  fromFixedSeg (FLinear p1 p2)      = straight (p2 .-. p1) `at` p1
  fromFixedSeg (FCubic x1 c1 c2 x2) = bezier3 (c1 .-. x1) (c2 .-. x1) (x2 .-. x1) `at` x1
  
  -- | Use a 'FixedSegment' to make an 'Iso' between an
  -- a fixed segment and a located segment.
  fixedSegIso :: (Num n, Additive v) => Iso' (FixedSegment v n) (Located (Segment Closed v n))
  fixedSegIso = iso fromFixedSeg mkFixedSeg
  
  type instance Codomain (FixedSegment v n) = Point v
  
  instance (Additive v, Num n) => Parametric (FixedSegment v n) where
    atParam (FLinear p1 p2) t = lerp t p1 p2
    atParam (FCubic x1 c1 c2 x2) t = p3
      where p11 = lerp t x1 c1
            p12 = lerp t c1 c2
            p13 = lerp t c2 x2
  
            p21 = lerp t p11 p12
            p22 = lerp t p12 p13
  
            p3  = lerp t p21 p22
  
  instance Num n => DomainBounds (FixedSegment v n)
  
  instance (Additive v, Num n) => EndValues (FixedSegment v n) where
    atStart (FLinear p0 _)     = p0
    atStart (FCubic  p0 _ _ _) = p0
    atEnd   (FLinear _ p1)     = p1
    atEnd   (FCubic _ _ _ p1 ) = p1
  
  instance (Additive v, Fractional n) => Sectionable (FixedSegment v n) where
    splitAtParam (FLinear p0 p1) t = (left, right)
      where left  = FLinear p0 p
            right = FLinear p  p1
            p = lerp t p0 p1
    splitAtParam (FCubic p0 c1 c2 p1) t = (left, right)
      where left  = FCubic p0 a b cut
            right = FCubic cut c d p1
            -- first round
            a   = lerp t p0 c1
            p   = lerp t c1 c2
            d   = lerp t c2 p1
            -- second round
            b   = lerp t a p
            c   = lerp t p d
            -- final round
            cut = lerp t b c
  
    reverseDomain (FLinear p0 p1) = FLinear p1 p0
    reverseDomain (FCubic p0 c1 c2 p1) = FCubic p1 c2 c1 p0
  
  ------------------------------------------------------------
  --  Segment measures  --------------------------------------
  ------------------------------------------------------------
  
  -- $segmeas
  -- Trails store a sequence of segments in a fingertree, which can
  -- automatically track various monoidal \"measures\" on segments.
  
  -- | A type to track the count of segments in a 'Trail'.
  newtype SegCount = SegCount (Sum Int)
    deriving (Semigroup, Monoid)
  
  instance Wrapped SegCount where
    type Unwrapped SegCount = Sum Int
    _Wrapped' = iso (\(SegCount x) -> x) SegCount
  
  instance Rewrapped SegCount SegCount
  
  -- | A type to represent the total arc length of a chain of
  --   segments. The first component is a \"standard\" arc length,
  --   computed to within a tolerance of @10e-6@.  The second component is
  --   a generic arc length function taking the tolerance as an
  --   argument.
  
  newtype ArcLength n
    = ArcLength (Sum (Interval n), n -> Sum (Interval n))
  
  instance Wrapped (ArcLength n) where
    type Unwrapped (ArcLength n) = (Sum (Interval n), n -> Sum (Interval n))
    _Wrapped' = iso (\(ArcLength x) -> x) ArcLength
  
  instance Rewrapped (ArcLength n) (ArcLength n')
  
  -- | Project out the cached arc length, stored together with error
  --   bounds.
  getArcLengthCached :: ArcLength n -> Interval n
  getArcLengthCached = getSum . fst . op ArcLength
  
  -- | Project out the generic arc length function taking the tolerance as
  --   an argument.
  getArcLengthFun :: ArcLength n -> n -> Interval n
  getArcLengthFun = fmap getSum . snd . op ArcLength
  
  -- | Given a specified tolerance, project out the cached arc length if
  --   it is accurate enough; otherwise call the generic arc length
  --   function with the given tolerance.
  getArcLengthBounded :: (Num n, Ord n)
                      => n -> ArcLength n -> Interval n
  getArcLengthBounded eps al
    | I.width cached <= eps = cached
    | otherwise             = getArcLengthFun al eps
    where
      cached = getArcLengthCached al
  deriving instance (Num n, Ord n) => Semigroup (ArcLength n)
  deriving instance (Num n, Ord n) => Monoid    (ArcLength n)
  
  -- | A type to represent the total cumulative offset of a chain of
  --   segments.
  newtype TotalOffset v n = TotalOffset (v n)
  
  instance Wrapped (TotalOffset v n) where
    type Unwrapped (TotalOffset v n) = v n
    _Wrapped' = iso (\(TotalOffset x) -> x) TotalOffset
  
  instance Rewrapped (TotalOffset v n) (TotalOffset v' n')
  
  instance (Num n, Additive v) => Semigroup (TotalOffset v n) where
    TotalOffset v1 <> TotalOffset v2 = TotalOffset (v1 ^+^ v2)
  
  instance (Num n, Additive v) => Monoid (TotalOffset v n) where
    mempty  = TotalOffset zero
    mappend = (<>)
  
  -- | A type to represent the offset and envelope of a chain of
  --   segments.  They have to be paired into one data structure, since
  --   combining the envelopes of two consecutive chains needs to take
  --   the offset of the first into account.
  data OffsetEnvelope v n = OffsetEnvelope
    { _oeOffset   :: !(TotalOffset v n)
    , _oeEnvelope :: Envelope v n
    }
  
  makeLenses ''OffsetEnvelope
  
  instance (Metric v, OrderedField n) => Semigroup (OffsetEnvelope v n) where
    (OffsetEnvelope o1 e1) <> (OffsetEnvelope o2 e2)
      = let !negOff = negated . op TotalOffset $ o1
            e2Off = moveOriginBy negOff e2
            !_unused = maybe () (\f -> f `seq` ()) $ appEnvelope e2Off
        in OffsetEnvelope
            (o1 <> o2)
            (e1 <> e2Off)
  
  -- | @SegMeasure@ collects up all the measurements over a chain of
  --   segments.
  type SegMeasure v n = SegCount
                    ::: ArcLength n
                    ::: OffsetEnvelope v n
                    ::: ()
    -- unfortunately we can't cache Trace, since there is not a generic
    -- instance Traced (Segment Closed v), only Traced (Segment Closed R2).
  
  instance (Metric v, OrderedField n)
      => Measured (SegMeasure v n) (SegMeasure v n) where
    measure = id
  
  instance (OrderedField n, Metric v)
      => Measured (SegMeasure v n) (Segment Closed v n) where
    measure s = (SegCount . Sum) 1
  
              -- cache arc length with two orders of magnitude more
              -- accuracy than standard, so we have a hope of coming out
              -- with an accurate enough total arc length for
              -- reasonable-length trails
              *: ArcLength ( Sum $ arcLengthBounded (stdTolerance/100) s
                           , Sum . flip arcLengthBounded s               )
  
              *: OffsetEnvelope (TotalOffset . segOffset $ s)
                                (getEnvelope s)
  
              *: ()
  
  ------------------------------------------------------------
  --  Serialize instances
  ------------------------------------------------------------
  
  instance (Serialize (v n)) => Serialize (Segment Open v n) where
    {-# INLINE put #-}
    put segment = case segment of
      Linear OffsetOpen    -> Serialize.put True
      Cubic v w OffsetOpen -> do
        Serialize.put False
        Serialize.put v
        Serialize.put w
  
    {-# INLINE get #-}
    get = do
      isLinear <- Serialize.get
      case isLinear of
        True  -> return (Linear OffsetOpen)
        False -> do
          v <- Serialize.get
          w <- Serialize.get
          return (Cubic v w OffsetOpen)
  
  instance (Serialize (v n)) => Serialize (Segment Closed v n) where
    {-# INLINE put #-}
    put segment = case segment of
      Linear (OffsetClosed z)    -> do
        Serialize.put z
        Serialize.put True
      Cubic v w (OffsetClosed z) -> do
        Serialize.put z
        Serialize.put False
        Serialize.put v
        Serialize.put w
  
    {-# INLINE get #-}
    get = do
      z <- Serialize.get
      isLinear <- Serialize.get
      case isLinear of
        True  -> return (Linear (OffsetClosed z))
        False -> do
          v <- Serialize.get
          w <- Serialize.get
          return (Cubic v w (OffsetClosed z))
  