{-# OPTIONS_GHC -cpp -fglasgow-exts -fallow-overlapping-instances #-}
import Control.Concurrent
import Control.Exception
import Control.Monad
import Data.List
import Data.Word
import Foreign.C.String
import Foreign.Marshal.Alloc
import Foreign.Storable
import Numeric
import System.CPUTime
import System.Directory
import System.Environment
import System.Exit
import System.IO
import System.Mem
import System.Time

import Data.Array.Base
import Data.Array.IO
import Data.Array.Unboxed
import Binary

size  = 10^8 :: Int    -- whole size of data processed in each test
chunk = 10^4 :: Int    -- size of chunk what is read/written in each operation
type Element = Word32  -- type of elements used for testing

-- Automatically calculated
bufsize = fromIntegral (size+times*10)              -- size of buffer allocated
times = size `div` chunk                            -- count of repetitions
elements = chunk `div` sizeOf (undefined::Element)  -- number of elements in array/list
tempname = "BinaryBenchmark.tmp"                    -- name of temporary file used in test

main = do
    putStrLn ("Binary libraries benchmark."++
              " Using "++showM size++" of data in chunks of "++showM chunk)
    try (removeFile tempname)

    flip finally (try (removeFile tempname)) $ do
    test "GHC 6.5 Binary Memory" $ openBinMem bufsize
    test "GHC 6.5 Binary File"   $ (openBinaryFile tempname ReadWriteMode >>= openBinIO )

{-# INLINE test #-}
test name open = do
    putStrLn ""
    putStrLn (name++":")
    h <- open
    o <- tellBin h

    let arr = array (0,elements-1) [] :: UArray Int Element
    return $! (arr!0 )  -- to make the following timing accurate
    benchmark h "Writing UArray" times (putUArray h arr)
    performGC
    seekBin h o
    benchmark h "Reading UArray" times (getUArray h :: IO (IOUArray Int Element))
    performGC

    let list = replicate elements (0::Element)
    return $! (length list)  -- to make the following timing accurate
    seekBin h o
    benchmark h "Writing list" times (putNListJhc h list)
    performGC
    seekBin h o
    benchmark h "Reading list" times (getNListJhc h :: IO [Element])
    performGC


{-# INLINE putNListJhc #-}
-- Put list
putNListJhc :: Binary a => BinHandle -> [a] -> IO ()
putNListJhc bh xs = do
    put_ bh (length xs)
    mapM_ (put_ bh) xs

{-# INLINE getNListJhc #-}
-- Get list
getNListJhc :: Binary a => BinHandle -> IO [a]
getNListJhc bh = do
    n <- get bh
    sequence $ replicate n (get bh)

{-# INLINE putUArray #-}
-- Put UArray
putUArray h arr = do put_ h (bounds arr)
                     mapRange 0 (rangeSize(bounds arr)-1)
                         (put_ h . unsafeAt arr)

{-# INLINE getUArray #-}
-- Get UArray
getUArray h = do bounds <- get h
                 arr <- newArray_ bounds :: IO (IOUArray Int Element)
                 mapRange 0 (rangeSize(bounds)-1)
                     (\i -> get h >>= unsafeWrite arr i)
                 return arr

{-# INLINE mapRange #-}
-- Faster equivalent of "mapM_ action [from..to]"
mapRange from to action  = go from
  where
    go i | i>to      = return ()
         | otherwise = do action i
                          go $! (i+1)


{-# INLINE benchmark #-}
benchmark h str times action = do
  handle (\_ -> putStrLn$ str ++ ": failed!") $ do
      prev <- getCPUTime
      prev2 <- getClockTime
      loop times action
      current <- getCPUTime
      current2 <- getClockTime
      let secs = fromIntegral (current-prev) / 1e12
          secs2 = diffTimes current2 prev2
      putStrLn$ str ++ ": " ++ showTime secs2 ++ " (user: " ++ showTime secs ++ ")"

showTime secs  =  showFFloat (Just 3) secs " secs"

diffTimes (TOD sa pa) (TOD sb pb)  =  i(sa - sb) + (i(pa-pb) / 1e12)

i x = fromIntegral x

{-# INLINE loop #-}
loop n action  = go n
  where
    go n | n `seq` False = undefined
    go 0 = return ()
    go n = do action
              go (n-1)

showM x | x `mod` 10^9 == 0   = show (x `div` 10^9) ++ "gb"
        | x `mod` 10^6 == 0   = show (x `div` 10^6) ++ "mb"
        | x `mod` 10^3 == 0   = show (x `div` 10^3) ++ "kb"
        | otherwise           = show x              ++ " bytes"

