{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Main (main) where

import Control.Monad
import Control.Monad.State.Strict
import Data.Int
import Data.List (sortOn)
import Data.Proxy
import Data.Typeable
import Data.Word
import Foreign.C.Types
import Numeric.Natural (Natural)
import System.Random.SplitMix as SM
import Test.Tasty.Bench
#if MIN_VERSION_primitive(0,7,1)
import Control.Monad.Primitive
import Data.Primitive.Types
import Data.Primitive.PrimArray
#endif

import System.Random.Stateful

seed :: Int
seed = 1337

main :: IO ()
main = do
  let !sz = 100000
      !sz100MiB = 100 * 1024 * 1024
      genLengths :: ([Int], StdGen)
      genLengths =
        -- create 5000 small lengths that are needed for ShortByteString generation
        runStateGen (mkStdGen 2020) $ \g -> replicateM 5000 (uniformRM (16 + 1, 16 + 7) g)
  setStdGen $ mkStdGen seed
  defaultMain
    [ bgroup
        "baseline"
        [ env (pure $ SM.mkSMGen $ fromIntegral seed) $ \smGen ->
            bench "nextWord32" $ whnf (genMany SM.nextWord32 smGen) sz
        , env (pure $ SM.mkSMGen $ fromIntegral seed) $ \smGen ->
            bench "nextWord64" $ whnf (genMany SM.nextWord64 smGen) sz
        , env (pure $ SM.mkSMGen $ fromIntegral seed) $ \smGen ->
            bench "nextInt" $ whnf (genMany SM.nextInt smGen) sz
        , env (pure $ SM.mkSMGen $ fromIntegral seed) $ \smGen ->
            bench "split" $ whnf (genMany SM.splitSMGen smGen) sz
        ]
    , bgroup
        "pure"
        [ bgroup
            "random"
            [ pureBench random sz (Proxy :: Proxy Word8)
            , pureBench random sz (Proxy :: Proxy Word16)
            , pureBench random sz (Proxy :: Proxy Word32)
            , pureBench random sz (Proxy :: Proxy Word64)
            , pureBench random sz (Proxy :: Proxy Int8)
            , pureBench random sz (Proxy :: Proxy Int16)
            , pureBench random sz (Proxy :: Proxy Int32)
            , pureBench random sz (Proxy :: Proxy Int64)
            , pureBench random sz (Proxy :: Proxy Bool)
            , pureBench random sz (Proxy :: Proxy Char)
            , pureBench random sz (Proxy :: Proxy Float)
            , pureBench random sz (Proxy :: Proxy Double)
            , pureBench random sz (Proxy :: Proxy Integer)
            ]
        , bgroup
            "uniform"
            [ pureBench uniform sz (Proxy :: Proxy Word8)
            , pureBench uniform sz (Proxy :: Proxy Word16)
            , pureBench uniform sz (Proxy :: Proxy Word32)
            , pureBench uniform sz (Proxy :: Proxy Word64)
            , pureBench uniform sz (Proxy :: Proxy Int8)
            , pureBench uniform sz (Proxy :: Proxy Int16)
            , pureBench uniform sz (Proxy :: Proxy Int32)
            , pureBench uniform sz (Proxy :: Proxy Int64)
            , pureBench uniform sz (Proxy :: Proxy Bool)
            , pureBench uniform sz (Proxy :: Proxy Char)
            , pureBench uniform sz (Proxy :: Proxy CChar)
            , pureBench uniform sz (Proxy :: Proxy CSChar)
            , pureBench uniform sz (Proxy :: Proxy CUChar)
            , pureBench uniform sz (Proxy :: Proxy CShort)
            , pureBench uniform sz (Proxy :: Proxy CUShort)
            , pureBench uniform sz (Proxy :: Proxy CInt)
            , pureBench uniform sz (Proxy :: Proxy CUInt)
            , pureBench uniform sz (Proxy :: Proxy CLong)
            , pureBench uniform sz (Proxy :: Proxy CULong)
            , pureBench uniform sz (Proxy :: Proxy CPtrdiff)
            , pureBench uniform sz (Proxy :: Proxy CSize)
            , pureBench uniform sz (Proxy :: Proxy CWchar)
            , pureBench uniform sz (Proxy :: Proxy CSigAtomic)
            , pureBench uniform sz (Proxy :: Proxy CLLong)
            , pureBench uniform sz (Proxy :: Proxy CULLong)
            , pureBench uniform sz (Proxy :: Proxy CIntPtr)
            , pureBench uniform sz (Proxy :: Proxy CUIntPtr)
            , pureBench uniform sz (Proxy :: Proxy CIntMax)
            , pureBench uniform sz (Proxy :: Proxy CUIntMax)
            ]
        , bgroup
            "uniformR"
            [ bgroup
                "full"
                [ pureUniformRFullBench (Proxy :: Proxy Word8) sz
                , pureUniformRFullBench (Proxy :: Proxy Word16) sz
                , pureUniformRFullBench (Proxy :: Proxy Word32) sz
                , pureUniformRFullBench (Proxy :: Proxy Word64) sz
                , pureUniformRFullBench (Proxy :: Proxy Word) sz
                , pureUniformRFullBench (Proxy :: Proxy Int8) sz
                , pureUniformRFullBench (Proxy :: Proxy Int16) sz
                , pureUniformRFullBench (Proxy :: Proxy Int32) sz
                , pureUniformRFullBench (Proxy :: Proxy Int64) sz
                , pureUniformRFullBench (Proxy :: Proxy Int) sz
                , pureUniformRFullBench (Proxy :: Proxy Char) sz
                , pureUniformRFullBench (Proxy :: Proxy Bool) sz
                , pureUniformRFullBench (Proxy :: Proxy CChar) sz
                , pureUniformRFullBench (Proxy :: Proxy CSChar) sz
                , pureUniformRFullBench (Proxy :: Proxy CUChar) sz
                , pureUniformRFullBench (Proxy :: Proxy CShort) sz
                , pureUniformRFullBench (Proxy :: Proxy CUShort) sz
                , pureUniformRFullBench (Proxy :: Proxy CInt) sz
                , pureUniformRFullBench (Proxy :: Proxy CUInt) sz
                , pureUniformRFullBench (Proxy :: Proxy CLong) sz
                , pureUniformRFullBench (Proxy :: Proxy CULong) sz
                , pureUniformRFullBench (Proxy :: Proxy CPtrdiff) sz
                , pureUniformRFullBench (Proxy :: Proxy CSize) sz
                , pureUniformRFullBench (Proxy :: Proxy CWchar) sz
                , pureUniformRFullBench (Proxy :: Proxy CSigAtomic) sz
                , pureUniformRFullBench (Proxy :: Proxy CLLong) sz
                , pureUniformRFullBench (Proxy :: Proxy CULLong) sz
                , pureUniformRFullBench (Proxy :: Proxy CIntPtr) sz
                , pureUniformRFullBench (Proxy :: Proxy CUIntPtr) sz
                , pureUniformRFullBench (Proxy :: Proxy CIntMax) sz
                , pureUniformRFullBench (Proxy :: Proxy CUIntMax) sz
                ]
            , bgroup
                "excludeMax"
                [ pureUniformRExcludeMaxBench (Proxy :: Proxy Word8) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Word16) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Word32) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Word64) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Word) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Int8) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Int16) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Int32) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Int64) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Int) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Char) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy Bool) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CChar) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CSChar) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CUChar) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CShort) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CUShort) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CInt) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CUInt) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CLong) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CULong) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CPtrdiff) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CSize) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CWchar) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CSigAtomic) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CLLong) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CULLong) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CIntPtr) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CUIntPtr) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CIntMax) sz
                , pureUniformRExcludeMaxBench (Proxy :: Proxy CUIntMax) sz
                ]
            , bgroup
                "includeHalf"
                [ pureUniformRIncludeHalfBench (Proxy :: Proxy Word8) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy Word16) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy Word32) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy Word64) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy Word) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy Int8) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy Int16) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy Int32) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy Int64) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy Int) sz
                , pureUniformRIncludeHalfEnumBench (Proxy :: Proxy Char) sz
                , pureUniformRIncludeHalfEnumBench (Proxy :: Proxy Bool) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CChar) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CSChar) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CUChar) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CShort) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CUShort) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CInt) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CUInt) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CLong) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CULong) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CPtrdiff) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CSize) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CWchar) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CSigAtomic) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CLLong) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CULLong) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CIntPtr) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CUIntPtr) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CIntMax) sz
                , pureUniformRIncludeHalfBench (Proxy :: Proxy CUIntMax) sz
                ]
            , bgroup
                "unbounded"
                [ pureUniformRBench (Proxy :: Proxy Float) (1.23e-4, 5.67e8) sz
                , pureUniformRBench (Proxy :: Proxy Double) (1.23e-4, 5.67e8) sz
                , let !i = (10 :: Integer) ^ (100 :: Integer)
                      !range = (-i - 1, i + 1)
                   in pureUniformRBench (Proxy :: Proxy Integer) range sz
                , let !n = (10 :: Natural) ^ (100 :: Natural)
                      !range = (1, n - 1)
                   in pureUniformRBench (Proxy :: Proxy Natural) range sz
                ]
            , bgroup "floating" $
                fillFloating sz
                  ++ [ bgroup
                         "State"
                         [ bgroup
                             "Float"
                             [ env getStdGen $
                                 bench "uniformRM" . nf (`runStateGen` (replicateM_ sz . uniformRM (0.1 :: Float, 1.1)))
                             , env getStdGen $
                                 bench "uniformFloat01M" . nf (`runStateGen` (replicateM_ sz . uniformFloat01M))
                             , env getStdGen $
                                 bench "uniformFloatPositive01M"
                                   . nf (`runStateGen` (replicateM_ sz . uniformFloatPositive01M))
                             ]
                         , bgroup
                             "Double"
                             [ env getStdGen $
                                 bench "uniformRM" . nf (`runStateGen` (replicateM_ sz . uniformRM (0.1 :: Double, 1.1)))
                             , env getStdGen $
                                 bench "uniformDouble01M" . nf (`runStateGen` (replicateM_ sz . uniformDouble01M))
                             , env getStdGen $
                                 bench "uniformDoublePositive01M"
                                   . nf (`runStateGen` (replicateM_ sz . uniformDoublePositive01M))
                             ]
                         ]
                     , bgroup
                         "pure"
                         [ bgroup
                             "Float"
                             [ env getStdGen $ \gen ->
                                 bench "uniformRM" $
                                   nf
                                     (genMany (runState $ uniformRM (0.1 :: Float, 1.1) (StateGenM :: StateGenM StdGen)) gen)
                                     sz
                             , env getStdGen $ \gen ->
                                 bench "uniformFloat01M" $
                                   nf
                                     (genMany (runState $ uniformFloat01M (StateGenM :: StateGenM StdGen)) gen)
                                     sz
                             , env getStdGen $ \gen ->
                                 bench "uniformFloatPositive01M" $
                                   nf
                                     (genMany (runState $ uniformFloatPositive01M (StateGenM :: StateGenM StdGen)) gen)
                                     sz
                             ]
                         , bgroup
                             "Double"
                             [ env getStdGen $ \gen ->
                                 bench "uniformRM" $
                                   nf
                                     (genMany (runState $ uniformRM (0.1 :: Double, 1.1) (StateGenM :: StateGenM StdGen)) gen)
                                     sz
                             , env getStdGen $ \gen ->
                                 bench "uniformDouble01M" $
                                   nf
                                     (genMany (runState $ uniformDouble01M (StateGenM :: StateGenM StdGen)) gen)
                                     sz
                             , env getStdGen $ \gen ->
                                 bench "uniformDoublePositive01M" $
                                   nf
                                     (genMany (runState $ uniformDoublePositive01M (StateGenM :: StateGenM StdGen)) gen)
                                     sz
                             ]
                         ]
                     ]
            ]
        , bgroup
            "Bytes"
            [ env (pure genLengths) $ \ ~(ns, gen) ->
                bench "uniformShortByteStringM" $
                  nfIO $
                    runStateGenT gen $
                      \g -> mapM (`uniformShortByteStringM` g) ns
            , env getStdGen $ \gen ->
                bench "uniformByteStringM 100MB" $
                  nf (runStateGen gen . uniformByteStringM) sz100MiB
            , env getStdGen $ \gen ->
                bench "uniformByteArray 100MB" $ nf (\n -> uniformByteArray False n gen) sz100MiB
            , env getStdGen $ \gen ->
                bench "uniformByteString 100MB" $ nf (`uniformByteString` gen) sz100MiB
            ]
        ]
    , env (pure [0 :: Integer .. 200000]) $ \xs ->
        bgroup
          "shuffle"
          [ env getStdGen $ bench "uniformShuffleList" . nf (uniformShuffleList xs)
          , env getStdGen $ bench "uniformShuffleListM" . nf (`runStateGen` uniformShuffleListM xs)
          , env getStdGen $ bench "naiveShuffleListM" . nf (`runStateGen` naiveShuffleListM xs)
          ]
    ]
  where
