{-# LANGUAGE CPP #-}

-- | Measure CPU time for individual phases of the Agda pipeline.

module Agda.TypeChecking.Monad.Benchmark
  ( module Agda.TypeChecking.Monad.Base.Benchmark
  , getBenchmark
  , benchmarking, reportBenchmarkingLn, reportBenchmarkingDoc
  , billTo, billTop, billPureTo, billSub
  , reimburse, reimburseTop
  ) where

import qualified Control.Exception as E (evaluate)
import Control.Monad.State

import Agda.TypeChecking.Monad.Base.Benchmark
import Agda.TypeChecking.Monad.Base
import{-# SOURCE #-} Agda.TypeChecking.Monad.Options
import Agda.TypeChecking.Monad.State

import Agda.Utils.Monad
import Agda.Utils.Pretty (Doc)
import Agda.Utils.Time

#include "undefined.h"
import Agda.Utils.Impossible

-- | Check whether benchmarking is activated.
{-# SPECIALIZE benchmarking :: TCM Bool #-}
benchmarking :: MonadTCM tcm => tcm Bool
benchmarking = liftTCM $ hasVerbosity "profile" 7

-- | Report benchmarking results.
reportBenchmarkingLn :: String -> TCM ()
reportBenchmarkingLn = reportSLn "profile" 7

-- | Report benchmarking results.
reportBenchmarkingDoc :: TCM Doc -> TCM ()
reportBenchmarkingDoc = reportSDoc "profile" 7

-- | Bill a computation to a specific account (True) or reimburse (False).
billTo' :: MonadTCM tcm => Bool -> Account -> tcm a -> tcm a
billTo' add k m = ifNotM benchmarking m {- else -} $ do
  (res, time) <- measureTime $ liftIO . E.evaluate =<< m
  addToAccount k $ if add then time else -time
  return res

-- | Bill a computation to a specific account.
billTo :: MonadTCM tcm => Account -> tcm a -> tcm a
billTo = billTo' True

-- | Bill a top account.
billTop ::  MonadTCM tcm => Phase -> tcm a -> tcm a
billTop k = billTo [k]

-- | Bill a sub account.
billSub ::  MonadTCM tcm => Account -> tcm a -> tcm a
billSub [] = __IMPOSSIBLE__
billSub k  = reimburse (init k) . billTo k

-- | Bill a pure computation to a specific account.
{-# SPECIALIZE billPureTo :: Account -> a -> TCM a #-}
billPureTo :: MonadTCM tcm => Account -> a -> tcm a
billPureTo k a = liftTCM $ billTo k $ return a
-- billPureTo k a = liftTCM $ billTo k $ liftIO $ E.evaluate a

-- | Reimburse a specific account for computation costs.
reimburse ::  MonadTCM tcm => Account -> tcm a -> tcm a
reimburse = billTo' False

-- | Reimburse a top account.
reimburseTop ::  MonadTCM tcm => Phase -> tcm a -> tcm a
reimburseTop k = reimburse [k]

-- * Auxiliary functions

-- | Add CPU time to specified account.
addToAccount ::  MonadTCM tcm => Account -> CPUTime -> tcm ()
addToAccount k v = liftTCM $ modifyBenchmark $ addCPUTime k v