    {-# LANGUAGE FlexibleContexts     #-}
    {-# LANGUAGE TypeFamilies         #-}
    {-# LANGUAGE UndecidableInstances #-}

    -----------------------------------------------------------------------------
    -- |
    -- Module      :  Diagrams.Align
    -- Copyright   :  (c) 2011-2013 diagrams-lib team (see LICENSE)
    -- License     :  BSD-style (see LICENSE)
    -- Maintainer  :  diagrams-discuss@googlegroups.com
    --
    -- The /alignment/ of an object refers to the position of its local
    -- origin with respect to its envelope.  This module defines the
    -- 'Alignable' class for things which can be aligned, as well as a
    -- default implementation in terms of 'HasOrigin' and 'Enveloped',
    -- along with several utility methods for alignment.
    --
    -----------------------------------------------------------------------------

    module Diagrams.Align
           ( -- * Alignable class

             Alignable(..)
           , alignByDefault

             -- * General alignment functions

           , align
           , center

           ) where

    import           Diagrams.Core

    import           Data.AffineSpace (alerp)
    import           Data.VectorSpace

    import qualified Data.Map         as M
    import qualified Data.Set         as S

    -- | Class of things which can be aligned.
    class Alignable a where

      -- | @alignBy v d a@ moves the origin of @a@ along the vector
      --   @v@. If @d = 1@, the origin is moved to the edge of the
      --   envelope in the direction of @v@; if @d = -1@, it moves to the
      --   edge of the envelope in the direction of the negation of @v@.
      --   Other values of @d@ interpolate linearly (so for example, @d =
      --   0@ centers the origin along the direction of @v@).
      alignBy :: V a -> Scalar (V a) -> a -> a

    -- | Default implementation of 'alignBy' for types with 'HasOrigin'
    --   and 'Enveloped' instances.
    alignByDefault :: (HasOrigin a, Enveloped a, Num (Scalar (V a)))
                   => V a -> Scalar (V a) -> a -> a
    alignByDefault v d a = moveOriginTo (alerp (envelopeP (negateV v) a)
                                        (envelopeP v a)
                                        ((d + 1) / 2))
                                 a

    instance (InnerSpace v, OrderedField (Scalar v)) => Alignable (Envelope v) where
      alignBy = alignByDefault

    instance (Enveloped b, HasOrigin b) => Alignable [b] where
      alignBy = alignByDefault

    instance (Enveloped b, HasOrigin b, Ord b) => Alignable (S.Set b) where
      alignBy = alignByDefault

    instance (Enveloped b, HasOrigin b) => Alignable (M.Map k b) where
      alignBy = alignByDefault

    instance ( HasLinearMap v, InnerSpace v, OrderedField (Scalar v)
             , Monoid' m
             ) => Alignable (QDiagram b v m) where
      alignBy = alignByDefault

    -- | @align v@ aligns an enveloped object along the edge in the
    --   direction of @v@.  That is, it moves the local origin in the
    --   direction of @v@ until it is on the edge of the envelope.  (Note
    --   that if the local origin is outside the envelope to begin with,
    --   it may have to move \"backwards\".)
    align :: (Alignable a, Num (Scalar (V a))) => V a -> a -> a
    align v = alignBy v 1

    -- | @center v@ centers an enveloped object along the direction of
    --   @v@.
    center :: (Alignable a, Num (Scalar (V a))) => V a -> a -> a
    center v = alignBy v 0