#if MIN_VERSION_primitive(0,7,1)
    fillFloating sz =
      [ bgroup "IO"
        [ bgroup "Float"
          [ env ((,) <$> getStdGen <*> newAlignedPinnedPrimArray sz) $ \ ~(gen, ma) ->
              bench "uniformRM" $
              nfIO (runStateGenT gen (fillMutablePrimArrayM (uniformRM (0 :: Float, 1.1)) ma))
          , env ((,) <$> getStdGen <*> newAlignedPinnedPrimArray sz) $ \ ~(gen, ma) ->
              bench "uniformFloat01M" $
              nfIO (runStateGenT gen (fillMutablePrimArrayM uniformFloat01M ma))
          , env ((,) <$> getStdGen <*> newAlignedPinnedPrimArray sz) $ \ ~(gen, ma) ->
              bench "uniformFloatPositive01M" $
              nfIO (runStateGenT gen (fillMutablePrimArrayM uniformFloatPositive01M ma))
          ]
        , bgroup "Double"
          [ env ((,) <$> getStdGen <*> newAlignedPinnedPrimArray sz) $ \ ~(gen, ma) ->
              bench "uniformRM" $
              nfIO (runStateGenT gen (fillMutablePrimArrayM (uniformRM (0 :: Double, 1.1)) ma))
          , env ((,) <$> getStdGen <*> newAlignedPinnedPrimArray sz) $ \ ~(gen, ma) ->
              bench "uniformDouble01M" $
              nfIO (runStateGenT gen (fillMutablePrimArrayM uniformDouble01M ma))
          , env ((,) <$> getStdGen <*> newAlignedPinnedPrimArray sz) $ \ ~(gen, ma) ->
              bench "uniformDoublePositive01M" $
              nfIO (runStateGenT gen (fillMutablePrimArrayM uniformDoublePositive01M ma))
          ]
        ]
      ]
