{-# LANGUAGE NamedFieldPuns #-}

module CRDT.Cv.PNCounter
    ( PNCounter (..)
    , initial
    , query
    -- * Operations
    , decrement
    , increment
    ) where

import           Data.Semilattice (Semilattice)

import           CRDT.Cv.GCounter (GCounter)
import qualified CRDT.Cv.GCounter as GCounter

{- |
Positive-negative counter. Allows incrementing and decrementing.
Nice example of combining of existing CvRDT ('GCounter' in this case)
to create another CvRDT.
-}
data PNCounter a = PNCounter
    { PNCounter a -> GCounter a
positive :: !(GCounter a)
    , PNCounter a -> GCounter a
negative :: !(GCounter a)
    }
    deriving (PNCounter a -> PNCounter a -> Bool
(PNCounter a -> PNCounter a -> Bool)
-> (PNCounter a -> PNCounter a -> Bool) -> Eq (PNCounter a)
forall a. Eq a => PNCounter a -> PNCounter a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PNCounter a -> PNCounter a -> Bool
$c/= :: forall a. Eq a => PNCounter a -> PNCounter a -> Bool
== :: PNCounter a -> PNCounter a -> Bool
$c== :: forall a. Eq a => PNCounter a -> PNCounter a -> Bool
Eq, Int -> PNCounter a -> ShowS
[PNCounter a] -> ShowS
PNCounter a -> String
(Int -> PNCounter a -> ShowS)
-> (PNCounter a -> String)
-> ([PNCounter a] -> ShowS)
-> Show (PNCounter a)
forall a. Show a => Int -> PNCounter a -> ShowS
forall a. Show a => [PNCounter a] -> ShowS
forall a. Show a => PNCounter a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PNCounter a] -> ShowS
$cshowList :: forall a. Show a => [PNCounter a] -> ShowS
show :: PNCounter a -> String
$cshow :: forall a. Show a => PNCounter a -> String
showsPrec :: Int -> PNCounter a -> ShowS
$cshowsPrec :: forall a. Show a => Int -> PNCounter a -> ShowS
Show)

instance Ord a => Semigroup (PNCounter a) where
    PNCounter GCounter a
p1 GCounter a
n1 <> :: PNCounter a -> PNCounter a -> PNCounter a
<> PNCounter GCounter a
p2 GCounter a
n2 = GCounter a -> GCounter a -> PNCounter a
forall a. GCounter a -> GCounter a -> PNCounter a
PNCounter (GCounter a
p1 GCounter a -> GCounter a -> GCounter a
forall a. Semigroup a => a -> a -> a
<> GCounter a
p2) (GCounter a
n1 GCounter a -> GCounter a -> GCounter a
forall a. Semigroup a => a -> a -> a
<> GCounter a
n2)

-- | See 'CvRDT'
instance Ord a => Semilattice (PNCounter a)

-- | Get value from the state
query :: Num a => PNCounter a -> a
query :: PNCounter a -> a
query PNCounter{GCounter a
positive :: GCounter a
positive :: forall a. PNCounter a -> GCounter a
positive, GCounter a
negative :: GCounter a
negative :: forall a. PNCounter a -> GCounter a
negative} =
    GCounter a -> a
forall a. Num a => GCounter a -> a
GCounter.query GCounter a
positive a -> a -> a
forall a. Num a => a -> a -> a
- GCounter a -> a
forall a. Num a => GCounter a -> a
GCounter.query GCounter a
negative

-- | Decrement counter
decrement
    :: Num a
    => Word -- ^ replica id
    -> PNCounter a
    -> PNCounter a
decrement :: Word -> PNCounter a -> PNCounter a
decrement Word
i pnc :: PNCounter a
pnc@PNCounter{GCounter a
negative :: GCounter a
negative :: forall a. PNCounter a -> GCounter a
negative} =
    PNCounter a
pnc{negative :: GCounter a
negative = Word -> GCounter a -> GCounter a
forall a. Num a => Word -> GCounter a -> GCounter a
GCounter.increment Word
i GCounter a
negative}

-- | Increment counter
increment
    :: Num a
    => Word -- ^ replica id
    -> PNCounter a
    -> PNCounter a
increment :: Word -> PNCounter a -> PNCounter a
increment Word
i pnc :: PNCounter a
pnc@PNCounter{GCounter a
positive :: GCounter a
positive :: forall a. PNCounter a -> GCounter a
positive} =
    PNCounter a
pnc{positive :: GCounter a
positive = Word -> GCounter a -> GCounter a
forall a. Num a => Word -> GCounter a -> GCounter a
GCounter.increment Word
i GCounter a
positive}

-- | Initial state
initial :: PNCounter a
initial :: PNCounter a
initial = PNCounter :: forall a. GCounter a -> GCounter a -> PNCounter a
PNCounter{positive :: GCounter a
positive = GCounter a
forall a. GCounter a
GCounter.initial, negative :: GCounter a
negative = GCounter a
forall a. GCounter a
GCounter.initial}