content: {-# LANGUAGE BangPatterns  #-}
  {-# LANGUAGE CPP           #-}
  {-# LANGUAGE DeriveFunctor #-}
  
  -- |Strict Decoder Types
  module Flat.Decoder.Types
    (
      Get(..)
    , S(..)
    , GetResult(..)
    , Decoded
    , DecodeException(..)
    , notEnoughSpace
    , tooMuchSpace
    , badEncoding
    , badOp
    ) where
  
  import           Control.DeepSeq    (NFData (..))
  import           Control.Exception  (Exception, throwIO)
  import           Data.Word          (Word8)
  import           Foreign            (Ptr)
  
  #if MIN_VERSION_base(4,9,0)
  import qualified Control.Monad.Fail as Fail
  #endif
  
  {- |
  A decoder.
  
  Given:
  
  * end of input buffer
  
  * current position in input buffer
  
  Returns:
  
  * decoded value
  
  * new position in input buffer
  -}
  newtype Get a =
    Get
      { runGet ::
        Ptr Word8
        -> S
        -> IO (GetResult a)
      }
  
  -- Seems to give better performance than the derived version
  instance Functor Get where
    fmap f g =
      Get $ \end s -> do
        GetResult s' a <- runGet g end s
        return $ GetResult s' (f a)
    {-# INLINE fmap #-}
  
  -- Is this correct?
  instance NFData (Get a) where
    rnf !_ = ()
  
  instance Show (Get a) where
    show _ = "Get"
  
  instance Applicative Get where
    pure x = Get (\_ ptr -> return $ GetResult ptr x)
    {-# INLINE pure #-}
    Get f <*> Get g =
      Get $ \end ptr1 -> do
        GetResult ptr2 f' <- f end ptr1
        GetResult ptr3 g' <- g end ptr2
        return $ GetResult ptr3 (f' g')
    {-# INLINE (<*>) #-}
    Get f *> Get g =
      Get $ \end ptr1 -> do
        GetResult ptr2 _ <- f end ptr1
        g end ptr2
    {-# INLINE (*>) #-}
  
  instance Monad Get where
    return = pure
    {-# INLINE return #-}
    (>>) = (*>)
    {-# INLINE (>>) #-}
    Get x >>= f =
      Get $ \end s -> do
        GetResult s' x' <- x end s
        runGet (f x') end s'
    {-# INLINE (>>=) #-}
  #if !(MIN_VERSION_base(4,13,0))
    fail = failGet
  #endif
  
  #if MIN_VERSION_base(4,9,0)
  instance Fail.MonadFail Get where
    fail = failGet
  #endif
  {-# INLINE failGet #-}
  failGet :: String -> Get a
  failGet msg = Get $ \end s -> badEncoding end s msg
  
  -- |Decoder state
  data S =
    S
      { currPtr  :: {-# UNPACK #-}!(Ptr Word8)
      , usedBits :: {-# UNPACK #-}!Int
      }
    deriving (Show, Eq, Ord)
  
  data GetResult a =
    GetResult {-# UNPACK #-}!S !a
    deriving (Functor)
  
  -- |A decoded value
  type Decoded a = Either DecodeException a
  
  -- |An exception during decoding
  data DecodeException
    = NotEnoughSpace Env
    | TooMuchSpace Env
    | BadEncoding Env String
    | BadOp String
    deriving (Show, Eq, Ord)
  
  type Env = (Ptr Word8, S)
  
  notEnoughSpace :: Ptr Word8 -> S -> IO a
  notEnoughSpace endPtr s = throwIO $ NotEnoughSpace (endPtr, s)
  
  tooMuchSpace :: Ptr Word8 -> S -> IO a
  tooMuchSpace endPtr s = throwIO $ TooMuchSpace (endPtr, s)
  
  badEncoding :: Ptr Word8 -> S -> String -> IO a
  badEncoding endPtr s msg = throwIO $ BadEncoding (endPtr, s) msg
  
  badOp :: String -> IO a
  badOp msg = throwIO $ BadOp msg
  
  instance Exception DecodeException
  