{- Data/Dimensions.hs

   The units Package
   Copyright (c) 2013 Richard Eisenberg
   eir@cis.upenn.edu

   This file defines the Dim type and operations on that type.
-}

{-# LANGUAGE TypeFamilies, TypeOperators, DataKinds, UndecidableInstances,
             ConstraintKinds, StandaloneDeriving, GeneralizedNewtypeDeriving,
             FlexibleInstances #-}

module Data.Dimensions.Dim where

import Data.Singletons ( Sing )
import Data.Dimensions.DimSpec
import Data.Dimensions.Z

-------------------------------------------------------------
--- Internal ------------------------------------------------
-------------------------------------------------------------

-- | Dim adds a dimensional annotation to its base type @n@. This is the
-- representation for all dimensioned quantities.
newtype Dim (n :: *) (a :: [DimSpec *]) = Dim n

-------------------------------------------------------------
--- User-facing ---------------------------------------------
-------------------------------------------------------------

infixl 6 .+
-- | Add two compatible dimensioned quantities
(.+) :: (d1 @~ d2, Num n) => Dim n d1 -> Dim n d2 -> Dim n d1
(Dim a) .+ (Dim b) = Dim (a + b)

infixl 6 .-
-- | Subtract two compatible dimensioned quantities
(.-) :: (d1 @~ d2, Num n) => Dim n d1 -> Dim n d2 -> Dim n d1
(Dim a) .- (Dim b) = Dim (a - b)

infixl 7 .*
-- | Multiply two dimensioned quantities
(.*) :: Num n => Dim n a -> Dim n b -> Dim n (Normalize (a @+ b))
(Dim a) .* (Dim b) = Dim (a * b)

infixl 7 ./
-- | Divide two dimensioned quantities
(./) :: Fractional n => Dim n a -> Dim n b -> Dim n (Normalize (a @- b))
(Dim a) ./ (Dim b) = Dim (a / b)

infixr 8 .^
-- | Raise a dimensioned quantity to a power known at compile time
(.^) :: Fractional n => Dim n a -> Sing z -> Dim n (a @* z)
(Dim a) .^ sz = Dim (a ^^ szToInt sz)

-- | Take the n'th root of a dimensioned quantity, where n is known at compile
-- time
nthRoot :: ((Zero < z) ~ True, Floating n) => Sing z -> Dim n a -> Dim n (a @/ z)
nthRoot sz (Dim a) = Dim (a ** (1.0 / (fromIntegral $ szToInt sz)))

infix 4 .<
-- | Check if one dimensioned quantity is less than a compatible one
(.<) :: (d1 @~ d2, Ord n) => Dim n d1 -> Dim n d2 -> Bool
(Dim a) .< (Dim b) = a < b

infix 4 .>
-- | Check if one dimensioned quantity is greater than a compatible one
(.>) :: (d1 @~ d2, Ord n) => Dim n d1 -> Dim n d2 -> Bool
(Dim a) .> (Dim b) = a > b

infix 4 .<=
-- | Check if one dimensioned quantity is less than or equal to a compatible one
(.<=) :: (d1 @~ d2, Ord n) => Dim n d1 -> Dim n d2 -> Bool
(Dim a) .<= (Dim b) = a <= b

infix 4 .>=
-- | Check if one dimensioned quantity is greater than or equal to a compatible one
(.>=) :: (d1 @~ d2, Ord n) => Dim n d1 -> Dim n d2 -> Bool
(Dim a) .>= (Dim b) = a >= b

-- | Compare two compatible dimensioned quantities for equality
dimEq :: (d0 @~ d1, d0 @~ d2, Num n, Ord n)
      => Dim n d0  -- ^ If the difference between the next
                   -- two arguments are less  than this 
                   -- amount, they are considered equal
      -> Dim n d1 -> Dim n d2 -> Bool
dimEq (Dim epsilon) (Dim a) (Dim b) = abs(a-b) < epsilon

-- | Compare two compatible dimensioned quantities for inequality
dimNeq :: (d0 @~ d1, d0 @~ d2, Num n, Ord n)
       => Dim n d0 -- ^ If the difference between the next
                   -- two arguments are less  than this 
                   -- amount, they are considered equal
       -> Dim n d1 -> Dim n d2 -> Bool
dimNeq (Dim epsilon) (Dim a) (Dim b) = abs(a-b) >= epsilon

-- | Square a dimensioned quantity
dimSqr :: Num n => Dim n a -> Dim n (Normalize (a @+ a))
dimSqr x = x .* x

-- | Take the square root of a dimensioned quantity
dimSqrt :: Floating n => Dim n a -> Dim n (a @/ Two)
dimSqrt = nthRoot pTwo

-- | Take the cube root of a dimensioned quantity
dimCubeRoot :: Floating n => Dim n a -> Dim n (a @/ Three)
dimCubeRoot = nthRoot pThree

infixl 7 *.
-- | Multiply a dimensioned quantity by a scalar
(*.) :: Num n => n -> Dim n a -> Dim n a
a *. (Dim b) = Dim (a * b)

-------------------------------------------------------------
--- Instances -----------------------------------------------
-------------------------------------------------------------

deriving instance Eq n => Eq (Dim n '[])
deriving instance Ord n => Ord (Dim n '[])
deriving instance Num n => Num (Dim n '[])
deriving instance Real n => Real (Dim n '[])
deriving instance Fractional n => Fractional (Dim n '[])
deriving instance Floating n => Floating (Dim n '[])
deriving instance RealFrac n => RealFrac (Dim n '[])
deriving instance RealFloat n => RealFloat (Dim n '[])

-------------------------------------------------------------
--- Combinators ---------------------------------------------
-------------------------------------------------------------

infixl 7 %*
-- | Multiply two dimension types to produce a new one. For example:
--
-- > type Velocity = Length %/ Time
type family (d1 :: *) %* (d2 :: *) :: *
type instance (Dim n d1) %* (Dim n d2) = Dim n (d1 @+ d2)

infixl 7 %/
-- | Divide two dimension types to produce a new one
type family (d1 :: *) %/ (d2 :: *) :: *
type instance (Dim n d1) %/ (Dim n d2) = Dim n (d1 @- d2)

infixr 8 %^
-- | Exponentiate a dimension type to an integer
type family (d :: *) %^ (z :: Z) :: *
type instance (Dim n d) %^ z = Dim n (d @* z)