content: {-# LANGUAGE BangPatterns               #-}
  {-# LANGUAGE ExistentialQuantification  #-}
  {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  
  module Criterion.Collection.Internal.Types
    ( Workload(..)
    , WorkloadGenerator
    , WorkloadMonad(..)
    , runWorkloadMonad
    , getRNG
    , DataStructure(..)
    , setupData
    , setupDataIO
    ) where
  
  ------------------------------------------------------------------------------
  import           Control.DeepSeq
  import           Control.Monad.Reader
  import           Data.Vector (Vector)
  import           System.Random.MWC
  
  ------------------------------------------------------------------------------
  -- Some thoughts on benchmarking modes
  --
  -- * pre-fill data structure, test an operation workload without modifying the
  --   data structure, measure time for each operation
  --
  --   ---> allows you to get fine-grained per-operation times with distributions
  --
  -- * pre-fill data structure, get a bunch of work to do (cumulatively modifying
  --   the data structure), measure time per-operation OR for the whole batch and
  --   divide out
  --
  --
  -- Maybe it will look like this?
  -- > data MeasurementMode = PerBatch | PerOperation
  -- > data WorkloadMode = Pure | Mutating
  
  ------------------------------------------------------------------------------
  newtype WorkloadMonad a = WM (ReaderT GenIO IO a)
    deriving (Functor, Applicative, Monad, MonadIO)
  
  
  ------------------------------------------------------------------------------
  runWorkloadMonad :: WorkloadMonad a -> GenIO -> IO a
  runWorkloadMonad (WM m) gen = runReaderT m gen
  
  
  ------------------------------------------------------------------------------
  getRNG :: WorkloadMonad GenIO
  getRNG = WM ask
  
  
  ------------------------------------------------------------------------------
  -- | Given an 'Int' representing \"input size\", a 'WorkloadGenerator' makes a
  -- 'Workload'. @Workload@s generate operations to prepopulate data structures
  -- with /O(n)/ data items, then generate operations on-demand to benchmark your
  -- data structure according to some interesting distribution.
  type WorkloadGenerator op = Int -> WorkloadMonad (Workload op)
  
  
  ------------------------------------------------------------------------------
  data Workload op = Workload {
        -- | \"Setup work\" is work that you do to prepopulate a data structure
        -- to a certain size before testing begins.
        setupWork             :: !(Vector op)
  
        -- | Given the number of operations to produce, 'genWorkload' spits out a
        -- randomly-distributed workload simulation to be used in the benchmark.
        --
        -- | Some kinds of skewed workload distributions (the canonical example
        -- being \"frequent lookups for a small set of keys and infrequent
        -- lookups for the others\") need a certain minimum number of operations
        -- to be generated to be statistically valid, which only the
        -- 'WorkloadGenerator' would know how to decide. In these cases, you are
        -- free to return more than @N@ samples from 'genWorkload', and
        -- @criterion-collection@ will run them all for you.
        --
        -- Otherwise, @criterion-collection@ is free to bootstrap your benchmark
        -- using as many sample points as it would take to make the results
        -- statistically relevant.
      , genWorkload           :: !(Int -> WorkloadMonad (Vector op))
  }
  
  
  ------------------------------------------------------------------------------
  data DataStructure op = forall m . DataStructure {
        emptyData    :: !(Int -> IO m)
      , runOperation :: !(m -> op -> IO m)
  }
  
  
  ------------------------------------------------------------------------------
  setupData :: m -> (m -> op -> m) -> DataStructure op
  setupData e r = DataStructure (const $ return e) (\m o -> return $ r m o)
  
  
  ------------------------------------------------------------------------------
  setupDataIO :: (Int -> IO m) -> (m -> op -> IO m) -> DataStructure op
  setupDataIO = DataStructure
  