{-# LANGUAGE BangPatterns #-}

module Data.Array.Repa.Algorithms.Randomish
        ( randomishIntArray
        , randomishIntVector
        , randomishDoubleArray
        , randomishDoubleVector)
where
import Data.Word
import Data.Vector.Unboxed                      (Vector)
import Data.Array.Repa                          as R
import qualified Data.Vector.Unboxed.Mutable    as MV
import qualified Data.Vector.Unboxed            as V
import qualified Data.Vector.Generic            as G


-- | Use the ''minimal standard'' Lehmer generator to quickly generate some random
--   numbers with reasonable statistical properties. By ''reasonable'' we mean good
--   enough for games and test data, but not cryptography or anything where the
--   quality of the randomness really matters. 
--
--   By nature of the algorithm, the maximum value in the output is clipped
--   to (valMin + 2^31 - 1)
-- 
--   From ''Random Number Generators: Good ones are hard to find''
--   Stephen K. Park and Keith W. Miller.
--   Communications of the ACM, Oct 1988, Volume 31, Number 10.
--
randomishIntArray
        :: Shape sh
        => sh                   -- ^ Shape of array
        -> Int                  -- ^ Minumum value in output.
        -> Int                  -- ^ Maximum value in output.
        -> Int                  -- ^ Random seed.       
        -> Array U sh Int       -- ^ Array of randomish numbers.

randomishIntArray !sh !valMin !valMax !seed
        = fromUnboxed sh $ randomishIntVector (R.size sh) valMin valMax seed


randomishIntVector 
        :: Int                  -- ^ Length of vector.
        -> Int                  -- ^ Minumum value in output.
        -> Int                  -- ^ Maximum value in output.
        -> Int                  -- ^ Random seed.       
        -> Vector Int           -- ^ Vector of randomish numbers.

randomishIntVector !len !valMin' !valMax' !seed'
 = let  -- a magic number
        -- (don't change it, the randomness depends on this specific number).
        multiplier :: Word64
        multiplier = 16807

        -- a merzenne prime
        -- (don't change it, the randomness depends on this specific number).
        modulus :: Word64
        modulus = 2^(31 :: Integer) - 1

        -- if the seed is 0 all the numbers in the sequence are also 0.
        seed    
         | seed' == 0   = 1
         | otherwise    = seed'

        !valMin = fromIntegral valMin'
        !valMax = fromIntegral valMax' + 1
        !range  = valMax - valMin

        {-# INLINE f #-}
        f x             = multiplier * x `mod` modulus
 in G.create 
     $ do       
        vec             <- MV.new len

        let go !ix !x 
                | ix == len     = return ()
                | otherwise
                = do    let x'  = f x
                        MV.write vec ix $ fromIntegral $ (x `mod` range) + valMin
                        go (ix + 1) x'

        go 0 (f $ f $ f $ fromIntegral seed)
        return vec


-- | Generate some randomish doubles with terrible statistical properties.
--   This just takes randomish ints then scales them, so there's not much randomness in low-order bits.
randomishDoubleArray
        :: Shape sh
        => sh                   -- ^ Shape of array
        -> Double               -- ^ Minumum value in output.
        -> Double               -- ^ Maximum value in output.
        -> Int                  -- ^ Random seed.       
        -> Array U sh Double    -- ^ Array of randomish numbers.

randomishDoubleArray !sh !valMin !valMax !seed
        = fromUnboxed sh $ randomishDoubleVector (R.size sh) valMin valMax seed


-- | Generate some randomish doubles with terrible statistical properties.
--   This just takes randmish ints then scales them, so there's not much randomness in low-order bits.
randomishDoubleVector
        :: Int                  -- ^ Length of vector
        -> Double               -- ^ Minimum value in output
        -> Double               -- ^ Maximum value in output
        -> Int                  -- ^ Random seed.
        -> Vector Double        -- ^ Vector of randomish doubles.

randomishDoubleVector !len !valMin !valMax !seed
 = let  range   = valMax - valMin

        mx      = 2^(30 :: Integer) - 1
        mxf     = fromIntegral (mx :: Integer)
        ints    = randomishIntVector len 0 mx seed
        
   in   V.map (\n -> valMin + (fromIntegral n / mxf) * range) ints
