{-# LANGUAGE MagicHash #-}
{-# LANGUAGE StrictData #-}

{-|
Module      : Random.XorshiftPlus
Description : xorshift+ implementation
Copyright   : (c) OSANAI Kazuyoshi, 2019
License     : BSD 3-Clause
Maintainer  : osmium.k@gmail.com
Stability   : experimental
Portability : GHC, word size 64bit

Simple implementation of xorshift+.

>>> gen <- genXorshiftPlusInt 1
>>> getInt gen
2455688531189531812
-}
module Random.XorshiftPlus
  (
  -- * IO version
    XorshiftPlus
  -- ** Generator
  , genXorshiftPlusWord
  , genXorshiftPlusInt
  -- ** Values
  , getWord
  , getInt
  , getDouble
  -- * Low level, ST version
  , XorshiftPlusST
  , genXorshiftPlusWordST
  , getWordST
  -- * Internal
  , splitMix64Next
  ) where

-- import Data.IORef
import Data.STRef
-- import Data.Coerce
import Control.Monad.ST
import GHC.Types
import GHC.Prim
import Prelude hiding (not)

-- $setup
-- >>> :set -XMagicHash

data XorshiftPlus1 = XorshiftPlus1 Word# Word#

-- | Random state
newtype XorshiftPlusST s = XorshiftPlusST (STRef s XorshiftPlus1)

-- | Random state
type XorshiftPlus = XorshiftPlusST RealWorld

plus :: Word# -> Word# -> Word#
plus = plusWord#

times :: Word# -> Word# -> Word#
times = timesWord#

xor :: Word# -> Word# -> Word#
xor = xor#

shiftL :: Word# -> Int# -> Word#
shiftL = uncheckedShiftL#

shiftR :: Word# -> Int# -> Word#
shiftR = uncheckedShiftRL#

-- | For first return value.
--
-- >>> W# (splitMix64Next 1##)
-- 10451216379200822465
splitMix64Next :: Word# -> Word#
splitMix64Next x =
  let z1 = x `plus` 0x9e3779b97f4a7c15## in
  let z2 = (z1 `xor` (z1 `shiftR` 30#)) `times` 0xbf58476d1ce4e5b9## in
  let z3 = (z2 `xor` (z2 `shiftR` 27#)) `times` 0x94d049bb133111eb## in
  z3 `xor` (z3 `shiftR` 31#)

-- | Generate a new random state by a Word seed.
genXorshiftPlusWordST :: Word -> ST s (XorshiftPlusST s)
genXorshiftPlusWordST (W# w) = do
  ref <- newSTRef (XorshiftPlus1 w (splitMix64Next w))
  return $ XorshiftPlusST ref

-- | Generate a new random state by a Word seed.
genXorshiftPlusWord
  :: Word            -- ^ random seed
  -> IO XorshiftPlus
genXorshiftPlusWord w = stToIO (genXorshiftPlusWordST w)

-- | Generate a new random state by an Int seed.
genXorshiftPlusInt
  :: Int             -- ^ random seed
  -> IO XorshiftPlus
genXorshiftPlusInt i = genXorshiftPlusWord (fromIntegral i)

-- | Get a new random value as Word.
getWordST :: XorshiftPlusST s -> ST s Word
getWordST (XorshiftPlusST ref) = do
  XorshiftPlus1 w0 w1 <- readSTRef ref
  let x = w0 `xor` (w0 `shiftL` 17#)
  let y = x `xor` w1 `xor` (x `shiftR` 17#) `xor` (w1 `shiftR` 26#)
  writeSTRef ref $ XorshiftPlus1 w1 y
  return $ W# (w1 `plus` y)

-- | Get a new random value as Word.
getWord :: XorshiftPlus -> IO Word
getWord = stToIO . getWordST

-- | Get a new random value as Int.
getInt :: XorshiftPlus -> IO Int
getInt x = do
  w <- getWord x
  return $ fromIntegral w

-- | Get a new random value as Double [0, 1.0].
getDouble :: XorshiftPlus -> IO Double
getDouble x = do
  let maxWord = fromIntegral (maxBound :: Word)
  w <- getWord x
  return $ (fromIntegral w) / maxWord