{-# LANGUAGE GeneralizedNewtypeDeriving, DeriveFunctor
    , MultiParamTypeClasses #-}


import System.IO
import qualified Streamly as S
import qualified Streamly.Prelude as S
import qualified Data.Csv as Csv
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Control.Monad.Error.Class
import qualified Data.Vector as V
import System.Environment (getArgs)
import GHC.Generics
import Control.Monad.Catch
import Control.Monad.IO.Class
import qualified Streaming as ST
import qualified Streaming.With as ST
import qualified Streaming.Prelude as ST
import qualified Streaming.Cassava as CsvST
import qualified Data.ByteString.Streaming as BSS
import Criterion.Main
import Weigh
import Streamly.Csv


readAllCassava :: FilePath -> IO (V.Vector (V.Vector BS.ByteString))
readAllCassava filename = do
  contents <- BSL.readFile filename
  either (error . show) return $
    Csv.decode Csv.HasHeader contents

copyAllCassava :: FilePath -> FilePath -> IO ()
copyAllCassava fIn fOut = do
  contents <- BSL.readFile fIn
  recs <- either (error . show) return $
    Csv.decode Csv.HasHeader contents :: IO (V.Vector (V.Vector BS.ByteString))
  BSL.writeFile fOut $ Csv.encode $ V.toList recs


readAllStreamly :: FilePath -> IO [V.Vector BS.ByteString]
readAllStreamly filename = do
  h <- openFile filename ReadMode
  let chunks = chunkStream h (64*1024)
      recs = decode Csv.HasHeader chunks
  S.toList recs


copyAllStreamly :: FilePath -> FilePath -> IO ()
copyAllStreamly fIn fOut = do
  h <- openFile fIn ReadMode
  let chunks = chunkStream h (64*1024)
      recs = decode Csv.HasHeader chunks :: S.SerialT IO (V.Vector BS.ByteString)
  withFile fOut WriteMode $ \ho ->
    S.mapM_ (BS.hPut ho) $ encode Nothing recs

newtype StupidMonad a = StupidMonad {runStupid :: IO a}
  deriving (Functor, Applicative, Monad, MonadIO, MonadMask
           , MonadCatch, MonadThrow)

instance MonadError CsvST.CsvParseException StupidMonad where
  throwError e = StupidMonad (throwM e)
  catchError (StupidMonad a) f = StupidMonad $ catch a (runStupid . f)

readAllStreaming :: FilePath -> IO [V.Vector BS.ByteString]
readAllStreaming filename = ST.withBinaryFileContents filename go
  where 
    go :: BSS.ByteString IO () -> IO [V.Vector BS.ByteString]
    go contents = 
      let recs :: ST.Stream (ST.Of (V.Vector BS.ByteString)) StupidMonad ()
          recs = CsvST.decode HasHeader (ST.hoist StupidMonad contents)
       in runStupid $ ST.toList_  recs

main = cpuBenchmark >> allocationBenchmark

cpuBenchmark = do
  let testfile = "testfile.csv"
  defaultMain [ bench "plainCassava" $ nfIO (readAllCassava testfile)
              , bench "streamlyCassava" $ nfIO (readAllStreamly testfile)
              , bench "streamingCassava" $ nfIO (readAllStreaming testfile)]


allocationBenchmark = do
  let testfile = "testfile.csv"
  putStrLn "Running allocation benchmarks"
  mainWith $ do 
    io "plainCassava" (copyAllCassava testfile) $ "out_"<>testfile
    io "streamlyCassava" (copyAllStreamly testfile) $ "out_"<>testfile
    -- io "streamingCassava" readAllStreaming testfile