-- | Language for defining and manipulating units.
module Language.Drasil.UnitLang (
    -- * Types
    USymb(US), UDefn(..), UnitSymbol(BaseSI, DerivedSI, Defined)
    -- * Functions
  , fromUDefn, compUSymb, getUSymb, getDefn
  ) where

import Language.Drasil.Symbol (Symbol, compsy)
import Data.Foldable (foldl')

-- UName for the base cases, otherwise build up.
-- Probably a 7-vector would be better (less error-prone!)
-- | Language of units (how to build them up into a unit symbol).
-- Of the form ('Symbol' ^ 'Integer'). The 'Integer' may be negative, but should not be zero.
newtype USymb = US [(Symbol, Integer)] -- can be negative, should not be 0
  deriving (USymb -> USymb -> Bool
(USymb -> USymb -> Bool) -> (USymb -> USymb -> Bool) -> Eq USymb
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: USymb -> USymb -> Bool
== :: USymb -> USymb -> Bool
$c/= :: USymb -> USymb -> Bool
/= :: USymb -> USymb -> Bool
Eq)

-- | Language of unit equations, to define a unit relative
-- to another.
data UDefn = USynonym USymb      -- ^ to define straight synonyms.
           | UScale Double USymb -- ^ scale, i.e. *.
           | UShift Double USymb -- ^ shift, i.e. +.

-- | Generates a default unit symbol.
fromUDefn :: UDefn -> USymb
fromUDefn :: UDefn -> USymb
fromUDefn (USynonym USymb
x) = USymb
x
fromUDefn (UScale Double
_ USymb
s) = USymb
s
fromUDefn (UShift Double
_ USymb
s) = USymb
s

-- | Hand-rolled version of compare. Should assume 'USymb' is normalized, so
-- that some redundant EQ cases can be removed.
compUSymb :: USymb -> USymb -> Ordering
compUSymb :: USymb -> USymb -> Ordering
compUSymb (US [(Symbol, Integer)]
l)  (US [(Symbol, Integer)]
m)  = (Ordering -> Ordering -> Ordering)
-> Ordering -> [Ordering] -> Ordering
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' Ordering -> Ordering -> Ordering
forall a. Monoid a => a -> a -> a
mappend Ordering
EQ ([Ordering] -> Ordering) -> [Ordering] -> Ordering
forall a b. (a -> b) -> a -> b
$ ((Symbol, Integer) -> (Symbol, Integer) -> Ordering)
-> [(Symbol, Integer)] -> [(Symbol, Integer)] -> [Ordering]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (Symbol, Integer) -> (Symbol, Integer) -> Ordering
forall {a}. Ord a => (Symbol, a) -> (Symbol, a) -> Ordering
comp [(Symbol, Integer)]
l [(Symbol, Integer)]
m
  where
    comp :: (Symbol, a) -> (Symbol, a) -> Ordering
comp (Symbol
s1, a
i1) (Symbol
s2, a
i2) = Symbol -> Symbol -> Ordering
compsy Symbol
s1 Symbol
s2 Ordering -> Ordering -> Ordering
forall a. Monoid a => a -> a -> a
`mappend` a -> a -> Ordering
forall a. Ord a => a -> a -> Ordering
compare a
i1 a
i2

-- | When we define units, they come in three flavours:
-- SI (base) units, derived SI units (aka synonyms), and defined units.
-- The type below captures that knowledge.
data UnitSymbol =
     BaseSI USymb
   | DerivedSI USymb USymb UDefn
   | Defined USymb UDefn

-- | Generates a default unit symbol.
getUSymb :: UnitSymbol -> USymb
getUSymb :: UnitSymbol -> USymb
getUSymb (BaseSI USymb
u) = USymb
u
getUSymb (DerivedSI USymb
u USymb
_ UDefn
_) = USymb
u
getUSymb (Defined USymb
u UDefn
_) = USymb
u

-- | Gets the unit definition of a unit symbol.
getDefn :: UnitSymbol -> Maybe UDefn
getDefn :: UnitSymbol -> Maybe UDefn
getDefn (BaseSI USymb
_) = Maybe UDefn
forall a. Maybe a
Nothing
getDefn (DerivedSI USymb
_ USymb
_ UDefn
d) = UDefn -> Maybe UDefn
forall a. a -> Maybe a
Just UDefn
d
getDefn (Defined USymb
_ UDefn
d) = UDefn -> Maybe UDefn
forall a. a -> Maybe a
Just UDefn
d