{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}

module Memoize
  ( memoizeBenchmark
  ) where

import Data.Bits
import Data.Chimera
import Data.Foldable
import Data.Function
import qualified Data.Vector.Unboxed as U
import Test.Tasty.Bench

memoizeBenchmark :: Benchmark
memoizeBenchmark = bgroup "memoize"
  [ bgroup "memoizeFix" $ memoizeFixBenchmark memoizeFix
  , bgroup "memoizeFix unboxed" $ memoizeFixBenchmark (index . tabulateFix @U.Vector)
  , bgroup "fix memoize" $ memoizeFixBenchmark (fix . (memoize .))
  ]

memoizeFixBenchmark :: (forall a. U.Unbox a => ((Word -> a) -> Word -> a) -> Word -> a) -> [Benchmark]
memoizeFixBenchmark fixer =
  [ bench "isOdd" $ nf (\f -> let isOdd = fixer f in
      foldl' (\acc n -> xor acc (isOdd n)) False [0..10000]) isOddF
  , bench "isPrime" $ nf (\f -> let isPrime = fixer f in
      foldl' (\acc n -> xor acc (isPrime n)) False [0..10000]) isPrimeF
  , bench "fibo" $ nf (\f -> let fibo = fixer f in
      foldl' (\acc n -> acc + fibo n) 0 [0..10000]) fiboF
  , bench "collatz" $ nf (\f -> let collatz = fixer f in
      foldl' (\acc n -> acc + collatz n) 0 [0..1000]) collatzF
  ]

isOddF :: (Word -> Bool) -> Word -> Bool
isOddF f n = n /= 0 && not (f (n - 1))

isPrimeF :: (Word -> Bool) -> Word -> Bool
isPrimeF f n = n > 1 && and [ n `rem` d /= 0 | d <- [2 .. floor (sqrt (fromIntegral n :: Double))], f d]

fiboF :: (Word -> Word) -> Word -> Word
fiboF f n = if n < 2 then fromIntegral n else f (n - 1) + f (n - 2)

collatzF :: (Word -> Word) -> Word -> Word
collatzF f n = if n <= 1 then 0 else 1 + f (if even n then n `quot` 2 else 3 * n + 1)
