-- | Provides the 'Percentage' type.
--
-- @since 0.1
module Pythia.Data.Percentage
  ( -- * Type
    Percentage (MkPercentage),

    -- * Creation
    mkPercentage,
    mkPercentageTH,
    unsafePercentage,

    -- * Elimination
    unPercentage,

    -- * Optics
    _MkPercentage,
  )
where

import Effects.Exception (HasCallStack)
import Language.Haskell.TH (Code, Q)
import Language.Haskell.TH.Syntax (Lift (liftTyped))
import Numeric.Data.Interval qualified as Interval
import Optics.Core (ReversedPrism')
import Pythia.Data.Percentage.Internal
  ( Percentage (InternalPercentage, MkPercentage),
    unPercentage,
  )
import Pythia.Prelude

-- | Creates a percentage for x in [0, 100].
--
-- @since 0.1
mkPercentage :: Word8 -> Maybe Percentage
mkPercentage :: Word8 -> Maybe Percentage
mkPercentage = (Interval ('Closed 0) ('Closed 100) Word8 -> Percentage)
-> Maybe (Interval ('Closed 0) ('Closed 100) Word8)
-> Maybe Percentage
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap Interval ('Closed 0) ('Closed 100) Word8 -> Percentage
InternalPercentage (Maybe (Interval ('Closed 0) ('Closed 100) Word8)
 -> Maybe Percentage)
-> (Word8 -> Maybe (Interval ('Closed 0) ('Closed 100) Word8))
-> Word8
-> Maybe Percentage
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> Maybe (Interval ('Closed 0) ('Closed 100) Word8)
forall (l :: IntervalBound) (r :: IntervalBound) a.
(Num a, Ord a, SingIntervalBound l, SingIntervalBound r) =>
a -> Maybe (Interval l r a)
Interval.mkInterval

-- | Safely creates a percentage at compile-time.
--
-- @since 0.1
mkPercentageTH :: Word8 -> Code Q Percentage
mkPercentageTH :: Word8 -> Code Q Percentage
mkPercentageTH Word8
x = Code Q Percentage
-> (Percentage -> Code Q Percentage)
-> Maybe Percentage
-> Code Q Percentage
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (String -> Code Q Percentage
forall a. HasCallStack => String -> a
error (String -> Code Q Percentage) -> String -> Code Q Percentage
forall a b. (a -> b) -> a -> b
$ Word8 -> String
errMsg Word8
x) Percentage -> Code Q Percentage
forall t (m :: Type -> Type). (Lift t, Quote m) => t -> Code m t
forall (m :: Type -> Type).
Quote m =>
Percentage -> Code m Percentage
liftTyped (Maybe Percentage -> Code Q Percentage)
-> Maybe Percentage -> Code Q Percentage
forall a b. (a -> b) -> a -> b
$ Word8 -> Maybe Percentage
mkPercentage Word8
x

-- | Unsafely creates a percentage for x in [0, 100]. Calls error otherwise.
--
-- @since 0.1
unsafePercentage :: (HasCallStack) => Word8 -> Percentage
unsafePercentage :: HasCallStack => Word8 -> Percentage
unsafePercentage Word8
x = Percentage -> Maybe Percentage -> Percentage
forall a. a -> Maybe a -> a
fromMaybe (String -> Percentage
forall a. HasCallStack => String -> a
error (String -> Percentage) -> String -> Percentage
forall a b. (a -> b) -> a -> b
$ Word8 -> String
errMsg Word8
x) (Maybe Percentage -> Percentage) -> Maybe Percentage -> Percentage
forall a b. (a -> b) -> a -> b
$ Word8 -> Maybe Percentage
mkPercentage Word8
x

-- | 'ReversedPrism'' that enables total elimination and partial construction.
--
-- @since 0.1
_MkPercentage :: ReversedPrism' Percentage Word8
_MkPercentage :: ReversedPrism' Percentage Word8
_MkPercentage = Optic A_Prism NoIx Word8 Word8 Percentage Percentage
-> Optic
     (ReversedOptic A_Prism) NoIx Percentage Percentage Word8 Word8
forall (is :: IxList) s t a b.
AcceptsEmptyIndices "re" is =>
Optic A_Prism is s t a b
-> Optic (ReversedOptic A_Prism) is b a t s
forall k (is :: IxList) s t a b.
(ReversibleOptic k, AcceptsEmptyIndices "re" is) =>
Optic k is s t a b -> Optic (ReversedOptic k) is b a t s
re ((Percentage -> Word8)
-> (Word8 -> Either Word8 Percentage)
-> Optic A_Prism NoIx Word8 Word8 Percentage Percentage
forall b t s a. (b -> t) -> (s -> Either t a) -> Prism s t a b
prism Percentage -> Word8
unPercentage Word8 -> Either Word8 Percentage
g)
  where
    g :: Word8 -> Either Word8 Percentage
g Word8
x = case Word8 -> Maybe Percentage
mkPercentage Word8
x of
      Maybe Percentage
Nothing -> Word8 -> Either Word8 Percentage
forall a b. a -> Either a b
Left Word8
x
      Just Percentage
x' -> Percentage -> Either Word8 Percentage
forall a b. b -> Either a b
Right Percentage
x'

errMsg :: Word8 -> String
errMsg :: Word8 -> String
errMsg Word8
x =
  String
"Pythia.Data.Percentage: Wanted percentage in [0, 100], received: " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Word8 -> String
forall a. Show a => a -> String
show Word8
x