{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances  #-}
-- |
-- Module      : Data.Metrics.Counter
-- Copyright   : (c) Ian Duncan 2013
-- Stability   : experimental
-- Portability : non-portable
--
-- An incrementing and decrementing counter metric
--
-- > import Data.Metrics.Counter
-- >
-- > main :: IO ()
-- > main = do
-- >   c <- counter
-- >   increment c
-- >   x <- value c
-- >   print $ x == 1
--
module Data.Metrics.Counter (
  Counter,
  counter,
  increment,
  increment',
  decrement,
  decrement',
  module Data.Metrics.Types
) where
import Control.Monad.Base
import Control.Monad.Primitive
import qualified Data.HashMap.Strict as H
import Data.Metrics.Internal
import Data.Metrics.Types
import Data.Primitive.MutVar

-- | A basic atomic counter.
newtype Counter m = Counter { fromCounter :: MV m Int }

instance (MonadBase b m, PrimMonad b) => Count b m (Counter b) where
  count (Counter ref) = liftBase $ readMutVar ref
  {-# INLINEABLE count #-}

instance (MonadBase b m, PrimMonad b) => Value b m (Counter b) Int where
  value (Counter ref) = liftBase $ readMutVar ref
  {-# INLINEABLE value #-}

instance (MonadBase b m, PrimMonad b) => Set b m (Counter b) Int where
  set (Counter ref) x = liftBase $ updateRef ref (const x)
  {-# INLINEABLE set #-}

instance (MonadBase b m, PrimMonad b) => Clear b m (Counter b) where
  clear c = set c 0
  {-# INLINEABLE clear #-}

-- | Create a new counter.
counter :: (MonadBase b m, PrimMonad b) => m (Counter b)
counter = liftBase $ fmap Counter $ newMutVar 0
{-# INLINEABLE counter #-}

-- | Bump up a counter by 1.
increment :: PrimMonad m => Counter m -> m ()
increment = flip increment' 1
{-# INLINEABLE increment #-}

-- | Add an arbitrary amount to a counter.
increment' :: PrimMonad m => Counter m -> Int -> m ()
increment' (Counter ref) x = updateRef ref (+ x)
{-# INLINEABLE increment' #-}

-- | Decrease the value of a counter by 1.
decrement :: PrimMonad m => Counter m -> m ()
decrement = flip decrement' 1
{-# INLINEABLE decrement #-}

-- | Subtract an arbitrary amount from a counter.
decrement' :: PrimMonad m => Counter m -> Int -> m ()
decrement' (Counter ref) x = updateRef ref (subtract x)
{-# INLINEABLE decrement' #-}