content: {-# LANGUAGE TypeOperators, FlexibleContexts, DataKinds, TypeFamilies, CPP,
               ScopedTypeVariables, ConstraintKinds, GeneralizedNewtypeDeriving #-}
  
  #if __GLASGOW_HASKELL__ >= 711
  {-# OPTIONS_GHC -Wno-redundant-constraints #-}
  #endif
  
  -----------------------------------------------------------------------------
  -- |
  -- Module      :  Data.Metrology.Vector
  -- Copyright   :  (C) 2014 Richard Eisenberg
  -- License     :  BSD-style (see LICENSE)
  -- Maintainer  :  Richard Eisenberg (rae@cs.brynmawr.edu)
  -- Stability   :  experimental
  -- Portability :  non-portable
  --
  -- Exports combinators for building quantities out of vectors, from the
  -- vector-space library.
  ------------------------------------------------------------------------------
  
  module Data.Metrology.Vector (
    -- * Term-level combinators
  
    -- | The term-level arithmetic operators are defined by
    -- applying vertical bar(s) to the sides the dimensioned
    -- quantities acts on.
  
    -- ** Additive operations
    zero, (|+|), (|-|), qSum, qNegate,
  
    -- ** Multiplicative operations between non-vector quantities
    (|*|), (|/|), (/|),
  
    -- ** Multiplicative operations between a vector and a scalar
    (*|), (|*), (|/),
  
    -- ** Multiplicative operations on vectors
    (|*^|), (|^*|), (|^/|), (|.|),
  
    -- ** Exponentiation
    (|^), (|^^), qNthRoot,
    qSq, qCube, qSqrt, qCubeRoot,
  
    -- ** Other vector operations
    qMagnitudeSq, qMagnitude, qNormalized, qProject, qCross2, qCross3,
  
    -- ** Affine operations
    Point(..), QPoint, (|.-.|), (|.+^|), (|.-^|), qDistanceSq, qDistance,
    pointNumIn, (.#), quOfPoint, (%.),
  
    -- ** Comparison
    qCompare, (|<|), (|>|), (|<=|), (|>=|), (|==|), (|/=|),
    qApprox, qNapprox,
  
    -- * Nondimensional units, conversion between quantities and numeric values
    numIn, (#), quOf, (%), showIn,
    unity, redim, convert,
    defaultLCSU, constant,
  
    -- * Type-level unit combinators
    (:*)(..), (:/)(..), (:^)(..), (:@)(..),
    UnitPrefix(..),
  
    -- * Type-level quantity combinators
    type (%*), type (%/), type (%^),
  
    -- * Creating quantity types
    Qu, MkQu_D, MkQu_DLN, MkQu_U, MkQu_ULN,
  
    -- * Creating new dimensions
    Dimension,
  
    -- * Creating new units
    Unit(type BaseUnit, type DimOfUnit, conversionRatio),
    Canonical,
  
    -- * Numbers, the only built-in unit
    Dimensionless(..), Number(..), Count, quantity,
  
    -- * LCSUs (locally coherent system of units)
    MkLCSU, LCSU(DefaultLCSU), DefaultUnitOfDim,
  
    -- * Validity checks and assertions
    CompatibleUnit, CompatibleDim, ConvertibleLCSUs_D,
    DefaultConvertibleLCSU_D, DefaultConvertibleLCSU_U,
    MultDimFactors, MultUnitFactors, UnitOfDimFactors,
  
    -- * Type-level integers
    Z(..), Succ, Pred, type (#+), type (#-), type (#*), type (#/), Negate,
  
    -- ** Synonyms for small numbers
    One, Two, Three, Four, Five, MOne, MTwo, MThree, MFour, MFive,
  
    -- ** Term-level singletons
    sZero, sOne, sTwo, sThree, sFour, sFive,
    sMOne, sMTwo, sMThree, sMFour, sMFive,
    sSucc, sPred, sNegate,
  
    -- * Internal definitions
    -- | The following module is re-exported solely to prevent noise in error messages;
    -- we do not recommend trying to use these definitions in user code.
    module Data.Metrology.Internal
  
    ) where
  
  import Data.Metrology.Qu
  import Data.Metrology.LCSU
  import Data.Metrology.Validity
  import Data.Metrology.Factor
  import Data.Metrology.Z as Z
  import Data.Metrology.Units
  import Data.Metrology.Combinators
  import Data.Metrology.Dimensions
  import Data.Metrology.Internal
  
  import Data.AffineSpace
  import Data.VectorSpace
  import Data.Cross hiding ( One, Two, Three )
  
  import Data.Proxy
  import Data.Coerce
  import Data.Foldable as F
  
  ---------------------------------------
  -- Additive operations
  ---------------------------------------
  
  -- | The number 0, polymorphic in its dimension. Use of this will
  -- often require a type annotation.
  zero :: AdditiveGroup n => Qu dimspec l n
  zero = Qu zeroV
  
  infixl 6 |+|
  -- | Add two compatible quantities
  (|+|) :: (d1 @~ d2, AdditiveGroup n) => Qu d1 l n -> Qu d2 l n -> Qu d1 l n
  (Qu a) |+| (Qu b) = Qu (a ^+^ b)
  
  -- | Negate a quantity
  qNegate :: AdditiveGroup n => Qu d l n -> Qu d l n
  qNegate (Qu x) = Qu (negateV x)
  
  infixl 6 |-|
  -- | Subtract two compatible quantities
  (|-|) :: (d1 @~ d2, AdditiveGroup n) => Qu d1 l n -> Qu d2 l n -> Qu d1 l n
  a |-| b = a |+| qNegate b
  
  -- | Take the sum of a list of quantities
  qSum :: (Foldable f, AdditiveGroup n) => f (Qu d l n) -> Qu d l n
  qSum = F.foldr (|+|) zero
  
  ---------------------------------------
  -- Vector multiplicative operations
  ---------------------------------------
  
  infixl 7 |*^|, |^*|, |^/|
  -- | Multiply a scalar quantity by a vector quantity
  (|*^|) :: VectorSpace n => Qu d1 l (Scalar n) -> Qu d2 l n -> Qu (Normalize (d1 @+ d2)) l n
  (Qu a) |*^| (Qu b) = Qu (a *^ b)
  
  -- | Multiply a vector quantity by a scalar quantity
  (|^*|) :: VectorSpace n => Qu d1 l n -> Qu d2 l (Scalar n) -> Qu (Normalize (d1 @+ d2)) l n
  (Qu a) |^*| (Qu b) = Qu (a ^* b)
  
  -- | Divide a vector quantity by a scalar quantity
  (|^/|) :: (VectorSpace n, Fractional (Scalar n))
         => Qu d1 l n -> Qu d2 l (Scalar n) -> Qu (Normalize (d1 @- d2)) l n
  (Qu a) |^/| (Qu b) = Qu (a ^/ b)
  
  infixl 7 |/
  -- | Divide a quantity by a scalar
  (|/) :: (VectorSpace n, Fractional (Scalar n)) => Qu a l n -> Scalar n -> Qu a l n
  (Qu a) |/ b = Qu (a ^/ b)
  -- The above function should *not* need to be privileged. But, GHC can't figure
  -- out that a @@- '[] ~ a. Urgh.
  
  infixl 7 *| , |*
  -- | Multiply a quantity by a scalar from the left
  (*|) :: VectorSpace n => Scalar n -> Qu b l n -> Qu b l n
  a *| (Qu b) =  Qu (a *^ b)
  
  -- | Multiply a quantity by a scalar from the right
  (|*) :: VectorSpace n => Qu a l n -> Scalar n -> Qu a l n
  (Qu a) |* b = Qu (a ^* b)
  
  ---------------------------------------
  -- Multiplicative operations
  ---------------------------------------
  
  infixl 7 |.|
  -- | Take a inner (dot) product between two quantities.
  (|.|) :: InnerSpace n => Qu d1 l n -> Qu d2 l n -> Qu (Normalize (d1 @+ d2)) l (Scalar n)
  (Qu a) |.| (Qu b) = Qu (a <.> b)
  
  -- | Square the length of a vector.
  qMagnitudeSq :: InnerSpace n => Qu d l n -> Qu (d @* Z.Two) l (Scalar n)
  qMagnitudeSq (Qu x) = Qu (magnitudeSq x)
  
  -- | Length of a vector.
  qMagnitude :: (InnerSpace n, Floating (Scalar n)) => Qu d l n -> Qu d l (Scalar n)
  qMagnitude (Qu x) = Qu (magnitude x)
  
  -- | Vector in same direction as given one but with length of one. If given the zero
  -- vector, then return it. The returned vector is dimensionless.
  qNormalized :: (InnerSpace n, Floating (Scalar n)) => Qu d l n -> Qu '[] l n
  qNormalized (Qu x) = Qu (normalized x)
  
  -- | @qProject u v@ computes the projection of @v@ onto @u@.
  qProject :: (InnerSpace n, Floating (Scalar n)) => Qu d2 l n -> Qu d1 l n -> Qu d1 l n
  qProject (Qu u) (Qu v) = Qu (u `project` v)
  
  -- | Cross product of 2D vectors.
  qCross2 :: HasCross2 n => Qu d l n -> Qu d l n
  qCross2 (Qu x) = Qu (cross2 x)
  
  -- | Cross product of 3D vectors.
  qCross3 :: HasCross3 n => Qu d1 l n -> Qu d2 l n -> Qu (Normalize (d1 @+ d2)) l n
  qCross3 (Qu x) (Qu y) = Qu (x `cross3` y)
  
  ---------------------------------------
  -- Affine space operations
  ---------------------------------------
  
  -- | A @Point n@ is an affine space built over @n@. Two @Point@s cannot be added,
  -- but they can be subtracted to yield a difference of type @n@.
  newtype Point n = Point n
    deriving (Show, Eq, Enum, Bounded)
  
  -- | Make a point quantity from a non-point quantity.
  type family QPoint n where
    QPoint (Qu d l n) = Qu d l (Point n)
  
  instance AdditiveGroup n => AffineSpace (Point n) where
    type Diff (Point n) = n
    (.-.) = coerce ((^-^) :: n -> n -> n)
    (.+^) = coerce ((^+^) :: n -> n -> n)
  
  -- | Make a point quantity at the given unit. Like 'quOf'.
  quOfPoint :: forall dim lcsu unit n.
               ( ValidDLU dim lcsu unit
               , VectorSpace n
               , Fractional (Scalar n) )
            => n -> unit -> Qu dim lcsu (Point n)
  quOfPoint n unit = Qu (Point x)
    where Qu x = quOf n unit :: Qu dim lcsu n
  
  infix 5 %.
  -- | Infix synonym of 'quOfPoint'
  (%.) :: ( ValidDLU dim lcsu unit
          , VectorSpace n
          , Fractional (Scalar n) )
       => n -> unit -> Qu dim lcsu (Point n)
  (%.) = quOfPoint
  
  -- | Extract the numerical value from a point quantity. Like 'numIn'.
  pointNumIn :: forall unit dim lcsu n.
                ( ValidDLU dim lcsu unit
                , VectorSpace n
                , Fractional (Scalar n) )
             => Qu dim lcsu (Point n) -> unit -> n
  pointNumIn (Qu (Point n)) unit = numIn (Qu n :: Qu dim lcsu n) unit
  
  infix 5 .#
  -- | Infix synonym for 'pointNumIn'.
  (.#) :: (ValidDLU dim lcsu unit, VectorSpace n, Fractional (Scalar n))
       => Qu dim lcsu (Point n) -> unit -> n
  (.#) = pointNumIn
  
  infixl 6 |.-.|, |.+^|, |.-^|
  
  -- | Subtract point quantities.
  (|.-.|) :: (d1 @~ d2, AffineSpace n) => Qu d1 l n -> Qu d2 l n -> Qu d1 l (Diff n)
  (Qu a) |.-.| (Qu b) = Qu (a .-. b)
  
  -- | Add a point to a vector.
  (|.+^|) :: (d1 @~ d2, AffineSpace n) => Qu d1 l n -> Qu d2 l (Diff n) -> Qu d1 l n
  (Qu a) |.+^| (Qu b) = Qu (a .+^ b)
  
  -- | Subract a vector from a point.
  (|.-^|) :: (d1 @~ d2, AffineSpace n) => Qu d1 l n -> Qu d2 l (Diff n) -> Qu d1 l n
  (Qu a) |.-^| (Qu b) = Qu (a .-^ b)
  
  -- | Square of the distance between two points.
  qDistanceSq :: (d1 @~ d2, AffineSpace n, InnerSpace (Diff n))
              => Qu d1 l n -> Qu d2 l n -> Qu (d1 @* Z.Two) l (Scalar (Diff n))
  qDistanceSq (Qu a) (Qu b) = Qu (a `distanceSq` b)
  
  -- | Distance between two points.
  qDistance :: (d1 @~ d2, AffineSpace n, InnerSpace (Diff n), Floating (Scalar (Diff n)))
            => Qu d1 l n -> Qu d2 l n -> Qu d1 l (Scalar (Diff n))
  qDistance (Qu a) (Qu b) = Qu (a `distance` b)
  
  ---------------------------------------
  -- Top-level operations
  ---------------------------------------
  
  -- | Extracts a numerical value from a dimensioned quantity, expressed in
  --   the given unit. For example:
  --
  --   > inMeters :: Length -> Double
  --   > inMeters x = numIn x Meter
  --
  --   or
  --
  --   > inMeters x = x # Meter
  numIn :: forall unit dim lcsu n.
           ( ValidDLU dim lcsu unit
           , VectorSpace n
           , Fractional (Scalar n) )
        => Qu dim lcsu n -> unit -> n
  numIn (Qu val) u
    = val ^* fromRational
               (canonicalConvRatioSpec (Proxy :: Proxy (LookupList dim lcsu))
                / canonicalConvRatio u)
  
  infix 5 #
  -- | Infix synonym for 'numIn'
  (#) :: ( ValidDLU dim lcsu unit
         , VectorSpace n
         , Fractional (Scalar n) )
      => Qu dim lcsu n -> unit -> n
  (#) = numIn
  
  -- | Creates a dimensioned quantity in the given unit. For example:
  --
  --   > height :: Length
  --   > height = quOf 2.0 Meter
  --
  --   or
  --
  --   > height = 2.0 % Meter
  quOf :: forall unit dim lcsu n.
           ( ValidDLU dim lcsu unit
           , VectorSpace n
           , Fractional (Scalar n) )
        => n -> unit -> Qu dim lcsu n
  quOf d u
    = Qu (d ^* fromRational
                 (canonicalConvRatio u
                  / canonicalConvRatioSpec (Proxy :: Proxy (LookupList dim lcsu))))
  
  infix 5 %
  -- | Infix synonym for 'quOf'
  (%) :: ( ValidDLU dim lcsu unit
         , VectorSpace n
         , Fractional (Scalar n) )
      => n -> unit -> Qu dim lcsu n
  (%) = quOf
  
  -- | Dimension-keeping cast between different CSUs.
  convert :: forall d l1 l2 n.
    ( ConvertibleLCSUs d l1 l2
    , VectorSpace n
    , Fractional (Scalar n) )
    => Qu d l1 n -> Qu d l2 n
  convert (Qu x) = Qu $ x ^* fromRational (
    canonicalConvRatioSpec (Proxy :: Proxy (LookupList d l1))
    / canonicalConvRatioSpec (Proxy :: Proxy (LookupList d l2)))
  
  
  -- | Compute the argument in the @DefaultLCSU@, and present the result as
  -- lcsu-polymorphic dimension-polymorphic value. Named 'constant' because one
  -- of its dominant usecase is to inject constant quantities into
  -- dimension-polymorphic expressions.
  constant :: ( d @~ e
              , ConvertibleLCSUs e DefaultLCSU l
              , VectorSpace n
              , Fractional (Scalar n) )
           => Qu d DefaultLCSU n -> Qu e l n
  constant = convert . redim
  
  infix 1 `showIn`
  -- | Show a dimensioned quantity in a given unit. (The default @Show@
  -- instance always uses units as specified in the LCSU.)
  showIn :: ( ValidDLU dim lcsu unit
            , VectorSpace n
            , Fractional (Scalar n)
            , Show unit
            , Show n )
         => Qu dim lcsu n -> unit -> String
  showIn x u = show (x # u) ++ " " ++ show u
  