module Data.BinaryCom
  (BinaryCom, binaryCom, binaryCom', binaryCom'',
   send, flush, sendFlush, receive)
where

import System.IO
import Data.IORef
import Control.Monad (liftM)
import Control.Monad.Trans (MonadIO(..))
import qualified Data.ByteString.Lazy as L
import qualified Data.Binary as B
import qualified Data.Binary.Get as B


-- | @BinaryCom@ type
newtype BinaryCom = BinaryCom (IORef (L.ByteString, Handle))

-- | Creates a @BinaryCom@ from a @Handle@ opened for both reading and writing.
-- Be careful not to use the handle afterwards
binaryCom :: (MonadIO m) => Handle -> m BinaryCom
binaryCom h = binaryCom' h h

-- | Creates a @BinaryCom@ from two @Handle@s: one for reading, one for writing
binaryCom' :: (MonadIO m) => Handle -> Handle -> m BinaryCom
binaryCom' hR hW = do
  inp <- liftIO $ L.hGetContents hR
  binaryCom'' inp hW

-- | Creates a @BinaryCom@ from a lazy @ByteString@ (for reading) and a @Handle@ (for writing)
binaryCom'' :: (MonadIO m) => L.ByteString -> Handle -> m BinaryCom
binaryCom'' inp hW = liftIO $
  liftM BinaryCom $ newIORef (inp, hW)

-- | Sends a serializable value through a @BinaryCom@
send :: (B.Binary a, MonadIO m) => BinaryCom -> a -> m ()
send (BinaryCom ref) val = liftIO $ do
  (_, h) <- readIORef ref
  L.hPut h (B.encode val)

-- | Flushes a @BinaryCom@. Do not forget to do this after sending!
flush :: (MonadIO m) => BinaryCom -> m ()
flush (BinaryCom ref) = liftIO $ do
  (_, h) <- readIORef ref
  hFlush h

-- | Shortcut for sending a value and flushing the @BinaryCom@
sendFlush :: (B.Binary a, MonadIO m) => BinaryCom -> a -> m ()
sendFlush b v = send b v >> flush b

-- | Receives a serializable value through a @BinaryCom@
receive :: (B.Binary a, MonadIO m) => BinaryCom -> m a
receive (BinaryCom ref) = liftIO $ do
  (inp, h) <- readIORef ref
  let (val, inpRest, _) = B.runGetState B.get inp 0
  writeIORef ref (inpRest, h)
  return val