----------------------------------------------------------------------------
-- |
-- Module      :  Control.Concurrent.Counter.Lifted.IO
-- Copyright   :  (c) Sergey Vinokurov 2022
-- License     :  Apache-2.0 (see LICENSE)
-- Maintainer  :  serg.foo@gmail.com
--
-- Lifted 'Control.Concurrent.Counter.Lifted.Counter' specialized to
-- operate in the 'IO' monad.
----------------------------------------------------------------------------

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE TypeApplications    #-}

module Control.Concurrent.Counter.Lifted.IO
  ( Counter

  -- * Create
  , new

  -- * Read/write
  , get
  , set
  , cas

  -- * Arithmetic operations
  , add
  , sub

  -- * Bitwise operations
  , and
  , or
  , xor
  , nand
  ) where

import Prelude hiding (and, or)

import Data.Coerce
import GHC.Exts (RealWorld)
import GHC.IO
import GHC.ST

import Control.Concurrent.Counter.Lifted.ST qualified as Lifted


-- | Memory location that supports select few atomic operations.
--
-- Isomorphic to @IORef Int@.
newtype Counter = Counter (Lifted.Counter RealWorld)

-- | Pointer equality
instance Eq Counter where
  == :: Counter -> Counter -> Bool
(==) = coerce :: forall a b. Coercible a b => a -> b
coerce (forall a. Eq a => a -> a -> Bool
(==) @(Lifted.Counter RealWorld))

{-# INLINE new #-}
-- | Create new counter with initial value.
new :: Int -> IO Counter
new :: Int -> IO Counter
new = coerce :: forall a b. Coercible a b => a -> b
coerce forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. ST RealWorld a -> IO a
stToIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall s. Int -> ST s (Counter s)
Lifted.new


{-# INLINE get #-}
-- | Atomically read the counter's value.
get :: Counter -> IO Int
get :: Counter -> IO Int
get = coerce :: forall a b. Coercible a b => a -> b
coerce forall s. Counter s -> ST s Int
Lifted.get

{-# INLINE set #-}
-- | Atomically assign new value to the counter.
set :: Counter -> Int -> IO ()
set :: Counter -> Int -> IO ()
set = coerce :: forall a b. Coercible a b => a -> b
coerce forall s. Counter s -> Int -> ST s ()
Lifted.set

{-# INLINE cas #-}
-- | Atomic compare and swap, i.e. write the new value if the current
-- value matches the provided old value. Returns the value of the
-- element before the operation
cas
  :: Counter
  -> Int -- ^ Expected old value
  -> Int -- ^ New value
  -> IO Int
cas :: Counter -> Int -> Int -> IO Int
cas = coerce :: forall a b. Coercible a b => a -> b
coerce forall s. Counter s -> Int -> Int -> ST s Int
Lifted.cas

{-# INLINE add #-}
-- | Atomically add an amount to the counter and return its old value.
add :: Counter -> Int -> IO Int
add :: Counter -> Int -> IO Int
add = coerce :: forall a b. Coercible a b => a -> b
coerce forall s. Counter s -> Int -> ST s Int
Lifted.add

{-# INLINE sub #-}
-- | Atomically subtract an amount from the counter and return its old value.
sub :: Counter -> Int -> IO Int
sub :: Counter -> Int -> IO Int
sub = coerce :: forall a b. Coercible a b => a -> b
coerce forall s. Counter s -> Int -> ST s Int
Lifted.sub


{-# INLINE and #-}
-- | Atomically combine old value with a new one via bitwise and. Returns old counter value.
and :: Counter -> Int -> IO Int
and :: Counter -> Int -> IO Int
and = coerce :: forall a b. Coercible a b => a -> b
coerce forall s. Counter s -> Int -> ST s Int
Lifted.and

{-# INLINE or #-}
-- | Atomically combine old value with a new one via bitwise or. Returns old counter value.
or :: Counter -> Int -> IO Int
or :: Counter -> Int -> IO Int
or = coerce :: forall a b. Coercible a b => a -> b
coerce forall s. Counter s -> Int -> ST s Int
Lifted.or

{-# INLINE xor #-}
-- | Atomically combine old value with a new one via bitwise xor. Returns old counter value.
xor :: Counter -> Int -> IO Int
xor :: Counter -> Int -> IO Int
xor = coerce :: forall a b. Coercible a b => a -> b
coerce forall s. Counter s -> Int -> ST s Int
Lifted.xor

{-# INLINE nand #-}
-- | Atomically combine old value with a new one via bitwise nand. Returns old counter value.
nand :: Counter -> Int -> IO Int
nand :: Counter -> Int -> IO Int
nand = coerce :: forall a b. Coercible a b => a -> b
coerce forall s. Counter s -> Int -> ST s Int
Lifted.nand