    {-# LANGUAGE CPP, OverloadedStrings #-}
    -- |
    -- Module      : Criterion.IO
    -- Copyright   : (c) 2009-2014 Bryan O'Sullivan
    --
    -- License     : BSD-style
    -- Maintainer  : bos@serpentine.com
    -- Stability   : experimental
    -- Portability : GHC
    --
    -- Input and output actions.

    module Criterion.IO
        (
          header
        , hGetReports
        , hPutReports
        , readReports
        , writeReports
        ) where

    import Criterion.Types (Report(..))
    import Data.Binary (Binary(..), encode)
    #if MIN_VERSION_binary(0, 6, 3)
    import Data.Binary.Get (runGetOrFail)
    #else
    import Data.Binary.Get (runGetState)
    #endif
    import Data.Binary.Put (putByteString, putWord16be, runPut)
    import Data.ByteString.Char8 ()
    import Data.Version (Version(..))
    import Paths_criterion (version)
    import System.IO (Handle, IOMode(..), withFile)
    import qualified Data.ByteString.Lazy as L

    -- | The header identifies a criterion data file. This contains
    -- version information; there is no expectation of cross-version
    -- compatibility.
    header :: L.ByteString
    header = runPut $ do
      putByteString "criterio"
      mapM_ (putWord16be . fromIntegral) (versionBranch version)

    -- | Read all reports from the given 'Handle'.
    hGetReports :: Handle -> IO (Either String [Report])
    hGetReports handle = do
      bs <- L.hGet handle (fromIntegral (L.length header))
      if bs == header
        then Right `fmap` readAll handle
        else return $ Left "unexpected header"

    -- | Write reports to the given 'Handle'.
    hPutReports :: Handle -> [Report] -> IO ()
    hPutReports handle rs = do
      L.hPut handle header
      mapM_ (L.hPut handle . encode) rs

    -- | Read all reports from the given file.
    readReports :: FilePath -> IO (Either String [Report])
    readReports path = withFile path ReadMode hGetReports

    -- | Write reports to the given file.
    writeReports :: FilePath -> [Report] -> IO ()
    writeReports path rs = withFile path WriteMode (flip hPutReports rs)

    #if MIN_VERSION_binary(0, 6, 3)
    readAll :: Binary a => Handle -> IO [a]
    readAll handle = do
      let go bs
             | L.null bs = return []
             | otherwise = case runGetOrFail get bs of
                             Left (_, _, err) -> fail err
                             Right (bs', _, a) -> (a:) `fmap` go bs'
      go =<< L.hGetContents handle
    #else
    readAll :: Binary a => Handle -> IO [a]
    readAll handle = do
      let go i bs
             | L.null bs = return []
             | otherwise =
                let (a, bs', i') = runGetState get bs i
                 in (a:) `fmap` go i' bs'
      go 0 =<< L.hGetContents handle
    #endif