module Data.Repa.Convert.Internal.Unpacker
        ( Unpacker (..)
        , unsafeRunUnpacker)
where
import Data.IORef
import Data.Word
import GHC.Exts
import Prelude hiding (fail)
import qualified Foreign.Ptr            as F


---------------------------------------------------------------------------------------------------
data Unpacker a
  =  Unpacker
  {  -- | Takes pointers to the first byte in the buffer; the first byte
     --   after the buffer; a predicate to detect a field terminator;
     --   a failure action; and a continuation.
     -- 
     --   The field terminator is used by variable length encodings where
     --   the length of the encoded data cannot be determined from the
     --   encoding itself.
     --
     --   We try to unpack a value from the buffer.
     --   If unpacking succeeds then call the continuation with a pointer
     --   to the next byte after the unpacked value, and the value itself,
     --   otherwise call the failure action.
     --
     fromUnpacker
        :: Addr#                 -- Start of buffer.
        -> Addr#                 -- Pointer to first byte after end of buffer.
        -> (Word8 -> Bool)       -- Detect a field terminator.
        -> IO ()                 -- Signal failure.
        -> (Addr# -> a -> IO ()) -- Accept an unpacked value.
        -> IO ()
  }


instance Functor Unpacker where
 fmap f (Unpacker fx)
  =  Unpacker $ \start end stop fail eat
  -> fx start end stop fail $ \start_x x
  -> eat start_x (f x)
 {-# INLINE fmap #-}


instance Applicative Unpacker where
 pure  x
  =  Unpacker $ \start _end _fail _stop eat
  -> eat start x
 {-# INLINE pure #-}

 (<*>) (Unpacker ff) (Unpacker fx)
  =  Unpacker $ \start end stop fail eat
  -> ff start   end stop fail $ \start_f f
  -> fx start_f end stop fail $ \start_x x
  -> eat start_x (f x)
 {-# INLINE (<*>) #-}


instance Monad Unpacker where
 return = pure
 {-# INLINE return #-}

 (>>=) (Unpacker fa) mkfb
  =  Unpacker $ \start end stop fail eat
  -> fa start end stop fail $ \start_x x
  -> case mkfb x of
        Unpacker fb
         -> fb start_x end stop fail eat
 {-# INLINE (>>=) #-}


-- | Unpack data from the given buffer.
--
--   PRECONDITION: The buffer must be at least the minimum size of the 
--   format (minSize). This allows us to avoid repeatedly checking for 
--   buffer overrun when unpacking fixed size format. If the buffer
--   is not long enough then you'll get an indeterminate result (bad).
--
unsafeRunUnpacker
        :: Unpacker a           -- ^ Unpacker to run.
        -> F.Ptr Word8          -- ^ Source buffer.
        -> Int                  -- ^ Length of source buffer.
        -> (Word8 -> Bool)      -- ^ Detect a field terminator.
        -> IO (Maybe (a, F.Ptr Word8))
                -- ^ Unpacked result, and pointer to the byte after the last
                --   one read.

unsafeRunUnpacker (Unpacker f) (Ptr start) (I# len) stop
 = do   ref     <- newIORef Nothing
        f       start
                (plusAddr# start len)
                stop
                (return ())
                (\addr' x -> writeIORef ref (Just (x, (Ptr addr'))))
        readIORef ref
{-# INLINE unsafeRunUnpacker #-}