{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where

import Criterion.Main

import Crypto.Random
import Crypto.PubKey.ML_KEM

import Data.ByteArray (Bytes)
import Data.Proxy

data KEM = forall a . ParamSet a => KEM (Proxy a)

kems :: [(String, KEM)]
kems =
    [ ("ML-KEM-512",  KEM (Proxy :: Proxy ML_KEM_512))
    , ("ML-KEM-768",  KEM (Proxy :: Proxy ML_KEM_768))
    , ("ML-KEM-1024", KEM (Proxy :: Proxy ML_KEM_1024))
    ]

doBench :: (String, KEM) -> Benchmark
doBench (name, KEM p) = bgroup name
    [ bench "generate" $ perRunEnv setupGenerate (return . runGenerate)
    , bench "encapsulate" $ perRunEnv setupEncap (return . runEncap)
    , bench "encapsulate (batch)" $ perBatchEnv (const setupEncap) (return . runEncap)
    , bench "decapsulate" $ perRunEnv setupDecap (return . runDecap)
    , bench "decapsulate (batch)" $ perBatchEnv (const setupDecap) (return . runDecap)
    , env (generate p) $ bench "encoding (public)" . nf (encodeDecode p) . fst
    , env (generate p) $ bench "encoding (private)" . nf (encodeDecode p) . snd
    ]
  where
    gen32 = getRandomBytes 32 :: IO Bytes

    setupGenerate = (,) <$> gen32 <*> gen32
    runGenerate = uncurry (generateWith p)

    setupEncap = do
        (ek, _) <- generate p
        m <- gen32
        return (ek, m)
    runEncap (ek, m) = encapsulateWith ek m

    runDecap (dk, c) = decapsulate dk c
    setupDecap = do
        (ek, dk) <- generate p
        (_, c) <- encapsulate ek
        return (dk, c)

    encodeDecode :: (Encode obj, Decode obj, Eq (obj a), ParamSet a)
                 => proxy a -> obj a -> Bool
    encodeDecode prx k = decode prx (aligned $ encode k) == Just k

    aligned :: Bytes -> Bytes
    aligned = id

main :: IO ()
main = defaultMain
    [ bgroup "mlkem" $ map doBench kems
    ]
