scan :: Reader -> B.ByteString -> IO [(B.ByteString, B.ByteString)]
scan (Reader h index) begin =
  case leastUpperBound index begin of
    Just n ->  scan' n
    Nothing -> return []

  where
    scan' n 
      | n > upperIndexBound = return []
      | otherwise = do
        block <- fetch n
        scanBlock n block

    -- TODO: this might be nicer with continuations.
    scanBlock n block
      | B.null block = scan' $ n + 1
      | otherwise = do
        let (header, rest)               = B.splitAt 8 block
            (keyLenBytes, entryLenBytes) = B.splitAt 4 header
            keyLen                       = unpackInt keyLenBytes
            entryLen                     = unpackInt entryLenBytes
            (bytes, block')              = B.splitAt (keyLen + entryLen) rest
            (key, entry)                 = B.splitAt keyLen bytes

        if key < begin  -- we may need to skip initial entries
           then scanBlock n block'
           else do 
             next <- unsafeInterleaveIO $ scanBlock n block'
             return $ (key, entry):next

    fetch n = do
      -- (a bounds check has already been performed here)
      let (_, off, len) = index!n
      hSeek h AbsoluteSeek $ fromIntegral off
      B.hGet h $ fromIntegral len

    (_, upperIndexBound) = bounds index

    unpackInt = fromIntegral . unpack32 . B.unpack

-- | /O(log n) in index size./ Find the smallest value in the index
-- greater than or equal to the given key using binary search. We
-- don't specialize the type to 'Index' in order to retain
-- compatibility with our quickcheck tests.
scan :: Reader -> B.ByteString -> IO [(B.ByteString, B.ByteString)]
scan (Reader h index) begin =
  case leastUpperBound index begin of
    Just n ->  scan' n
    Nothing -> return []

  where
    scan' n 
      | n > upperIndexBound = return []
      | otherwise = do
        block <- fetch n
        scanBlock n block

    -- TODO: this might be nicer with continuations.
    scanBlock n block
      | B.null block = scan' $ n + 1
      | otherwise = do
        let (header, rest)               = B.splitAt 8 block
            (keyLenBytes, entryLenBytes) = B.splitAt 4 header
            keyLen                       = unpackInt keyLenBytes
            entryLen                     = unpackInt entryLenBytes
            (bytes, block')              = B.splitAt (keyLen + entryLen) rest
            (key, entry)                 = B.splitAt keyLen bytes

        if key < begin  -- we may need to skip initial entries
           then scanBlock n block'
           else do 
             next <- unsafeInterleaveIO $ scanBlock n block'
             return $ (key, entry):next

    fetch n = do
      -- (a bounds check has already been performed here)
      let (_, off, len) = index!n
      hSeek h AbsoluteSeek $ fromIntegral off
      B.hGet h $ fromIntegral len

    (_, upperIndexBound) = bounds index

    unpackInt = fromIntegral . unpack32 . B.unpack

-- | /O(log n) in index size./ Find the smallest value in the index
-- greater than or equal to the given key using binary search. We
-- don't specialize the type to 'Index' in order to retain
-- compatibility with our quickcheck tests.
scan (Reader h index) begin =
  case leastUpperBound index begin of
    Just n ->  scan' n
    Nothing -> return []

  where
    scan' n 
      | n > upperIndexBound = return []
      | otherwise = do
        block <- fetch n
        scanBlock n block

    -- TODO: this might be nicer with continuations.
    scanBlock n block
      | B.null block = scan' $ n + 1
      | otherwise = do
        let (header, rest)               = B.splitAt 8 block
            (keyLenBytes, entryLenBytes) = B.splitAt 4 header
            keyLen                       = unpackInt keyLenBytes
            entryLen                     = unpackInt entryLenBytes
            (bytes, block')              = B.splitAt (keyLen + entryLen) rest
            (key, entry)                 = B.splitAt keyLen bytes

        if key < begin  -- we may need to skip initial entries
           then scanBlock n block'
           else do 
             next <- unsafeInterleaveIO $ scanBlock n block'
             return $ (key, entry):next

    fetch n = do
      -- (a bounds check has already been performed here)
      let (_, off, len) = index!n
      hSeek h AbsoluteSeek $ fromIntegral off
      B.hGet h $ fromIntegral len

    (_, upperIndexBound) = bounds index

    unpackInt = fromIntegral . unpack32 . B.unpack

-- | /O(log n) in index size./ Find the smallest value in the index
-- greater than or equal to the given key using binary search. We
-- don't specialize the type to 'Index' in order to retain
-- compatibility with our quickcheck tests.