#else
    fillFloating _ = []
#endif

pureUniformRFullBench ::
  forall a.
  (Typeable a, UniformRange a, Bounded a) =>
  Proxy a ->
  Int ->
  Benchmark
pureUniformRFullBench px =
  let range = (minBound :: a, maxBound :: a)
   in pureUniformRBench px range
{-# INLINE pureUniformRFullBench #-}

pureUniformRExcludeMaxBench ::
  forall a.
  (Typeable a, UniformRange a, Bounded a, Enum a) =>
  Proxy a ->
  Int ->
  Benchmark
pureUniformRExcludeMaxBench px =
  let range = (minBound :: a, pred (maxBound :: a))
   in pureUniformRBench px range
{-# INLINE pureUniformRExcludeMaxBench #-}

pureUniformRIncludeHalfBench ::
  forall a.
  (Typeable a, UniformRange a, Bounded a, Integral a) =>
  Proxy a ->
  Int ->
  Benchmark
pureUniformRIncludeHalfBench px =
  let range = ((minBound :: a) + 1, ((maxBound :: a) `div` 2) + 1)
   in pureUniformRBench px range
{-# INLINE pureUniformRIncludeHalfBench #-}

pureUniformRIncludeHalfEnumBench ::
  forall a.
  (Typeable a, UniformRange a, Bounded a, Enum a) =>
  Proxy a ->
  Int ->
  Benchmark
pureUniformRIncludeHalfEnumBench px =
  let range = (succ (minBound :: a), toEnum ((fromEnum (maxBound :: a) `div` 2) + 1))
   in pureUniformRBench px range
{-# INLINE pureUniformRIncludeHalfEnumBench #-}

pureUniformRBench ::
  forall a.
  (Typeable a, UniformRange a) =>
  Proxy a ->
  (a, a) ->
  Int ->
  Benchmark
pureUniformRBench px range@(!_, !_) sz = pureBench (uniformR range) sz px
{-# INLINE pureUniformRBench #-}

pureBench ::
  forall a.
  Typeable a =>
  (StdGen -> (a, StdGen)) ->
  Int ->
  Proxy a ->
  Benchmark
pureBench f sz px =
  env getStdGen $ \gen ->
    bench (showsTypeRep (typeRep px) "") $ whnf (genMany f gen) sz
{-# INLINE pureBench #-}

genMany :: (g -> (a, g)) -> g -> Int -> a
genMany f g0 n = go 0 $ f g0
  where
    go i (!y, !g)
      | i < n = go (i + 1) $ f g
      | otherwise = y

#if MIN_VERSION_primitive(0,7,1)
fillMutablePrimArrayM ::
     (Prim a, PrimMonad m)
  => (gen -> m a)
  -> MutablePrimArray (PrimState m) a
  -> gen
  -> m (PrimArray a)
fillMutablePrimArrayM f ma g = do
  n <- getSizeofMutablePrimArray ma
  let go i
        | i < n = f g >>= writePrimArray ma i >> go (i + 1)
        | otherwise = pure ()
  go 0
  unsafeFreezePrimArray ma
#endif

naiveShuffleListM :: StatefulGen g m => [a] -> g -> m [a]
naiveShuffleListM xs gen = do
  is <- uniformListM n gen
  pure $ map snd $ sortOn fst $ zip (is :: [Int]) xs
  where
    !n = length xs
{-# INLINE naiveShuffleListM #-}
