packages feed

snappy-c-0.1.0: src/Codec/Compression/SnappyC/Internal/Buffer.hs

-- | Intended for qualified import:
--
-- > import Codec.Compression.SnappyC.Internal.Buffer (Buffer)
-- > import Codec.Compression.SnappyC.Internal.Buffer qualified as Buffer

module Codec.Compression.SnappyC.Internal.Buffer
  ( -- * 'Buffer' type
    Buffer -- Opaque

    -- ** Introduction
  , empty
  , append

    -- ** Elimination
  , toStrict

    -- ** Length etc.
  , length
  , null

    -- ** Splitting
  , splitExactly
  ) where

import Prelude hiding (length, null)

import Data.ByteString qualified as Strict (ByteString)
import Data.ByteString qualified as BS.Strict
import Data.ByteString.Lazy qualified as Lazy (ByteString)
import Data.ByteString.Lazy qualified as BS.Lazy

-- | Intended for efficiency in the case of a bunch of appends followed by a
-- bunch of splits.
data Buffer =
      Forward
        Lazy.ByteString
        !Int
    | Backward
        [Strict.ByteString]
        !Int
  deriving Show

-- | Empty buffer
empty :: Buffer
empty = Backward [] 0

-- | Is the 'Buffer' empty?
--
-- /O(1)/
null :: Buffer -> Bool
null = (== 0) . length

-- | Length
--
-- /O(1)/
length :: Buffer -> Int
length (Forward  _ l) = l
length (Backward _ l) = l

-- | Append data to the end of the 'Buffer'.
--
-- /O(n)/ if the given buffer is forwards, /O(1)/ otherwise.
append :: Buffer -> Strict.ByteString -> Buffer
append b bs =
    let
      !(chunks, l) = backwards b
    in
      Backward (bs : chunks) (l + BS.Strict.length bs)

-- | Get the buffered chunks in backwards order
--
-- /O(n)/ if the given buffer is forwards, /O(1)/ otherwise.
backwards :: Buffer -> ([Strict.ByteString], Int)
backwards (Forward  bs l) = (reverse $ BS.Lazy.toChunks bs, l)
backwards (Backward bs l) = (bs, l)

-- | Get the buffer data as a 'Lazy.ByteString' paired with its length.
--
-- /O(n)/ if the buffer is backward, /O(1)/ otherwise.
toLazy :: Buffer -> (Lazy.ByteString, Int)
toLazy (Forward  bs l) = (bs, l)
toLazy (Backward bs l) = (BS.Lazy.fromChunks $ reverse bs, l)

-- | Create a 'Buffer' from a 'Lazy.ByteString' paired with its length.
--
-- /O(1)/
fromLazy :: (Lazy.ByteString, Int) -> Buffer
fromLazy (bs, l) = Forward bs l

-- | Split off a chunk of exactly @n@ bytes.
--
-- If there aren't enough bytes, return how many bytes we need.
--
-- /O(1)/ if the length is insufficient, /O(n)/ otherwise.
splitExactly :: Int -> Buffer -> Either Int (Strict.ByteString, Buffer)
splitExactly n b
    | length b < n
    = Left (n - length b)
    | otherwise
    = let
        !(bs, bsLength) = toLazy b
        !(tookBs, rest) = BS.Lazy.splitAt (fromIntegral n) bs
      in
        Right
          ( BS.Lazy.toStrict tookBs
          , fromLazy (rest, bsLength - n)
          )

-- | Get the buffer data as a 'Strict.ByteString'. __Only__ call this function if
-- you are __sure__ the data in the buffer is small.
--
-- /O(n)/
--
-- TODO: We are losing size information here that we could use to our advantage
-- in the conversion, but it's a minor inefficiency.
toStrict :: Buffer -> Strict.ByteString
toStrict (Forward  bs _) = BS.Lazy.toStrict bs
toStrict (Backward bs _) = BS.Strict.concat $ reverse bs