module Macro
  ( benchmarks -- :: [Benchmark]
  ) where
import           Data.Int

import           Criterion.Main
import           Control.DeepSeq
import qualified Data.ByteString        as B
import qualified Data.ByteString.Lazy   as BS
import qualified Codec.Compression.GZip as GZip

import qualified Macro.Types     as Types
import qualified Macro.MemSize
import           Macro.DeepSeq ()
import qualified Macro.Load as Load

import qualified Macro.ReadShow  as ReadShow
import qualified Macro.PkgBinary as PkgBinary
import qualified Macro.PkgCereal as PkgCereal
import qualified Macro.PkgAesonGeneric as PkgAesonGeneric
import qualified Macro.PkgAesonTH as PkgAesonTH
import qualified Macro.PkgStore as PkgStore

import qualified Macro.CBOR as CBOR

readBigTestData :: IO [Types.GenericPackageDescription]
readBigTestData = do
    Right pkgs_ <- fmap (Load.readPkgIndex . GZip.decompress)
                        (BS.readFile "bench/data/01-index.tar.gz")
    let tstdata  = take 100 pkgs_
    return tstdata

benchmarks :: [Benchmark]
benchmarks =
  [ env readBigTestData $ \tstdata ->
    bgroup "reference"
      [ bench "deepseq" (whnf rnf tstdata)
      , bench "memSize" (whnf (flip Macro.MemSize.memSize 0) tstdata)
      ]

  , env readBigTestData $ \tstdata ->
    bgroup "encoding"
      [ bench "binary"        (whnf perfEncodeBinary       tstdata)
      , bench "cereal"        (whnf perfEncodeCereal       tstdata)
      , bench "aeson generic" (whnf perfEncodeAesonGeneric tstdata)
      , bench "aeson TH"      (whnf perfEncodeAesonTH      tstdata)
      , bench "read/show"     (whnf perfEncodeReadShow     tstdata)
      , bench "cbor"          (whnf perfEncodeCBOR         tstdata)
      , bench "store"         (whnf perfEncodeStore        tstdata)
      ]

  , env readBigTestData $ \tstdata ->
    bgroup "decoding whnf"
      [ env (return $ combineChunks $ PkgBinary.serialise tstdata)
        $ \tstdataB -> bench "binary" (whnf perfDecodeBinary tstdataB)
      , env (return $ combineChunks $ PkgCereal.serialise tstdata)
        $ \tstdataC -> bench "cereal"  (whnf perfDecodeCereal tstdataC)
      , env (return $ combineChunks $ PkgAesonTH.serialise tstdata)
        $ \tstdataA -> bgroup "aeson"
            [ bench "generic"   (whnf perfDecodeAesonGeneric tstdataA)
            , bench "TH"        (whnf perfDecodeAesonTH      tstdataA)
            ]
      , env (return $ combineChunks $ ReadShow.serialise tstdata)
        $ \tstdataS -> bench "read/show" (whnf perfDecodeReadShow tstdataS)
      , env (return $ PkgStore.serialise tstdata)
        $ \tstdataR -> bench "store" (whnf perfDecodeStore tstdataR)
      , env (return $ combineChunks $ CBOR.serialise tstdata)
        $ \tstdataR -> bench "cbor" (whnf perfDecodeCBOR tstdataR)
      ]

  , env readBigTestData $ \tstdata ->
    bgroup "decoding nf"
      [ env (return $ combineChunks $ PkgBinary.serialise tstdata)
      $ \tstdataB -> bench "binary" (nf perfDecodeBinary tstdataB)
      , env (return $ combineChunks $ PkgCereal.serialise tstdata)
      $ \tstdataC -> bench "cereal" (nf perfDecodeCereal tstdataC)
      , env (return $ combineChunks $ PkgAesonTH.serialise tstdata)
      $ \tstdataA -> bgroup "aeson"
          [ bench "generic"   (nf perfDecodeAesonGeneric tstdataA)
          , bench "TH"        (nf perfDecodeAesonTH      tstdataA)
          ]

      , env (return $ combineChunks $ ReadShow.serialise tstdata)
      $ \tstdataS -> bench "read/show" (nf perfDecodeReadShow tstdataS)
      , env (return $ PkgStore.serialise tstdata)
      $ \tstdataR -> bench "store" (nf perfDecodeStore tstdataR)
      , env (return $ combineChunks $ CBOR.serialise tstdata)
      $ \tstdataR -> bench "cbor" (nf perfDecodeCBOR tstdataR)
      ]
  ]
  where
    perfEncodeBinary, perfEncodeCereal, perfEncodeAesonGeneric,
      perfEncodeAesonTH, perfEncodeReadShow,
      perfEncodeCBOR
      :: [Types.GenericPackageDescription] -> Int64


    perfEncodeBinary       = BS.length . PkgBinary.serialise
    perfEncodeCereal       = BS.length . PkgCereal.serialise
    perfEncodeAesonGeneric = BS.length . PkgAesonGeneric.serialise
    perfEncodeAesonTH      = BS.length . PkgAesonTH.serialise
    perfEncodeReadShow     = BS.length . ReadShow.serialise
    perfEncodeCBOR         = BS.length . CBOR.serialise

    perfDecodeBinary, perfDecodeCereal, perfDecodeAesonGeneric,
      perfDecodeAesonTH, perfDecodeReadShow,
      perfDecodeCBOR
      :: BS.ByteString -> [Types.GenericPackageDescription]

    perfDecodeBinary       = PkgBinary.deserialise
    perfDecodeCereal       = PkgCereal.deserialise
    perfDecodeAesonGeneric = PkgAesonGeneric.deserialise
    perfDecodeAesonTH      = PkgAesonTH.deserialise
    perfDecodeReadShow     = ReadShow.deserialise
    perfDecodeCBOR        = CBOR.deserialise

    perfDecodeStore :: B.ByteString -> [Types.GenericPackageDescription]
    perfDecodeStore = PkgStore.deserialise
    perfEncodeStore :: [Types.GenericPackageDescription] -> Int
    perfEncodeStore = B.length . PkgStore.serialise

    -- Convert any lazy ByteString to ByteString lazy bytestring
    -- that have only single chunk.
    combineChunks :: BS.ByteString -> BS.ByteString
    combineChunks = BS.fromStrict . BS.toStrict