-- |
-- Module           : Data.Parameterized.NonceGenerator
-- Description      : A counter in the ST monad.
-- Copyright        : (c) Galois, Inc 2014
-- Maintainer       : Joe Hendrix <jhendrix@galois.com>
-- Stability        : provisional
-- This module provides a simple generator of new indexes in the ST monad.
-- It is predictable and not intended for cryptographic purposes.
-- NOTE: the 'TestEquality' and 'OrdF' instances for the 'Nonce' type simply
-- compare the generated nonce values and then assert to the compiler
-- (via 'unsafeCoerce') that the types ascribed to the nonces are equal
-- if their values are equal.  This is only OK because of the discipline
-- by which nonces should be used: they should only be generated from
-- a 'NonceGenerator' (i.e., should not be built directly), and nonces from
-- different generators must never be compared!  Arranging to compare
-- Nonces from different origins would allow users to build 'unsafeCoerce'
-- via the 'testEquality' function.
-- A somewhat safer API would be to brand the generated Nonces with the
-- state type variable of the NonceGenerator whence they came, and to only
-- provide NonceGenerators via a Rank-2 continuation-passing API, similar to
-- 'runST'. This would (via a meta-argument involving parametricity)
-- help to prevent nonces of different origin from being compared.
-- However, this would force us to push the 'ST' type brand into a significant
-- number of other structures and APIs.
-- Another alternative would be to use 'unsafePerformIO' magic to make
-- a global nonce generator, and make that the only way to generate nonces.
-- It is not clear that this is actually an improvement from a type safety
-- point of view, but an argument could be made.
-- For now, be careful using Nonces, and ensure that you do not mix
-- Nonces from different NonceGenerators.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RoleAnnotations #-}
{-# LANGUAGE Unsafe #-}
module Data.Parameterized.Nonce.Unsafe
  ( NonceGenerator
  , newNonceGenerator
  , freshNonce
  , atLimit
  , Nonce
  , indexValue
  ) where

import Control.Monad.ST
import Data.Hashable
import Data.STRef
import Data.Word
import Unsafe.Coerce

import Data.Parameterized.Classes

-- | A simple type that for getting fresh indices in the 'ST' monad.
-- The type parameter @s@ is used for the 'ST' monad parameter.
newtype NonceGenerator s = NonceGenerator (STRef s Word64)

-- | Create a new counter.
newNonceGenerator :: ST s (NonceGenerator s)
newNonceGenerator = NonceGenerator `fmap` newSTRef (toEnum 0)

-- | An index generated by the counter.
newtype Nonce (tp :: k) = Nonce { indexValue :: Word64 }
  deriving (Eq, Ord, Hashable, Show)

--  Force the type role of Nonce to be nominal: this prevents Data.Coerce.coerce
--  from casting the types of nonces, which it would otherwise be able to do
--  because tp is a phantom type parameter.  This partially helps to protect
--  the nonce abstraction.
type role Nonce nominal

instance TestEquality Nonce where
  testEquality x y | indexValue x == indexValue y = unsafeCoerce (Just Refl)
                   | otherwise = Nothing

instance OrdF Nonce where
  compareF x y =
    case compare (indexValue x) (indexValue y) of
      LT -> LTF
      EQ -> unsafeCoerce EQF
      GT -> GTF

instance HashableF Nonce where
  hashWithSaltF s (Nonce x) = hashWithSalt s x

instance ShowF Nonce

{-# INLINE freshNonce #-}
-- | Get a fresh index and increment the counter.
freshNonce :: NonceGenerator s -> ST s (Nonce tp)
freshNonce (NonceGenerator r) = do
  i <- readSTRef r
  writeSTRef r $! succ i
  return (Nonce i)

-- | Return true if counter has reached the limit, and can't be
-- incremented without risk of error.
atLimit :: NonceGenerator s -> ST s Bool
atLimit (NonceGenerator r) = do
  i <- readSTRef r
  return (i == maxBound)