{-
 -      ``Data/Random/Source''
 -}
{-# LANGUAGE
    MultiParamTypeClasses, FlexibleInstances
  #-}

module Data.Random.Source
    ( MonadRandom(..)
    , RandomSource(..)
    ) where

import Data.Word
import Control.Monad

import Data.Random.Internal.Words

-- |A typeclass for monads with a chosen source of entropy.  For example,
-- 'RVar' is such a monad - the source from which it is (eventually) sampled
-- is the only source from which a random variable is permitted to draw, so
-- when directly requesting entropy for a random variable these functions
-- are used.
-- 
-- The minimal definition is either 'getRandomByte' or 'getRandomWord'.
-- 'getRandomDouble' is defaulted in terms of 'getRandomWord'.
class Monad m => MonadRandom m where
    -- |Get a random uniformly-distributed byte.
    getRandomByte :: m Word8
    getRandomByte = do
        word <- getRandomWord
        return (fromIntegral word)
    
    -- |Get a random 'Word64' uniformly-distributed over the full range of the type.
    getRandomWord :: m Word64
    getRandomWord = do
        b0 <- getRandomByte
        b1 <- getRandomByte
        b2 <- getRandomByte
        b3 <- getRandomByte
        b4 <- getRandomByte
        b5 <- getRandomByte
        b6 <- getRandomByte
        b7 <- getRandomByte
        
        return (buildWord b0 b1 b2 b3 b4 b5 b6 b7)
    
    -- |Get a random 'Double' uniformly-distributed over the interval [0,1)
    getRandomDouble :: m Double
    getRandomDouble = do
        word <- getRandomWord
        return (wordToDouble word)

-- |A source of entropy which can be used in the given monad.
--
-- The minimal definition is either 'getRandomByteFrom' or 'getRandomWordFrom'.
-- 'getRandomDoubleFrom' is defaulted in terms of 'getRandomWordFrom'
class Monad m => RandomSource m s where
    -- |Get a random uniformly-distributed byte.
    getRandomByteFrom :: s -> m Word8
    getRandomByteFrom src = do
        word <- getRandomWordFrom src
        return (fromIntegral word)
    
    -- |Get a random 'Word64' uniformly-distributed over the full range of the type.
    getRandomWordFrom :: s -> m Word64
    getRandomWordFrom src = do
        b0 <- getRandomByteFrom src
        b1 <- getRandomByteFrom src
        b2 <- getRandomByteFrom src
        b3 <- getRandomByteFrom src
        b4 <- getRandomByteFrom src
        b5 <- getRandomByteFrom src
        b6 <- getRandomByteFrom src
        b7 <- getRandomByteFrom src
        
        return (buildWord b0 b1 b2 b3 b4 b5 b6 b7)
    
    -- |Get a random 'Double' uniformly-distributed over the interval [0,1)
    getRandomDoubleFrom :: s -> m Double
    getRandomDoubleFrom src = do
        word <- getRandomWordFrom src
        return (wordToDouble word)

instance Monad m => RandomSource m (m Word8) where
    getRandomByteFrom = id

instance Monad m => RandomSource m (m Word64) where
    getRandomWordFrom = id