module General.Timing(resetTimings, addTiming, getTimings) where

import Data.IORef.Extra
import System.IO.Unsafe
import Data.Tuple.Extra
import Data.List.Extra
import Numeric.Extra
import General.Extra
import System.Time.Extra


{-# NOINLINE timer #-}
timer :: IO Seconds
timer = unsafePerformIO offsetTime


{-# NOINLINE timings #-}
timings :: IORef [(Seconds, String)] -- number of times called, newest first
timings = unsafePerformIO $ newIORef []


resetTimings :: IO ()
resetTimings = do
    now <- timer
    writeIORef timings [(now, "Start")]


-- | Print all withTiming information and clear it.
getTimings :: IO [String]
getTimings = do
    now <- timer
    old <- atomicModifyIORef timings dupe
    pure $ showTimings now $ reverse old


addTiming :: String -> IO ()
addTiming msg = do
    now <- timer
    atomicModifyIORef_ timings ((now,msg):)


showTimings :: Seconds -> [(Seconds, String)] -> [String]
showTimings _ [] = []
showTimings stop times = showGap $
    [(a ++ "  ", showDP 3 b ++ "s  " ++ showPerc b ++ "  " ++ progress b) | (a,b) <- xs] ++
    [("Total", showDP 3 sm ++ "s  " ++ showPerc sm ++ "  " ++ replicate 25 ' ')]
    where
        a // b = if b == 0 then 0 else a / b
        showPerc x = let s = show $ floor $ x * 100 // sm in replicate (3 - length s) ' ' ++ s ++ "%"
        progress x = let i = floor $ x * 25 // mx in replicate i '=' ++ replicate (25-i) ' '
        mx = maximum $ map snd xs
        sm = sum $ map snd xs
        xs = [ (name, stop - start)
             | ((start, name), stop) <- zipExact times $ map fst (drop1 times) ++ [stop]]


showGap :: [(String,String)] -> [String]
showGap xs = [a ++ replicate (n - length a - length b) ' ' ++ b | (a,b) <- xs]
    where n = maximum [length a + length b | (a,b) <- xs]