{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}

-- | Example uses of Weigh which should work.

module Main where

import Control.DeepSeq
import Weigh
import GHC.Generics

import Data.List (nub)

-- | Weigh integers.
main :: IO ()
main =
  mainWith
    (do wgroup "Integers" integers
        wgroup "IO actions" ioactions
        wgroup "Ints" ints
        wgroup "Structs" struct
        wgroup "Packing" packing
        wgroup "fst" aFuncNamedId
        wgroup "snd" anotherFuncNamedId
    )

-- | Weigh IO actions.
ioactions :: Weigh ()
ioactions =
  do action "integers count IO CAF 0" (return (count 0))
     io "integers count IO func 0" (return . count) 0
     action "integers count IO CAF 1" (return (count 1))
     io "integers count IO func 1" (return . count) 1
  where count :: Integer -> ()
        count 0 = ()
        count a = count (a - 1)

-- | Just counting integers.
integers :: Weigh ()
integers = do
  func "integers count 0" count 0
  func "integers count 1" count 1
  func "integers count 2" count 2
  func "integers count 3" count 3
  func "integers count 10" count 10
  func "integers count 100" count 100
  where
    count :: Integer -> ()
    count 0 = ()
    count a = count (a - 1)

-- | We count ints and ensure that the allocations are optimized away
-- to only two 64-bit Ints (16 bytes).
ints :: Weigh ()
ints =
  do validateFunc "ints count 1" count 1 (maxAllocs 0)
     validateFunc "ints count 10" count 10 (maxAllocs 0)
     validateFunc "ints count 1000000" count 1000000 (maxAllocs 0)
  where count :: Int -> ()
        count 0 = ()
        count a = count (a - 1)

-- | Some simple data structure of two ints.
data IntegerStruct = IntegerStruct !Integer !Integer
  deriving (Generic)
instance NFData IntegerStruct

-- | Weigh allocating a user-defined structure.
struct :: Weigh ()
struct =
  do func "\\_ -> IntegerStruct 0 0" (\_ -> IntegerStruct 0 0) (5 :: Integer)
     func "\\x -> IntegerStruct x 0" (\x -> IntegerStruct x 0) 5
     func "\\x -> IntegerStruct x x" (\x -> IntegerStruct x x) 5
     func "\\x -> IntegerStruct (x+1) x" (\x -> IntegerStruct (x+1) x) 5
     func "\\x -> IntegerStruct (x+1) (x+1)" (\x -> IntegerStruct (x+1) (x+1)) 5
     func "\\x -> IntegerStruct (x+1) (x+2)" (\x -> IntegerStruct (x+1) (x+2)) 5

-- | A simple structure with an Int in it.
data HasInt = HasInt !Int
  deriving (Generic)
instance NFData HasInt

-- | A simple structure with an Int in it.
data HasPacked = HasPacked HasInt
  deriving (Generic)
instance NFData HasPacked

-- | A simple structure with an Int in it.
data HasUnpacked = HasUnpacked {-# UNPACK #-} !HasInt
  deriving (Generic)
instance NFData HasUnpacked

-- | Weigh: packing vs no packing.
packing :: Weigh ()
packing =
  do func "\\x -> HasInt x" (\x -> HasInt x) 5
     func "\\x -> HasUnpacked (HasInt x)" (\x -> HasUnpacked (HasInt x)) 5
     func "\\x -> HasPacked (HasInt x)" (\x -> HasPacked (HasInt x)) 5

aFuncNamedId :: Weigh ()
aFuncNamedId = func "id" id (1::Int)

anotherFuncNamedId :: Weigh ()
anotherFuncNamedId = func "id" nub ([1,2,3,4,5,1]::[Int])
