{-|
Module      : Crypto.Lol.Benchmarks
Description : Infrastructure for benchmarking Lol.
Copyright   : (c) Eric Crockett, 2011-2017
                  Chris Peikert, 2011-2017
License     : GPL-2
Maintainer  : ecrockett0@email.com
Stability   : experimental
Portability : POSIX

Infrastructure for benchmarking Lol.
-}

{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds             #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeFamilies          #-}

module Crypto.Lol.Benchmarks
(Crypto.Lol.Benchmarks.bench
,benchM
,benchIO
,benchGroup
,genBenchArgs
,Bench
,Benchmark
,NFData
,addGen) where

import Control.DeepSeq
import Control.Monad.Random
import Criterion as C
import Crypto.Lol.Utils.GenArgs
import Data.Proxy

-- | Convenience function for benchmarks with an extra parameter.
addGen :: Proxy gen -> Proxy '(t,m,r) -> Proxy '(t,m,r,gen)
addGen _ _ = Proxy

-- | Wrapper for criterion's 'nf'
{-# INLINABLE bench #-}
bench :: NFData b => (a -> b) -> a -> Bench params
bench f = Bench . nf f

-- | Use when you need randomness /outside/ the benchmark.
benchM :: (forall m . (MonadRandom m) => m (Bench a)) -> Bench a
benchM = BenchM

-- | Wrapper for criterion's 'nfIO'. Use when there is randomness /inside/ the
-- benchmark.
benchIO :: NFData b => IO b -> Bench params
benchIO = Bench . nfIO

{-# INLINABLE benchGroup #-}
-- | Wrapper for criterion's 'bgroup'
benchGroup :: (Monad rnd) => String -> [rnd Benchmark] -> rnd Benchmark
benchGroup str = (bgroup str <$>) . sequence

-- | Converts a function mapping zero or more arguments to a 'Bench' @a@
-- by generating random inputs to the function
genBenchArgs :: (GenArgs bnch, ResultOf bnch ~ Bench a, MonadRandom rnd)
  => String -> bnch -> Proxy a -> rnd Benchmark
genBenchArgs s f _ = (C.bench s . unbench) <$> genArgs f

unbench :: Bench a -> Benchmarkable
unbench (Bench x) = x
unbench (BenchM _) = error "cannot unbench BenchM"

-- | Wrapper around criterion's 'Benchmarkable', with phantom parameters.
data Bench params where
  Bench :: Benchmarkable -> Bench a
  BenchM :: (forall m . (MonadRandom m) => m (Bench a)) -> Bench a

instance GenArgs (Bench params) where
  genArgs x@(Bench _) = return x
  genArgs (BenchM x) = x