{-# Language TemplateHaskell #-}
-- | Defines uncertainty types and functions.
module Language.Drasil.Uncertainty (
  -- * Type
  Uncertainty,
  -- * Class
  HasUncertainty(..),
  -- * Lenses
  uncert, prec,
  -- * Constructors
  uncty, exact,
  -- * Constructor
  defaultUncrt,
  -- * Accessors
  uncVal, uncPrec,
) where

import Control.Lens (Lens', (^.), makeLenses)

import Data.Maybe (fromMaybe)

-- | Something that may contain an uncertainty value and a precision value.
data Uncertainty = Uncert {
  Uncertainty -> Maybe Double
_uncert :: Maybe Double,
  Uncertainty -> Maybe Int
_prec   :: Maybe Int
}
makeLenses ''Uncertainty

-- | HasUncertainty is just a chunk with some uncertainty associated to it.
-- This uncertainty is represented as a decimal value between 0 and 1 (percentage).
class HasUncertainty c where
  -- | Provides the 'Lens' to an 'Uncertainty'.
  unc  :: Lens' c Uncertainty

-- | Smart constructor for values with uncertainty.
uncty :: Double -> Maybe Int -> Uncertainty
uncty :: Double -> Maybe Int -> Uncertainty
uncty Double
u = Maybe Double -> Maybe Int -> Uncertainty
Uncert (forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. (Num a, Ord a) => a -> a
isDecimal Double
u)

-- | Smart constructor for exact values (no uncertainty).
exact :: Uncertainty
exact :: Uncertainty
exact = Maybe Double -> Maybe Int -> Uncertainty
Uncert forall a. Maybe a
Nothing forall a. Maybe a
Nothing

-- | Make sure that input is between 0 and 1, and throw an error otherwise.
isDecimal :: (Num a, Ord a) => a -> a
isDecimal :: forall a. (Num a, Ord a) => a -> a
isDecimal a
0  =  forall a. HasCallStack => [Char] -> a
error [Char]
"An uncertain quantity cannot be exact (have 0% uncertainty). Reconsider whether your value is exact or uncertain"
isDecimal a
u  =  if (a
0 forall a. Ord a => a -> a -> Bool
< a
u) Bool -> Bool -> Bool
&& (a
u forall a. Ord a => a -> a -> Bool
< a
1) then a
u
                else forall a. HasCallStack => [Char] -> a
error [Char]
"Uncertainty must be between 0 and 1"

-- | The default uncertainty is set to 0.1.
defaultUncrt :: Uncertainty
defaultUncrt :: Uncertainty
defaultUncrt = Double -> Maybe Int -> Uncertainty
uncty Double
0.1 (forall a. a -> Maybe a
Just Int
0)

-- | Accessor for uncertainty value from something that has an uncertainty.
uncVal :: HasUncertainty x => x -> Double
uncVal :: forall x. HasUncertainty x => x -> Double
uncVal x
u = forall a. a -> Maybe a -> a
fromMaybe Double
0.0 forall a b. (a -> b) -> a -> b
$ x
u forall s a. s -> Getting a s a -> a
^. (forall c. HasUncertainty c => Lens' c Uncertainty
unc forall b c a. (b -> c) -> (a -> b) -> a -> c
. Lens' Uncertainty (Maybe Double)
uncert)

-- | Accessor for precision value from something that has an uncertainty.
uncPrec :: HasUncertainty x => x -> Maybe Int
uncPrec :: forall x. HasUncertainty x => x -> Maybe Int
uncPrec x
u = x
u forall s a. s -> Getting a s a -> a
^. (forall c. HasUncertainty c => Lens' c Uncertainty
unc forall b c a. (b -> c) -> (a -> b) -> a -> c
. Lens' Uncertainty (Maybe Int)
prec)