-- http://www.pcg-random.org/posts/bounded-rands.html
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP          #-}
module Main where

import Data.Bits
import Data.Bits.Compat
import Data.List        (unfoldr)
import Data.Word        (Word32, Word64)

import qualified System.Random            as R
import qualified System.Random.SplitMix32 as SM

#if defined(__GHCJS__)
#else
import System.Clock (Clock (Monotonic), getTime, toNanoSecs)
import Text.Printf  (printf)
#endif

main :: IO ()
main = do
    gen <- SM.newSMGen

    bench gen (\g h -> R.randomR (0, pred h) g)
    bench gen classicMod
    bench gen intMult
    bench gen bitmaskWithRejection

bench :: g -> (g -> Word32 -> (Word32, g)) -> IO ()
bench gen next = do
    print $ take 70 $ unfoldr (\g -> Just (next g 10)) gen
    clocked $ do
        let x = sumOf next gen
        print x

sumOf :: (g -> Word32 -> (Word32, g)) -> g -> Word32
sumOf next = go 0 2
  where
    go !acc !n g | n > 0xfffff = acc
                 | otherwise    = let (w, g') = next g n in go (acc + w) (succ n) g'

classicMod :: SM.SMGen -> Word32 -> (Word32, SM.SMGen)
classicMod g h =
    let (w32, g') = SM.nextWord32 g in (w32 `mod` h, g')


-- @
-- uint32_t bounded_rand(rng_t& rng, uint32_t range) {
--     uint32_t x = rng();
--     uint64_t m = uint64_t(x) * uint64_t(range);
--     return m >> 32;
-- }
-- @
--
intMult :: SM.SMGen -> Word32 -> (Word32, SM.SMGen)
intMult g h =
    (fromIntegral $ (fromIntegral w32 * fromIntegral h :: Word64) `shiftR` 32, g')
  where
    (w32, g') = SM.nextWord32 g

-- @
-- uint32_t bounded_rand(rng_t& rng, uint32_t range) {
--     uint32_t mask = ~uint32_t(0);
--     --range;
--     mask >>= __builtin_clz(range|1);
--     uint32_t x;
--     do {
--         x = rng() & mask;
--     } while (x > range);
--     return x;
-- }
-- @@
bitmaskWithRejection :: SM.SMGen -> Word32 -> (Word32, SM.SMGen)
bitmaskWithRejection g0 range = go g0
  where
    mask = complement zeroBits `shiftR` countLeadingZeros (range .|. 1)
    go g = let (x, g') = SM.nextWord32 g
               x' = x .&. mask
           in if x' >= range
              then go g'
              else (x', g')

-------------------------------------------------------------------------------
-- Poor man benchmarking with GHC and GHCJS
-------------------------------------------------------------------------------

clocked :: IO () -> IO ()
#if defined(__GHCJS__)
clocked action = do
    start
    action
    stop

foreign import javascript unsafe
    "console.time('loop');"
    start :: IO ()

foreign import javascript unsafe
    "console.timeEnd('loop');"
    stop :: IO ()
#else
clocked action =  do
    start <- getTime Monotonic
    action
    end <- getTime Monotonic
    printf "loop: %.03fms\n"
        $ fromIntegral (toNanoSecs (end - start))
        / (1e6 :: Double)
#endif
