{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
-- | A random number effect, using a pure mersenne twister under
--   the hood. This should be plug-and-play with any application
--   making use of extensible effects.
--
--   Patches, even for the smallest of documentation bugs, are
--   always welcome!
module System.Random.Effect ( Random
                            , mkRandom
                            , mkRandomIO
                            , randomInt
                            , randomInt64
                            , randomWord
                            , randomWord64
                            , randomDouble
                            ) where

import Data.Int
import Data.Typeable
import Data.Word
import qualified System.Random.Mersenne.Pure64 as SR

import Control.Eff
import Control.Eff.State
import Control.Eff.Lift

-- | A pure mersenne twister pseudo-random number generator.
data Random = Random {-# UNPACK #-} !SR.PureMT
    deriving Typeable

-- | Create a random number generator from a 'Word64' seed.
mkRandom :: Word64 -> Random
mkRandom = Random . SR.pureMT
{-# INLINE mkRandom #-}

-- | Create a new random number generator, using the clocktime as the base for
--   the seed. This must be called from a computation with a lifted base effect
--   of 'IO'.
mkRandomIO :: Member (Lift IO) r
           => Eff r Random
mkRandomIO = lift (fmap Random SR.newPureMT)
{-# INLINE mkRandomIO #-}

-- | Runs an effectful random computation, returning the computation's result.
runRandomState :: Random
               -> Eff (State Random :> r) w
               -> Eff r w
runRandomState seed computation =
    fmap snd (runState seed computation)
{-# INLINE runRandomState #-}

-- | A generalized form of generating a random number of the correct type
--   from System.Random.Mersenne.Pure64.
randomF :: Member (State Random) r
        => (SR.PureMT -> (a, SR.PureMT))
        -> Eff r a
randomF f = do
    (Random old) <- getState
    let (val, new) = f old
    putState (Random new)
    return val
{-# INLINE randomF #-}

-- | Yield a new 'Int' value from the generator. The full 64 bits will be used
--   on a 64 bit machine.
randomInt :: Member (State Random) r => Eff r Int
randomInt = randomF SR.randomInt
{-# INLINE randomInt #-}

-- | Yield a new 'Word' value from the generator.
randomWord :: Member (State Random) r => Eff r Word
randomWord = randomF SR.randomWord
{-# INLINE randomWord #-}

-- | Yield a new 'Int64' value from the generator.
randomInt64 :: Member (State Random) r => Eff r Int64
randomInt64 = randomF SR.randomInt64
{-# INLINE randomInt64 #-}

-- | Yield a new 'Word64' value from the generator.
randomWord64 :: Member (State Random) r => Eff r Word64
randomWord64 = randomF SR.randomWord64
{-# INLINE randomWord64 #-}

-- | Yield a new 53-bit precise 'Double' value from the generator.
randomDouble :: Member (State Random) r => Eff r Double
randomDouble = randomF SR.randomDouble
{-# INLINE randomDouble #-}