{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE DeriveGeneric    #-}
{-# LANGUAGE NamedFieldPuns   #-}
{-# LANGUAGE RankNTypes       #-}
{-# LANGUAGE RecordWildCards  #-}
{-# LANGUAGE TypeApplications #-}

module Main (main) where

import Control.DeepSeq
import Control.Lens.Operators
import Control.Lens.Type
import Control.Monad
import Criterion.Main
import Data.Generics.Product
import GHC.Generics
import Test.QuickCheck

main :: IO ()
main = defaultMain
  [ env (arbitraryAnimalsOfLength 100) $ products 100
  , env (arbitraryAnimalsOfLength 1000) $ products 1000
  , env (arbitraryAnimalsOfLength 10000) $ products 10000
  , env (arbitraryAnimalsOfLength 100000) $ products 100000
  ]

products :: Int -> [Animal] -> Benchmark
products n as
  = bgroup ("products/" ++ show n)
      [ bench "generic-lens/get" (nf (const $ map (^. field @"name") as) ())
      , bench "lens/get" (nf (const $ map (^. aName) as) ())
      , bench "generic-lens/set" (nf (const $ map (\a -> a & field @"name" .~ "Name") as) ())
      , bench "lens/set" (nf (const $ map (\a -> a & aName .~ "Name") as) ())
      ]

arbitraryAnimalsOfLength :: Int -> IO [Animal]
arbitraryAnimalsOfLength n
  = replicateM n (generate arbitrary)

data Animal = Animal
  { name :: String
  , age  :: Int
  , eats :: String
  } deriving (Generic, Show)

instance Arbitrary Animal where
  arbitrary = Animal <$> arbitrary <*> arbitrary <*> arbitrary

instance NFData Animal where
  rnf Animal{..} = rnf name `seq` rnf age `seq` rnf eats

aName :: Lens' Animal String
aName f Animal{..}
  = (\x -> Animal { name = x, age, eats }) <$> f name

{-
aAge :: Lens' Animal Int
aAge f Animal{..}
  = (\x -> Animal { name, age = x, eats }) <$> f age

aEats :: Lens' Animal String
aEats f Animal{..}
  = (\x -> Animal { name, age, eats = x }) <$> f eats
  -}