{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE NamedFieldPuns #-}

 -- |
 -- Measure data and IO evaluation time in a lightweight manner.
 --
 -- A 'Chronograph a' has two parts, the value 'a' and the measurement of
 -- evaluation time.  A Chronograph is lazy, so 'a' is only evaluted on demand.
 --
 -- This example counts the lines in a number of files, and records the
 -- evaluation time taken for each one.
 --
 -- >  import System.Environment
 -- >  import Control.Applicative
 -- >  import Data.Chronograph
 -- >
 -- >  import Text.Printf
 -- >
 -- >  formatOutput :: FilePath -> Chronograph Int -> String
 -- >  formatOutput fp chr = printf "%s :: %d, %s" fp (val chr) (show $ measure chr)
 -- >
 -- >  procFile :: FilePath -> IO ()
 -- >  procFile fp = do
 -- >      doc <- readFile fp
 -- >      let wc = length $ lines doc
 -- >      putStrLn $ formatOutput fp (chrono wc)
 --
 -- 'chrono' creates a chronograph that evaluates its input as far as 'seq' would.
 -- In this case the input 'wc' is an Int, so 'chrono' fully evaluates it.
 -- deepseq-style evaluation is performed by 'chronoNF', and custom evaluation
 -- strategies can be implemented with 'chronoBy'.
 --
 -- although 'wc' is a pure value, IO is lazily performed in its evalution.
 -- This IO cost is included in 'chrono's measurement.
 --
 -- You can explicitly include timings of IO actions as well:
 --
 -- >  fileLinesIO :: FilePath -> IO Int
 -- >  fileLinesIO fp = length . lines <$> readFile fp
 -- >
 -- >  procIO :: FilePath -> IO ()
 -- >  procIO fp = do
 -- >    wc <- chronoIO $ fileLinesIO fp
 -- >    putStrLn $ formatOutput fp wc
 --
 -- >  main :: IO ()
 -- >  main = do
 -- >      args <- getArgs
 -- >      putStrLn "pure Chronograph"
 -- >      mapM_ procFile args
 -- >      putStrLn "IO Chronograph"
 -- >      mapM_ procIO args
 --
module Data.Chronograph (
  Chronograph (..)
-- * chrono pure stuff
, chrono
, chronoNF
, chronoBy
-- * chrono IO stuff
, chronoJustIO
, chronoIO
, chronoNFIO
, chronoIOBy
) where

import Control.DeepSeq
import Data.Time (NominalDiffTime, getCurrentTime, diffUTCTime)
import GHC.Generics
import System.IO.Unsafe (unsafePerformIO)

data Chronograph a = Chronograph
    { val :: a
    , measure :: NominalDiffTime
    } deriving (Show, Generic)

----------------------------------------------------------
-- chrono pure functions

-- | Add a 'Chronograph' to measure evaluation to weak head normal form.
chrono :: a -> Chronograph a
chrono = chronoBy (`seq` ())

-- | Add a 'Chronograph' to measure evaluation to normal form.
chronoNF :: NFData a => a -> Chronograph a
chronoNF = chronoBy rnf

-- | Add a 'Chronograph' to measure evalution time with the provided strategy.
chronoBy :: (a -> ()) -> a -> Chronograph a
chronoBy eval x =
    let measure = unsafePerformIO $ do
                    t0 <- getCurrentTime
                    t1 <- eval x `seq` getCurrentTime
                    return (t1 `diffUTCTime` t0)
    in Chronograph {val = measure `seq` x, measure }

----------------------------------------------------------
-- chrono IO computations

-- | Add a 'Chronograph' to measure IO time (no additional evaluation is
-- performed, although the IO action itself may perform some evaluation)
chronoJustIO :: IO a -> IO (Chronograph a)
chronoJustIO = chronoIOBy (const ())

-- | Add a 'Chronograph' to measure time of IO and evaluation to weak head
-- normal form.
chronoIO :: IO a -> IO (Chronograph a)
chronoIO = chronoIOBy (`seq` ())

-- | Add a 'Chronograph' to measure time of IO and evaluation to normal form.
chronoNFIO :: NFData a => IO a -> IO (Chronograph a)
chronoNFIO = chronoIOBy rnf

-- | Add a 'Chronograph' to measure time of IO and evaluation with the
-- provided strategy.
chronoIOBy :: (a -> ()) -> IO a -> IO (Chronograph a)
chronoIOBy eval mx = do
    t0 <- getCurrentTime
    val  <- mx
    t1 <- eval val `seq` getCurrentTime
    let measure = t1 `diffUTCTime` t0

    return $ Chronograph {val, measure}