{-# LANGUAGE BangPatterns               #-}
{-# LANGUAGE CPP                        #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Main (main) where

import           Control.DeepSeq
import           Control.Monad
import           Control.Monad.ST
import           Control.Monad.Trans
import           Data.Benchmarks.UnorderedCollections.Distributions
import           Data.Benchmarks.UnorderedCollections.Types
import           Data.Bits
import           Data.ByteString                                    (ByteString)
import qualified Data.ByteString                                    as B
import qualified Data.ByteString.Base16                             as B16
import           Data.Hashable
import qualified Data.HashMap.Strict                                as UC
import qualified Data.HashTable.IO                                  as IOH
import           Data.IORef
import qualified Data.Map                                           as Map
import           System.Environment
import           System.FilePath
import           System.Random.MWC

import           Criterion.Collection.Main
import           Criterion.Collection.Sample
import           Criterion.Collection.Types


------------------------------------------------------------------------------
#if !MIN_VERSION_bytestring(0,10,0)
instance NFData ByteString
#endif

------------------------------------------------------------------------------
dataMap :: (Ord k, Eq k) => DataStructure (Operation k)
dataMap = setupData Map.empty f
  where
    f !m !op = case op of
                 (Insert k v) -> let !m' = Map.insert k v m in m'
                 (Lookup k)   -> let !_  = Map.lookup k m in m
                 (Delete k)   -> let !m' = Map.delete k m in m'
{-# INLINE dataMap #-}


------------------------------------------------------------------------------
hashMap :: (Hashable k, Eq k) => DataStructure (Operation k)
hashMap = setupData UC.empty f
  where
    f !m !op = case op of
                 (Insert k v) -> let !m' = UC.insert k v m in m'
                 (Lookup k)   -> let !_  = UC.lookup k m in m
                 (Delete k)   -> let !m' = UC.delete k m in m'
{-# INLINE hashMap #-}


------------------------------------------------------------------------------
basicHashTable :: (Hashable k, Eq k) => DataStructure (Operation k)
basicHashTable = setupDataIO (IOH.newSized :: Int -> IO (IOH.BasicHashTable k v))
                             f
  where
    f !m !op = case op of
                 (Insert k v) -> IOH.insert m k v >> return m
                 (Lookup k)   -> do
                           !_ <- IOH.lookup m k
                           return m
                 (Delete k)   -> IOH.delete m k >> return m
{-# INLINE basicHashTable #-}


------------------------------------------------------------------------------
cuckooHashTable :: (Hashable k, Eq k) => DataStructure (Operation k)
cuckooHashTable = setupDataIO (IOH.newSized :: Int -> IO (IOH.CuckooHashTable k v))
                             f
  where
    f !m !op = case op of
                 (Insert k v) -> IOH.insert m k v >> return m
                 (Lookup k)   -> do
                           !_ <- IOH.lookup m k
                           return m
                 (Delete k)   -> IOH.delete m k >> return m
{-# INLINE cuckooHashTable #-}


------------------------------------------------------------------------------
linearHashTable :: (Hashable k, Eq k) => DataStructure (Operation k)
linearHashTable = setupDataIO
                    (IOH.newSized :: Int -> IO (IOH.LinearHashTable k v))
                    f
  where
    f !m !op = case op of
                 (Insert k v) -> IOH.insert m k v >> return m
                 (Lookup k)   -> do
                           !_ <- IOH.lookup m k
                           return m
                 (Delete k)   -> IOH.delete m k >> return m
{-# INLINE linearHashTable #-}


------------------------------------------------------------------------------
mkByteString :: GenIO -> IO ByteString
mkByteString rng = do
    n <- uniformR (4,16) rng
    xs <- replicateM n (uniform rng)
    let !s = B.pack xs
    return $! B16.encode s


------------------------------------------------------------------------------
mkConsecutiveIntegers :: IORef Int -> GenIO -> IO Int
mkConsecutiveIntegers ref _ = do
    !i <- atomicModifyIORef ref f
    return $! i
  where
    f !i = let !j = i+1 in (j,j)


------------------------------------------------------------------------------
newtype IntMix = IntMix Int
  deriving (Num, Read, Show, Ord, Eq, NFData)


------------------------------------------------------------------------------
instance Hashable IntMix where
    hash (IntMix a) = hashWithSalt 1102951999 a
    hashWithSalt salt (IntMix a) = hashWithSalt salt a


------------------------------------------------------------------------------
loadConsecutiveIntegersWorkload :: WorkloadGenerator (Operation Int)
loadConsecutiveIntegersWorkload size = do
    ref <- liftIO $ newIORef 0
    loadOnly (mkConsecutiveIntegers ref) size


------------------------------------------------------------------------------
loadConsecutiveIntegersWorkload' :: WorkloadGenerator (Operation IntMix)
loadConsecutiveIntegersWorkload' size = do
    ref <- liftIO $ newIORef 0
    loadOnly (\rng -> IntMix `fmap` mkConsecutiveIntegers ref rng) size


------------------------------------------------------------------------------
testStructures = [ ("Data.Map"             , dataMap        )
                 , ("Data.HashMap"         , hashMap        )
                 , ("Data.BasicHashTable"  , basicHashTable )
                 , ("Data.LinearHashTable" , linearHashTable)
                 , ("Data.CuckooHashTable" , cuckooHashTable)
                 ]

intStructures = [ ("Data.Map"            , dataMap        )
                , ("Data.HashMap"        , hashMap        )
                , ("Data.BasicHashTable" , basicHashTable )
                , ("Data.CuckooHashTable", cuckooHashTable)
                ]

intStructures' = [ ("Data.Map"            , dataMap        )
                 , ("Data.HashMap"        , hashMap        )
                 , ("Data.BasicHashTable" , basicHashTable )
                 , ("Data.CuckooHashTable", cuckooHashTable)
                 ]


------------------------------------------------------------------------------
testSizes :: [Int]
testSizes = [ 250
            , 500
            , 1000
            , 2000
            , 4000
            , 8000
            , 16000
            , 32000
            , 64000
            , 128000
            , 256000
            , 512000
            , 1024000
            , 2048000 ]


------------------------------------------------------------------------------
lookupBenchmark :: Benchmark (Operation ByteString)
lookupBenchmark = Benchmark "Lookup Performance"
                      testStructures
                      testSizes
                      (loadAndUniformLookup mkByteString)


------------------------------------------------------------------------------
insertBenchmark :: Benchmark (Operation ByteString)
insertBenchmark = Benchmark "Insert Performance"
                      testStructures
                      testSizes
                      (loadOnly mkByteString)


------------------------------------------------------------------------------
consecutiveIntBenchmark :: Benchmark (Operation Int)
consecutiveIntBenchmark = Benchmark "Insert consecutive ints"
                              intStructures
                              testSizes
                              loadConsecutiveIntegersWorkload


------------------------------------------------------------------------------
consecutiveIntWithMixBenchmark :: Benchmark (Operation IntMix)
consecutiveIntWithMixBenchmark = Benchmark "Insert consecutive ints (mixed)"
                                     intStructures'
                                     testSizes
                                     loadConsecutiveIntegersWorkload'

------------------------------------------------------------------------------
main :: IO ()
main = do
    args <- getArgs
    let fn = case args of []    -> Nothing
                          (x:_) -> Just (dropExtensions x)

    let cfg = defaultCriterionCollectionConfig
    runBenchmark PerBatch Mutating insertBenchmark cfg (fmap (++".insert") fn)
    runBenchmark PerBatch Pure lookupBenchmark cfg (fmap (++".lookup") fn)
    runBenchmark PerBatch Mutating consecutiveIntBenchmark cfg
                 (fmap (++".int") fn)
    runBenchmark PerBatch Mutating consecutiveIntWithMixBenchmark cfg
                 (fmap (++".intmix") fn)

