{-# LANGUAGE RoleAnnotations #-}

-- |
-- Module: Covenant.Index
-- Copyright: (C) MLabs 2025
-- License: Apache 2.0
-- Maintainer: koz@mlabs.city, sean@mlabs.city
--
-- Positional indexes, starting from 0, and cardinality indicators.
--
-- @since 1.0.0
module Covenant.Index
  ( Index,
    Count,
    intIndex,
    intCount,
    ix0,
    count0,
    ix1,
    count1,
    ix2,
    count2,
    ix3,
    count3,
    wordCount,
  )
where

import Data.Bits (toIntegralSized)
import Data.Coerce (coerce)
import Data.List.NonEmpty (NonEmpty)
import Data.Semigroup (Semigroup (sconcat, stimes), Sum (Sum))
import Data.Word (Word32)
import GHC.TypeLits (Symbol)
import Optics.Core (Lens', lens)
import Optics.Prism (Prism', prism)
import Test.QuickCheck (Arbitrary)

-- | A positional index, starting from zero. The label allows distinguishing
-- different flavours of indices.
--
-- @since 1.0.0
newtype Index (ofWhat :: Symbol) = Index Word32
  deriving
    ( -- | @since 1.0.0
      Index ofWhat -> Index ofWhat -> Bool
(Index ofWhat -> Index ofWhat -> Bool)
-> (Index ofWhat -> Index ofWhat -> Bool) -> Eq (Index ofWhat)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Bool
$c== :: forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Bool
== :: Index ofWhat -> Index ofWhat -> Bool
$c/= :: forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Bool
/= :: Index ofWhat -> Index ofWhat -> Bool
Eq,
      -- | @since 1.0.0
      Eq (Index ofWhat)
Eq (Index ofWhat) =>
(Index ofWhat -> Index ofWhat -> Ordering)
-> (Index ofWhat -> Index ofWhat -> Bool)
-> (Index ofWhat -> Index ofWhat -> Bool)
-> (Index ofWhat -> Index ofWhat -> Bool)
-> (Index ofWhat -> Index ofWhat -> Bool)
-> (Index ofWhat -> Index ofWhat -> Index ofWhat)
-> (Index ofWhat -> Index ofWhat -> Index ofWhat)
-> Ord (Index ofWhat)
Index ofWhat -> Index ofWhat -> Bool
Index ofWhat -> Index ofWhat -> Ordering
Index ofWhat -> Index ofWhat -> Index ofWhat
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
forall (ofWhat :: Symbol). Eq (Index ofWhat)
forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Bool
forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Ordering
forall (ofWhat :: Symbol).
Index ofWhat -> Index ofWhat -> Index ofWhat
$ccompare :: forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Ordering
compare :: Index ofWhat -> Index ofWhat -> Ordering
$c< :: forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Bool
< :: Index ofWhat -> Index ofWhat -> Bool
$c<= :: forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Bool
<= :: Index ofWhat -> Index ofWhat -> Bool
$c> :: forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Bool
> :: Index ofWhat -> Index ofWhat -> Bool
$c>= :: forall (ofWhat :: Symbol). Index ofWhat -> Index ofWhat -> Bool
>= :: Index ofWhat -> Index ofWhat -> Bool
$cmax :: forall (ofWhat :: Symbol).
Index ofWhat -> Index ofWhat -> Index ofWhat
max :: Index ofWhat -> Index ofWhat -> Index ofWhat
$cmin :: forall (ofWhat :: Symbol).
Index ofWhat -> Index ofWhat -> Index ofWhat
min :: Index ofWhat -> Index ofWhat -> Index ofWhat
Ord,
      -- | @since 1.0.0
      Gen (Index ofWhat)
Gen (Index ofWhat)
-> (Index ofWhat -> [Index ofWhat]) -> Arbitrary (Index ofWhat)
Index ofWhat -> [Index ofWhat]
forall a. Gen a -> (a -> [a]) -> Arbitrary a
forall (ofWhat :: Symbol). Gen (Index ofWhat)
forall (ofWhat :: Symbol). Index ofWhat -> [Index ofWhat]
$carbitrary :: forall (ofWhat :: Symbol). Gen (Index ofWhat)
arbitrary :: Gen (Index ofWhat)
$cshrink :: forall (ofWhat :: Symbol). Index ofWhat -> [Index ofWhat]
shrink :: Index ofWhat -> [Index ofWhat]
Arbitrary
    )
    via Word32
  deriving stock
    ( -- | @since 1.0.0
      Int -> Index ofWhat -> ShowS
[Index ofWhat] -> ShowS
Index ofWhat -> String
(Int -> Index ofWhat -> ShowS)
-> (Index ofWhat -> String)
-> ([Index ofWhat] -> ShowS)
-> Show (Index ofWhat)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall (ofWhat :: Symbol). Int -> Index ofWhat -> ShowS
forall (ofWhat :: Symbol). [Index ofWhat] -> ShowS
forall (ofWhat :: Symbol). Index ofWhat -> String
$cshowsPrec :: forall (ofWhat :: Symbol). Int -> Index ofWhat -> ShowS
showsPrec :: Int -> Index ofWhat -> ShowS
$cshow :: forall (ofWhat :: Symbol). Index ofWhat -> String
show :: Index ofWhat -> String
$cshowList :: forall (ofWhat :: Symbol). [Index ofWhat] -> ShowS
showList :: [Index ofWhat] -> ShowS
Show
    )

type role Index nominal

-- | Enables some manner of arithmetic with 'Index'ess. In this case, '<>' is
-- analogous to '+', while @'stimes' b@ is analogous to scalar multiplication by
-- @b@. Note that 'Index'es cannot be scaled by negative numbers.
--
-- @since 1.0.0
instance Semigroup (Index ofWhat) where
  {-# INLINEABLE (<>) #-}
  Index Word32
x <> :: Index ofWhat -> Index ofWhat -> Index ofWhat
<> Index Word32
y = Word32 -> Index ofWhat
forall (ofWhat :: Symbol). Word32 -> Index ofWhat
Index (Word32
x Word32 -> Word32 -> Word32
forall a. Num a => a -> a -> a
+ Word32
y)
  {-# INLINEABLE sconcat #-}
  sconcat :: NonEmpty (Index ofWhat) -> Index ofWhat
sconcat = Word32 -> Index ofWhat
forall (ofWhat :: Symbol). Word32 -> Index ofWhat
Index (Word32 -> Index ofWhat)
-> (NonEmpty (Index ofWhat) -> Word32)
-> NonEmpty (Index ofWhat)
-> Index ofWhat
forall b c a. (b -> c) -> (a -> b) -> a -> c
. NonEmpty Word32 -> Word32
forall a. Num a => NonEmpty a -> a
forall (t :: Type -> Type) a. (Foldable t, Num a) => t a -> a
sum (NonEmpty Word32 -> Word32)
-> (NonEmpty (Index ofWhat) -> NonEmpty Word32)
-> NonEmpty (Index ofWhat)
-> Word32
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. Coercible a b => a -> b
forall a b. Coercible a b => a -> b
coerce @(NonEmpty (Index ofWhat)) @(NonEmpty Word32)
  {-# INLINEABLE stimes #-}
  stimes :: forall b. Integral b => b -> Index ofWhat -> Index ofWhat
stimes b
b = Word32 -> Index ofWhat
forall (ofWhat :: Symbol). Word32 -> Index ofWhat
Index (Word32 -> Index ofWhat)
-> (Index ofWhat -> Word32) -> Index ofWhat -> Index ofWhat
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Sum Word32 -> Word32
forall a b. Coercible a b => a -> b
coerce (Sum Word32 -> Word32)
-> (Index ofWhat -> Sum Word32) -> Index ofWhat -> Word32
forall b c a. (b -> c) -> (a -> b) -> a -> c
. b -> Sum Word32 -> Sum Word32
forall b. Integral b => b -> Sum Word32 -> Sum Word32
forall a b. (Semigroup a, Integral b) => b -> a -> a
stimes b
b (Sum Word32 -> Sum Word32)
-> (Index ofWhat -> Sum Word32) -> Index ofWhat -> Sum Word32
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. Coercible a b => a -> b
forall a b. Coercible a b => a -> b
coerce @_ @(Sum Word32)

-- | @since 1.0.0
instance Monoid (Index ofWhat) where
  {-# INLINEABLE mempty #-}
  mempty :: Index ofWhat
mempty = Word32 -> Index ofWhat
forall (ofWhat :: Symbol). Word32 -> Index ofWhat
Index Word32
0

-- | Helper to construct, and convert, 'Index'es and 'Int's. This is needed
-- because unfortunately, the standard Haskell practice is to use 'Int' for
-- indexes.
--
-- To use this, do one of the following:
--
-- * Construct with @'preview'@: for example, @'preview' intIndex 1@.
-- * Destruct with @'review'@.
--
-- @since 1.0.0
intIndex :: forall (ofWhat :: Symbol). Prism' Int (Index ofWhat)
intIndex :: forall (ofWhat :: Symbol). Prism' Int (Index ofWhat)
intIndex =
  (Index ofWhat -> Int)
-> (Int -> Either Int (Index ofWhat))
-> Prism Int Int (Index ofWhat) (Index ofWhat)
forall b t s a. (b -> t) -> (s -> Either t a) -> Prism s t a b
prism
    (Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> Int) -> (Index ofWhat -> Word32) -> Index ofWhat -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. Coercible a b => a -> b
forall a b. Coercible a b => a -> b
coerce @_ @Word32)
    (\Int
i -> Either Int (Index ofWhat)
-> (Word32 -> Either Int (Index ofWhat))
-> Maybe Word32
-> Either Int (Index ofWhat)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Int -> Either Int (Index ofWhat)
forall a b. a -> Either a b
Left Int
i) (Index ofWhat -> Either Int (Index ofWhat)
forall a b. b -> Either a b
Right (Index ofWhat -> Either Int (Index ofWhat))
-> (Word32 -> Index ofWhat) -> Word32 -> Either Int (Index ofWhat)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Index ofWhat
forall (ofWhat :: Symbol). Word32 -> Index ofWhat
Index) (Maybe Word32 -> Either Int (Index ofWhat))
-> (Int -> Maybe Word32) -> Int -> Either Int (Index ofWhat)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Maybe Word32
forall a b.
(Integral a, Integral b, Bits a, Bits b) =>
a -> Maybe b
toIntegralSized (Int -> Either Int (Index ofWhat))
-> Int -> Either Int (Index ofWhat)
forall a b. (a -> b) -> a -> b
$ Int
i)

-- | Helper for the first index.
--
-- @since 1.0.0
ix0 :: forall (ofWhat :: Symbol). Index ofWhat
ix0 :: forall (ofWhat :: Symbol). Index ofWhat
ix0 = Word32 -> Index ofWhat
forall (ofWhat :: Symbol). Word32 -> Index ofWhat
Index Word32
0

-- | Helper for the second index.
--
-- @since 1.0.0
ix1 :: forall (ofWhat :: Symbol). Index ofWhat
ix1 :: forall (ofWhat :: Symbol). Index ofWhat
ix1 = Word32 -> Index ofWhat
forall (ofWhat :: Symbol). Word32 -> Index ofWhat
Index Word32
1

-- | Helper for the third index.
--
-- @since 1.0.0
ix2 :: forall (ofWhat :: Symbol). Index ofWhat
ix2 :: forall (ofWhat :: Symbol). Index ofWhat
ix2 = Word32 -> Index ofWhat
forall (ofWhat :: Symbol). Word32 -> Index ofWhat
Index Word32
2

-- | Helper for the fourth index.
--
-- @since 1.0.0
ix3 :: forall (ofWhat :: Symbol). Index ofWhat
ix3 :: forall (ofWhat :: Symbol). Index ofWhat
ix3 = Word32 -> Index ofWhat
forall (ofWhat :: Symbol). Word32 -> Index ofWhat
Index Word32
3

-- | An indicator of the cardinality of something. Meant to be paired with
-- 'Index' to specify which unique something you mean.
--
-- @since 1.0.0
newtype Count (ofWhat :: Symbol) = Count Word32
  deriving
    ( -- | @since 1.0.0
      Count ofWhat -> Count ofWhat -> Bool
(Count ofWhat -> Count ofWhat -> Bool)
-> (Count ofWhat -> Count ofWhat -> Bool) -> Eq (Count ofWhat)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Bool
$c== :: forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Bool
== :: Count ofWhat -> Count ofWhat -> Bool
$c/= :: forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Bool
/= :: Count ofWhat -> Count ofWhat -> Bool
Eq,
      -- | @since 1.0.0
      Eq (Count ofWhat)
Eq (Count ofWhat) =>
(Count ofWhat -> Count ofWhat -> Ordering)
-> (Count ofWhat -> Count ofWhat -> Bool)
-> (Count ofWhat -> Count ofWhat -> Bool)
-> (Count ofWhat -> Count ofWhat -> Bool)
-> (Count ofWhat -> Count ofWhat -> Bool)
-> (Count ofWhat -> Count ofWhat -> Count ofWhat)
-> (Count ofWhat -> Count ofWhat -> Count ofWhat)
-> Ord (Count ofWhat)
Count ofWhat -> Count ofWhat -> Bool
Count ofWhat -> Count ofWhat -> Ordering
Count ofWhat -> Count ofWhat -> Count ofWhat
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
forall (ofWhat :: Symbol). Eq (Count ofWhat)
forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Bool
forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Ordering
forall (ofWhat :: Symbol).
Count ofWhat -> Count ofWhat -> Count ofWhat
$ccompare :: forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Ordering
compare :: Count ofWhat -> Count ofWhat -> Ordering
$c< :: forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Bool
< :: Count ofWhat -> Count ofWhat -> Bool
$c<= :: forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Bool
<= :: Count ofWhat -> Count ofWhat -> Bool
$c> :: forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Bool
> :: Count ofWhat -> Count ofWhat -> Bool
$c>= :: forall (ofWhat :: Symbol). Count ofWhat -> Count ofWhat -> Bool
>= :: Count ofWhat -> Count ofWhat -> Bool
$cmax :: forall (ofWhat :: Symbol).
Count ofWhat -> Count ofWhat -> Count ofWhat
max :: Count ofWhat -> Count ofWhat -> Count ofWhat
$cmin :: forall (ofWhat :: Symbol).
Count ofWhat -> Count ofWhat -> Count ofWhat
min :: Count ofWhat -> Count ofWhat -> Count ofWhat
Ord
    )
    via Word32
  deriving stock
    ( -- | @since 1.0.0
      Int -> Count ofWhat -> ShowS
[Count ofWhat] -> ShowS
Count ofWhat -> String
(Int -> Count ofWhat -> ShowS)
-> (Count ofWhat -> String)
-> ([Count ofWhat] -> ShowS)
-> Show (Count ofWhat)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall (ofWhat :: Symbol). Int -> Count ofWhat -> ShowS
forall (ofWhat :: Symbol). [Count ofWhat] -> ShowS
forall (ofWhat :: Symbol). Count ofWhat -> String
$cshowsPrec :: forall (ofWhat :: Symbol). Int -> Count ofWhat -> ShowS
showsPrec :: Int -> Count ofWhat -> ShowS
$cshow :: forall (ofWhat :: Symbol). Count ofWhat -> String
show :: Count ofWhat -> String
$cshowList :: forall (ofWhat :: Symbol). [Count ofWhat] -> ShowS
showList :: [Count ofWhat] -> ShowS
Show
    )

type role Count nominal

-- | Helper to construct, and convert, 'Count's and 'Int's. This is needed
-- because unfortunately, sizes of things are usually 'Int's in Haskell.
--
-- To use this, do one of the following:
--
-- * Construct with @'preview'@: for example, @'preview' intCount 1@.
-- * Destruct with @'review'@.
--
-- @since 1.0.0
intCount :: forall (ofWhat :: Symbol). Prism' Int (Count ofWhat)
intCount :: forall (ofWhat :: Symbol). Prism' Int (Count ofWhat)
intCount =
  (Count ofWhat -> Int)
-> (Int -> Either Int (Count ofWhat))
-> Prism Int Int (Count ofWhat) (Count ofWhat)
forall b t s a. (b -> t) -> (s -> Either t a) -> Prism s t a b
prism
    (Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> Int) -> (Count ofWhat -> Word32) -> Count ofWhat -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. Coercible a b => a -> b
forall a b. Coercible a b => a -> b
coerce @_ @Word32)
    (\Int
i -> Either Int (Count ofWhat)
-> (Word32 -> Either Int (Count ofWhat))
-> Maybe Word32
-> Either Int (Count ofWhat)
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (Int -> Either Int (Count ofWhat)
forall a b. a -> Either a b
Left Int
i) (Count ofWhat -> Either Int (Count ofWhat)
forall a b. b -> Either a b
Right (Count ofWhat -> Either Int (Count ofWhat))
-> (Word32 -> Count ofWhat) -> Word32 -> Either Int (Count ofWhat)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Count ofWhat
forall (ofWhat :: Symbol). Word32 -> Count ofWhat
Count) (Maybe Word32 -> Either Int (Count ofWhat))
-> (Int -> Maybe Word32) -> Int -> Either Int (Count ofWhat)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Maybe Word32
forall a b.
(Integral a, Integral b, Bits a, Bits b) =>
a -> Maybe b
toIntegralSized (Int -> Either Int (Count ofWhat))
-> Int -> Either Int (Count ofWhat)
forall a b. (a -> b) -> a -> b
$ Int
i)

-- | We use the Word32 directly during renaming, and a Lens is more appropriate
-- than a Prism if we're working with Word32s
wordCount :: forall (ofWhat :: Symbol). Lens' (Count ofWhat) Word32
wordCount :: forall (ofWhat :: Symbol). Lens' (Count ofWhat) Word32
wordCount = (Count ofWhat -> Word32)
-> (Count ofWhat -> Word32 -> Count ofWhat)
-> Lens (Count ofWhat) (Count ofWhat) Word32 Word32
forall s a b t. (s -> a) -> (s -> b -> t) -> Lens s t a b
lens (\(Count Word32
x) -> Word32
x) (\Count ofWhat
_ Word32
w -> Word32 -> Count ofWhat
forall (ofWhat :: Symbol). Word32 -> Count ofWhat
Count Word32
w)

-- | Helper for a count of zero items.
--
-- @since 1.0.0
count0 :: forall (ofWhat :: Symbol). Count ofWhat
count0 :: forall (ofWhat :: Symbol). Count ofWhat
count0 = Word32 -> Count ofWhat
forall (ofWhat :: Symbol). Word32 -> Count ofWhat
Count Word32
0

-- | Helper for a count of one item.
--
-- @since 1.0.0
count1 :: forall (ofWhat :: Symbol). Count ofWhat
count1 :: forall (ofWhat :: Symbol). Count ofWhat
count1 = Word32 -> Count ofWhat
forall (ofWhat :: Symbol). Word32 -> Count ofWhat
Count Word32
1

-- | Helper for a count of two items.
--
-- @since 1.0.0
count2 :: forall (ofWhat :: Symbol). Count ofWhat
count2 :: forall (ofWhat :: Symbol). Count ofWhat
count2 = Word32 -> Count ofWhat
forall (ofWhat :: Symbol). Word32 -> Count ofWhat
Count Word32
2

-- | Helper for a count of three items.
--
-- @since 1.0.0
count3 :: forall (ofWhat :: Symbol). Count ofWhat
count3 :: forall (ofWhat :: Symbol). Count ofWhat
count3 = Word32 -> Count ofWhat
forall (ofWhat :: Symbol). Word32 -> Count ofWhat
Count Word32
3