-- | Provides the 'ModP' type for modular arithmetic.
--
-- @since 0.1
module Numeric.Data.ModP
  ( -- * Type
    ModP (MkModP),

    -- * Creation
    Internal.mkModP,
    mkModPTH,
    Internal.unsafeModP,
    Internal.reallyUnsafeModP,

    -- * Elimination
    unModP,

    -- * Functions
    Internal.invert,

    -- * Optics
    -- $optics
    _MkModP,
    rmatching,
  )
where

import Data.Bounds (AnyUpperBounded)
import Data.Typeable (Typeable)
import GHC.TypeNats (KnownNat)
import Language.Haskell.TH (Code, Q)
import Language.Haskell.TH.Syntax (Lift (liftTyped))
import Numeric.Data.Internal.Utils (rmatching)
import Numeric.Data.ModP.Internal (ModP (MkModP, UnsafeModP))
import Numeric.Data.ModP.Internal qualified as Internal
import Optics.Core (ReversedPrism', ReversibleOptic (re), prism)

-- | @since 0.1
unModP :: ModP p a -> a
unModP :: forall (p :: Nat) a. ModP p a -> a
unModP (UnsafeModP a
x) = a
x
{-# INLINE unModP #-}

-- | Template haskell for creating a 'ModP' at compile-time.
--
-- ==== __Examples__
-- >>> $$(mkModPTH @11 7)
-- MkModP 7 (mod 11)
--
-- @since 0.1
mkModPTH ::
  forall p a.
  ( AnyUpperBounded a,
    Integral a,
    KnownNat p,
    Lift a,
    Typeable a
  ) =>
  a ->
  Code Q (ModP p a)
mkModPTH :: forall (p :: Nat) a.
(AnyUpperBounded a, Integral a, KnownNat p, Lift a, Typeable a) =>
a -> Code Q (ModP p a)
mkModPTH a
x = case a -> Either String (ModP p a)
forall (p :: Nat) a.
(AnyUpperBounded a, Integral a, KnownNat p, Typeable a) =>
a -> Either String (ModP p a)
Internal.mkModP a
x of
  Right ModP p a
y -> ModP p a -> Code Q (ModP p a)
forall t (m :: Type -> Type). (Lift t, Quote m) => t -> Code m t
forall (m :: Type -> Type).
Quote m =>
ModP p a -> Code m (ModP p a)
liftTyped ModP p a
y
  Left String
err -> String -> Code Q (ModP p a)
forall a. HasCallStack => String -> a
error (String -> Code Q (ModP p a)) -> String -> Code Q (ModP p a)
forall a b. (a -> b) -> a -> b
$ String -> String -> String
Internal.errMsg String
"mkModPTH" String
err
{-# INLINEABLE mkModPTH #-}

-- $optics
-- We provide a 'ReversedPrism'' '_MkModP' that allows for total
-- elimination and partial construction, along with a 'Optics.Core.LabelOptic' 'Optics.Core.Getter'
-- for @#unModP@.
--
-- ==== __Examples__
--
-- >>> :set -XOverloadedLabels
-- >>> import Optics.Core (view)
-- >>> let n = $$(mkModPTH @7 9)
-- >>> view #unModP n
-- 2

-- | 'ReversedPrism'' that enables total elimination and partial construction.
--
-- ==== __Examples__
-- >>> import Optics.Core (view)
-- >>> n = $$(mkModPTH @7 9)
-- >>> view _MkModP n
-- 2
--
-- >>> rmatching (_MkModP @7) 9
-- Right (MkModP 2 (mod 7))
--
-- >>> rmatching (_MkModP @6) 9
-- Left 9
--
-- @since 0.1
_MkModP ::
  forall p a.
  ( AnyUpperBounded a,
    Integral a,
    KnownNat p,
    Typeable a
  ) =>
  ReversedPrism' (ModP p a) a
_MkModP :: forall (p :: Nat) a.
(AnyUpperBounded a, Integral a, KnownNat p, Typeable a) =>
ReversedPrism' (ModP p a) a
_MkModP = Optic A_Prism NoIx a a (ModP p a) (ModP p a)
-> Optic (ReversedOptic A_Prism) NoIx (ModP p a) (ModP p a) a a
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 ((ModP p a -> a)
-> (a -> Either a (ModP p a))
-> Optic A_Prism NoIx a a (ModP p a) (ModP p a)
forall b t s a. (b -> t) -> (s -> Either t a) -> Prism s t a b
prism ModP p a -> a
forall (p :: Nat) a. ModP p a -> a
unModP a -> Either a (ModP p a)
forall {a} {p :: Nat}.
(AnyUpperBounded a, Integral a, KnownNat p, Typeable a) =>
a -> Either a (ModP p a)
g)
  where
    g :: a -> Either a (ModP p a)
g a
x = case a -> Either String (ModP p a)
forall (p :: Nat) a.
(AnyUpperBounded a, Integral a, KnownNat p, Typeable a) =>
a -> Either String (ModP p a)
Internal.mkModP a
x of
      Left String
_ -> a -> Either a (ModP p a)
forall a b. a -> Either a b
Left a
x
      Right ModP p a
x' -> ModP p a -> Either a (ModP p a)
forall a b. b -> Either a b
Right ModP p a
x'
{-# INLINEABLE _MkModP #-}