{-# LANGUAGE DeriveAnyClass #-}
module Data.Binary.IO
  ( 
    ReaderError (..)
  , Reader
  , newReader
    
  , Writer
  , newWriter
    
  , Duplex
  , newDuplex
    
  , CanGet (..)
  , read
  , readWith
  , CanPut (..)
  , write
  )
where
import Prelude hiding (read)
import qualified Control.Exception as Exception
import qualified Control.Concurrent.MVar as MVar
import qualified Data.ByteString.Lazy as ByteString
import qualified Data.ByteString as ByteString.Strict
import qualified Data.Binary.Get as Binary.Get
import qualified Data.Binary.Put as Binary.Put
import qualified Data.Binary as Binary
import System.IO (Handle, hSetBinaryMode)
data ReaderError = ReaderGetError 
  { readerErrorRemaining :: !ByteString.ByteString
  
  
  
  , readerErrorOffset :: !Binary.Get.ByteOffset
  
  
  
  , readerErrorInput :: !ByteString.ByteString
  
  
  
  , readerErrorMessage :: !String
  
  
  
  }
  deriving (Show, Exception.Exception)
newtype StationaryReader = StationaryReader ByteString.ByteString
runStationaryReader :: StationaryReader -> Binary.Get.Get a -> IO (StationaryReader, a)
runStationaryReader (StationaryReader stream) getter =  do
  
  
  result <- Exception.evaluate (Binary.Get.runGetOrFail getter stream)
  case result of
    Left (remainingBody, offset, errorMessage) ->
      Exception.throw ReaderGetError
        { readerErrorRemaining = remainingBody
        , readerErrorOffset = offset
        , readerErrorInput = stream
        , readerErrorMessage = errorMessage
        }
    Right (tailStream, _, value) ->
      pure (StationaryReader tailStream, value)
newStationaryReader :: Handle -> IO StationaryReader
newStationaryReader handle = do
  hSetBinaryMode handle True
  StationaryReader <$> ByteString.hGetContents handle
newtype Reader = Reader (MVar.MVar StationaryReader)
runReader :: Reader -> Binary.Get a -> (a -> IO b) -> IO b
runReader (Reader readerVar) getter continue =
  MVar.modifyMVar readerVar $ \posReader -> do
    toReturn <- runStationaryReader posReader getter
    traverse continue toReturn
newReader
  :: Handle 
  -> IO Reader
newReader handle = do
  posReader <- newStationaryReader handle
  Reader <$> MVar.newMVar posReader
newtype Writer = Writer Handle
runWriter :: Writer -> Binary.Put -> IO ()
runWriter (Writer handle) putter =
  writeBytesAtomically handle (Binary.Put.runPut putter)
newWriter
  :: Handle 
  -> Writer
newWriter = Writer
data Duplex = Duplex
  { duplexWriter :: !Writer
  , duplexReader :: !Reader
  }
newDuplex
  :: Handle 
  -> IO Duplex
newDuplex handle =
  Duplex (newWriter handle) <$> newReader handle
class CanGet r where
  runGet
    :: r 
    -> Binary.Get a 
    -> (a -> IO b) 
    -> IO b
instance CanGet Reader where
  runGet = runReader
instance CanGet Duplex where
  runGet = runGet . duplexReader
class CanPut w where
  runPut
    :: w 
    -> Binary.Put 
    -> IO ()
instance CanPut Handle where
  runPut handle putter = writeBytesAtomically handle (Binary.Put.runPut putter)
instance CanPut Writer where
  runPut = runWriter
instance CanPut Duplex where
  runPut = runPut . duplexWriter
read
  :: (CanGet r, Binary.Binary a)
  => r 
  -> IO a
read reader =
  runGet reader Binary.get pure
readWith
  :: (CanGet r, Binary.Binary a)
  => r 
  -> (a -> IO b) 
  -> IO b
readWith reader =
  runGet reader Binary.get
write
  :: (CanPut w, Binary.Binary a)
  => w 
  -> a 
  -> IO ()
write writer value =
  runPut writer (Binary.put value)
writeBytesAtomically
  :: Handle 
  -> ByteString.ByteString 
  -> IO ()
writeBytesAtomically handle payload =
  ByteString.Strict.hPut handle (ByteString.toStrict payload)