{-# LANGUAGE ExistentialQuantification #-}

import Criterion.Main


import Data.Binary
import Data.Binary.Typed
import Data.Typeable
import Control.DeepSeq
import Control.Exception (evaluate)






-- Test values

someInt :: Int
someInt = 12345

someShortString :: String
someShortString = "Hello"

someLongString :: String
someLongString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam\
                 \ vitae lacinia tellus. Maecenas posuere."

someComplicated :: Complicated
someComplicated = Right (Left "Hello")


-- | Data with a normal form.
data NF = forall a. NFData a => NF a

-- | Evaluate 'NF' data to normal form.
force' :: NF -> ()
force' (NF x) = x `deepseq` ()



type Complicated = Either (Char, Int) (Either String (Maybe Integer))



main :: IO ()
main = do
      forceCafs
      defaultMain [ bgroup "Encode" bench_binaryVsTyped ]

-- | List of all data that should be fully evaluated before the benchmark is
--   run.
cafs :: [NF]
cafs =
      [ NF someInt
      , NF someShortString
      , NF someLongString
      , NF defaultInt
      , NF defaultString
      , NF (encode intValUntyped) -- Evaluate the encodings to NF to force a 'Typed'. Hacky but works :-)
      , NF (encode intValHashed)
      , NF (encode intValShown)
      , NF (encode intValFull)
      , NF (encode strSValUntyped)
      , NF (encode strSValHashed)
      , NF (encode strSValShown)
      , NF (encode strSValFull)
      , NF (encode strLValUntyped)
      , NF (encode strLValHashed)
      , NF (encode strLValShown)
      , NF (encode strLValFull)
      ]

forceCafs :: IO ()
forceCafs = mapM_ (evaluate . force') cafs


bench_binaryVsTyped :: [Benchmark]
bench_binaryVsTyped =
      [ bgroup "Int"
            [ bench_int_untyped
            , bgroup "recalculate" bench_int
            , bgroup "precache"    (bench_encode_precached intValUntyped
                                                           intValHashed
                                                           intValShown
                                                           intValFull
                                                           someInt)
            ]
      , bgroup "\"hello\""
            [ bench_short_string_untyped
            , bgroup "recalculate" bench_short_string
            , bgroup "precache"    (bench_encode_precached strSValUntyped
                                                           strSValHashed
                                                           strSValShown
                                                           strSValFull
                                                           someShortString)
            ]
      , bgroup "Lipsum (length 100)"
            [ bench_long_string_untyped
            , bgroup "recalculate" bench_long_string
            , bgroup "precache"    (bench_encode_precached strLValUntyped
                                                           strLValHashed
                                                           strLValShown
                                                           strLValFull
                                                           someLongString)
            ]
      , bgroup "Complicated type"
            [ bench_complicated_untyped
            , bgroup "recalculate" bench_complicated
            , bgroup "precache"    (bench_encode_precached compLValUntyped
                                                           compLValHashed
                                                           compLValShown
                                                           compLValFull
                                                           someComplicated)
            ]
      ]



defaultInt :: Int
defaultInt = 0

defaultString :: String
defaultString = ""

defaultComplicated :: Complicated
defaultComplicated = Left (' ', 0)

intValUntyped, intValHashed, intValShown, intValFull :: Typed Int
intValUntyped = precache (typed  Untyped defaultInt)
intValHashed  = precache (typed  Hashed  defaultInt)
intValShown   = precache (typed  Shown   defaultInt)
intValFull    = precache (typed  Full    defaultInt)

strSValUntyped, strSValHashed, strSValShown, strSValFull :: Typed String
strSValUntyped = precache (typed Untyped defaultString)
strSValHashed  = precache (typed Hashed  defaultString)
strSValShown   = precache (typed Shown   defaultString)
strSValFull    = precache (typed Full    defaultString)

strLValUntyped, strLValHashed, strLValShown, strLValFull :: Typed String
strLValUntyped = precache (typed Untyped defaultString)
strLValHashed  = precache (typed Hashed  defaultString)
strLValShown   = precache (typed Shown   defaultString)
strLValFull    = precache (typed Full    defaultString)

compLValUntyped, compLValHashed, compLValShown, compLValFull :: Typed Complicated
compLValUntyped = precache (typed Untyped defaultComplicated)
compLValHashed  = precache (typed Hashed  defaultComplicated)
compLValShown   = precache (typed Shown   defaultComplicated)
compLValFull    = precache (typed Full    defaultComplicated)



bench_int :: [Benchmark]
bench_int = map (bench_encode someInt) formats

bench_int_untyped :: Benchmark
bench_int_untyped = bench "Binary only" (nf encode someInt)

bench_short_string :: [Benchmark]
bench_short_string = map (bench_encode someShortString) formats

bench_short_string_untyped :: Benchmark
bench_short_string_untyped = bench "Binary only" (nf encode someShortString)

bench_long_string :: [Benchmark]
bench_long_string = map (bench_encode someLongString) formats

bench_long_string_untyped :: Benchmark
bench_long_string_untyped = bench "Binary only" (nf encode someLongString)

bench_complicated :: [Benchmark]
bench_complicated = map (bench_encode someComplicated) formats

bench_complicated_untyped :: Benchmark
bench_complicated_untyped = bench "Binary only" (nf encode someComplicated)

formats :: [TypeFormat]
formats = [ Untyped
          , Hashed
          , Shown
          , Full
          ]

-- | Simply encode a value using the specified 'TypeFormat'.
bench_encode
      :: (Binary a, Typeable a)
      => a
      -> TypeFormat
      -> Benchmark
bench_encode x format = bench description (nf (encodeTyped format) x)
      where description = "Typed: " ++ show format



-- | Encode a value using a precached 'Typed' value.
bench_encode_precached
      :: (Binary a, Typeable a)
      => Typed a -- ^ Precached 'Untyped' dummy value
      -> Typed a -- ^ dito, with 'Hashed'
      -> Typed a -- ^ dito, with 'Shown'
      -> Typed a -- ^ dito, with 'Full'
      -> a       -- ^ Actual value to encode
      -> [Benchmark]
bench_encode_precached untyped hashed shown full x =
      [ bench (description Untyped) (nf (encodeTypedLike untyped) x)
      , bench (description Hashed)  (nf (encodeTypedLike hashed ) x)
      , bench (description Shown)   (nf (encodeTypedLike shown  ) x)
      , bench (description Full)    (nf (encodeTypedLike full   ) x)
      ]
      where description format = "Typed: " ++ show format