{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}

-- | This module provides low-level integration with the @binary@ package and is
-- likely to be modified in backwards-incompatible ways in the future.
--
-- Use the "Pipes.Binary" module instead.

module Pipes.Binary.Internal
  ( DecodingError(..)
  , parseWithDraw
  ) where

-------------------------------------------------------------------------------

import           Control.Exception            (Exception)
import           Control.Monad.Trans.Error    (Error)
import qualified Data.ByteString              as B
import qualified Data.Binary.Get              as Get
import           Data.Data                    (Data, Typeable)
import           Pipes                        (Producer)
import qualified Pipes.Parse                  as Pp

-------------------------------------------------------------------------------

-- | A 'Get.Get' decoding error, as provided by 'Get.Fail'.
data DecodingError = DecodingError
  { peConsumed :: Get.ByteOffset -- ^Number of bytes consumed before the error.
  , peMessage  :: String         -- ^Error message.
  } deriving (Show, Read, Eq, Data, Typeable)

instance Exception DecodingError
instance Error     DecodingError

-------------------------------------------------------------------------------

instance Monad m => Error (DecodingError, Producer B.ByteString m r)

-------------------------------------------------------------------------------

-- | Run a 'Get.Get' drawing input from the given monadic action as needed.
parseWith
  :: Monad m
  => m (Maybe B.ByteString)
  -- ^An action that will be executed to provide the parser with more input
  -- as needed. If the action returns 'Nothing', then it's assumed no more
  -- input is available.
  -> Get.Get r
  -- ^Parser to run on the given input.
  -> m (Either DecodingError (Get.ByteOffset, r), Maybe B.ByteString)
  -- ^Either a decoding error or a pair of a result and the number of bytes
  -- consumed, as well as an any leftovers.
parseWith refill = \g -> step (Get.runGetIncremental g)
  where
    step (Get.Partial k)   = refill >>= \a -> step (k a)
    step (Get.Done lo n r) = return (Right (n, r), mayInput lo)
    step (Get.Fail lo n m) = return (Left (DecodingError n m), mayInput lo)
{-# INLINABLE parseWith #-}

-- | Run a parser drawing input from the underlying 'Producer'.
parseWithDraw
  :: Monad m
  => Get.Get b -- ^Parser to run on the given input.
  -> Pp.StateT (Producer B.ByteString m r) m
               (Either DecodingError (Get.ByteOffset, b), Maybe B.ByteString)
parseWithDraw = parseWith $ do
    ea <- Pp.draw
    return (case ea of
      Left  _ -> Nothing
      Right a -> Just a)
{-# INLINABLE parseWithDraw #-}

--------------------------------------------------------------------------------

-- | Wrap @a@ in 'Just' if not-null. Otherwise, 'Nothing'.
mayInput :: B.ByteString -> Maybe B.ByteString
mayInput x | B.null x = Nothing
           | otherwise = Just x
{-# INLINE mayInput #-}