{-# LANGUAGE
    BangPatterns
  , FlexibleContexts
  , ScopedTypeVariables
  , UndecidableInstances
  , UnicodeSyntax
  #-}
-- | Fast, packed, strict bit streams (i.e. list of 'Bool's) with
-- semi-automatic stream fusion.
--
-- This module is intended to be imported @qualified@, to avoid name
-- clashes with "Prelude" functions. e.g.
--
-- > import qualified Data.BitStream as BS
--
-- Strict 'Bitstream's are made of strict 'SV.Vector' of 'Packet's,
-- and each 'Packet's have at least 1 bit.
module Data.Bitstream
    ( -- * Types
      Bitstream
    , Left
    , Right

      -- * Introducing and eliminating 'Bitstream's
    , empty
    , ()
    , singleton
    , pack
    , unpack
    , fromPackets
    , toPackets

      -- ** Converting from\/to strict 'BS.ByteString's
    , fromByteString
    , toByteString

      -- ** Converting from\/to 'S.Stream's
    , stream
    , unstream

      -- * Changing bit order in octets
    , directionLToR
    , directionRToL

      -- * Basic interface
    , cons
    , snoc
    , append
    , ()
    , head
    , last
    , tail
    , init
    , null
    , length

      -- * Transforming 'Bitstream's
    , map
    , reverse

      -- * Reducing 'Bitstream's
    , foldl
    , foldl'
    , foldl1
    , foldl1'
    , foldr
    , foldr1

      -- ** Special folds
    , concat
    , concatMap
    , and
    , or
    , any
    , all

      -- * Building lists
      -- ** Scans
    , scanl
    , scanl1
    , scanr
    , scanr1

      -- ** Replication
    , replicate

      -- ** Unfolding
    , unfoldr
    , unfoldrN

      -- * Substreams
    , take
    , drop
    , takeWhile
    , dropWhile
    , span
    , break

      -- * Searching streams
      -- ** Searching by equality
    , elem
    , ()
    , ()
    , notElem
    , ()
    , ()

      -- ** Searching with a predicate
    , find
    , filter
    , partition

      -- ** Indexing streams
    , (!!)
    , elemIndex
    , elemIndices
    , findIndex
    , findIndices

      -- * Zipping and unzipping streams
    , zip
    , zip3
    , zip4
    , zip5
    , zip6
    , zipWith
    , zipWith3
    , zipWith4
    , zipWith5
    , zipWith6
    , unzip
    , unzip3
    , unzip4
    , unzip5
    , unzip6

    -- * I/O with 'Bitstream's
    -- ** Standard input and output
    , getContents
    , putBits
    , interact

    -- ** Files
    , readFile
    , writeFile
    , appendFile

    -- ** I/O with 'Handle's
    , hGetContents
    , hGet
    , hGetSome
    , hGetNonBlocking
    , hPut
    )
    where
import Data.Bitstream.Generic hiding (Bitstream)
import qualified Data.Bitstream.Generic as G
import Data.Bitstream.Internal
import Data.Bitstream.Packet
import qualified Data.ByteString as BS
import qualified Data.List as L
import Data.Monoid
import qualified Data.Vector.Generic as GV
import qualified Data.Vector.Storable as SV
import qualified Data.Vector.Fusion.Stream as S
import Data.Vector.Fusion.Stream.Monadic (Stream(..), Step(..))
import Data.Vector.Fusion.Stream.Size
import Data.Vector.Fusion.Util
import Prelude ( Bool(..), Eq(..), Int, Integral, Maybe(..), Monad(..), Num(..)
               , Ord(..), Show(..), ($), div, error, fmap
               , fromIntegral, fst, mod, otherwise
               )
import Prelude.Unicode hiding ((), (), ())
import System.IO (FilePath, Handle, IO)

-- | A space-efficient representation of a 'Bool' vector, supporting
-- many efficient operations. 'Bitstream's have an idea of
-- /directions/ controlling how octets are interpreted as bits. There
-- are two types of concrete 'Bitstream's: @'Bitstream' 'Left'@ and
-- @'Bitstream' 'Right'@.
newtype Bitstream d
    = Bitstream (SV.Vector (Packet d))

instance Show (Packet d)  Show (Bitstream d) where
    {-# INLINEABLE show #-}
    show (Bitstream v0)
        = L.concat
          [ "(S"
          , L.concat (L.unfoldr go v0)
          , ")"
          ]
        where
          {-# INLINE go #-}
          go v | SV.null v = Nothing
               | otherwise = Just (show (SV.head v), SV.tail v)

instance G.Bitstream (Packet d)  Eq (Bitstream d) where
    {-# INLINE (==) #-}
    x == y = stream x  stream y

-- | 'Bitstream's are lexicographically ordered.
--
-- @
-- let x = 'pack' ['True' , 'False', 'False']
--     y = 'pack' ['False', 'True' , 'False']
--     z = 'pack' ['False']
-- in
--   [ 'compare' x y -- 'GT'
--   , 'compare' z y -- 'LT'
--   ]
-- @
instance G.Bitstream (Packet d)  Ord (Bitstream d) where
    {-# INLINE compare #-}
    x `compare` y = stream x `compare` stream y

-- | 'Bitstream' forms 'Monoid' in the same way as ordinary lists:
--
-- @
-- 'mempty'  = 'empty'
-- 'mappend' = 'append'
-- 'mconcat' = 'concat'
-- @
instance G.Bitstream (Packet d)  Monoid (Bitstream d) where
    mempty  = ()
    mappend = ()
    mconcat = concat

instance G.Bitstream (Packet d)  G.Bitstream (Bitstream d) where
    {-# INLINE [0] stream #-}
    stream (Bitstream v)
        = {-# CORE "Bitstream stream" #-}
          S.concatMap stream (GV.stream v)
          `S.sized`
          Exact (length (Bitstream v))

    {-# INLINE [0] unstream #-}
    unstream
        = {-# CORE "Bitstream unstream" #-}
          Bitstream  GV.unstream  packPackets

    {-# INLINEABLE [2] cons #-}
    cons b (Bitstream v)
        | SV.null v = Bitstream (SV.singleton (singleton b))
        | otherwise = case SV.head v of
                        p | length p < (8  Int)
                                 Bitstream ((b `cons` p) `SV.cons` SV.tail v)
                          | otherwise
                                 Bitstream (singleton b `SV.cons` v)

    {-# INLINEABLE [2] snoc #-}
    snoc (Bitstream v) b
        | SV.null v = Bitstream (SV.singleton (singleton b))
        | otherwise = case SV.last v of
                        p | length p < (8  Int)
                                 Bitstream (SV.init v `SV.snoc` (p `snoc` b))
                          | otherwise
                                 Bitstream (v `SV.snoc` singleton b)

    {-# INLINE [2] append #-}
    append (Bitstream x) (Bitstream y)
        = Bitstream (x SV.++ y)

    {-# INLINEABLE [2] tail #-}
    tail (Bitstream v)
        | SV.null v = emptyStream
        | otherwise = case tail (SV.head v) of
                        p' | null p'    Bitstream (SV.tail v)
                           | otherwise  Bitstream (p' `SV.cons` SV.tail v)

    {-# INLINEABLE [2] init #-}
    init (Bitstream v)
        | SV.null v = emptyStream
        | otherwise = case init (SV.last v) of
                        p' | null p'    Bitstream (SV.init v)
                           | otherwise  Bitstream (SV.init v `SV.snoc` p')

    {-# INLINE [2] map #-}
    map f (Bitstream v)
        = Bitstream (SV.map (map f) v)

    {-# INLINE [2] reverse #-}
    reverse (Bitstream v)
        = Bitstream (SV.reverse (SV.map reverse v))

    {-# INLINE [1] scanl #-}
    scanl f b
        = unstream  S.scanl f b  stream

    {-# INLINE [2] concat #-}
    concat = Bitstream  SV.concat  L.map toPackets

    {-# INLINEABLE replicate #-}
    replicate n0 b
        | n0  0         = ()
        | n0 `mod` 8  0 = Bitstream anterior
        | otherwise      = Bitstream (anterior `SV.snoc` posterior)
        where
          {-# INLINE anterior #-}
          anterior = SV.replicate n p
              where
                n  Int
                {-# INLINE n #-}
                n = fromIntegral (n0 `div` 8)
                {-# INLINE p #-}
                p = replicate (8  Int) b

          {-# INLINE posterior #-}
          posterior = replicate n b
              where
                n  Int
                {-# INLINE n #-}
                n = fromIntegral (n0 `mod` 8)

    {-# INLINEABLE [2] take #-}
    take n0 (Bitstream v0)
        | n0  0    = ()
        | otherwise = Bitstream (SV.unfoldrN nOctets go (n0, v0))
        where
          {-# INLINE nOctets #-}
          nOctets  Int
          nOctets = fromIntegral (min n0 (fromIntegral (SV.length v0)))
          {-# INLINE go #-}
          go (0, _) = Nothing
          go (n, v)
              | SV.null v = Nothing
              | otherwise = let p  = SV.head v
                                v' = SV.tail v
                                p' = take n p
                                n' = n - length p'
                            in
                              return (p', (n', v'))

    {-# INLINEABLE [2] drop #-}
    drop n0 (Bitstream v0)
        | n0  0    = Bitstream v0
        | otherwise = Bitstream (go n0 v0)
        where
          {-# INLINE go #-}
          go 0 v = v
          go n v
              | SV.null v = v
              | otherwise = case SV.head v of
                              p | n  length p  go (n - length p) (SV.tail v)
                                | otherwise     drop n p `SV.cons` (SV.tail v)

    {-# INLINEABLE [2] takeWhile #-}
    takeWhile f (Bitstream v0)
        = Bitstream (GV.unstream (takeWhilePS (GV.stream v0)))
        where
          {-# INLINE takeWhilePS #-}
          takeWhilePS (Stream step s0 sz) = Stream step' (Just s0) (toMax sz)
              where
                {-# INLINE step' #-}
                step' Nothing  = return Done
                step' (Just s)
                    = do r  step s
                         case r of
                           Yield p s'
                                case takeWhile f p of
                                    p' | p  p'     return $ Yield p' (Just s')
                                       | otherwise  return $ Yield p' Nothing
                           Skip    s'
                                return $ Skip (Just s')
                           Done
                                return Done

    {-# INLINEABLE [2] dropWhile #-}
    dropWhile f (Bitstream v0) = Bitstream (go v0)
        where
          {-# INLINE go #-}
          go v | SV.null v = v
               | otherwise = case dropWhile f (SV.head v) of
                               p' | null p'    go (SV.tail v)
                                  | otherwise  p' `SV.cons` SV.tail v

    {-# INLINEABLE [2] filter #-}
    filter f (Bitstream v0)
        = Bitstream (GV.unstream (filterPS (GV.stream v0)))
        where
          {-# INLINE filterPS #-}
          filterPS (Stream step s0 sz) = Stream step' s0 (toMax sz)
              where
                {-# INLINE step' #-}
                step' s
                    = do r  step s
                         case r of
                           Yield p s'  case filter f p of
                                           p' | null p'    return $ Skip s'
                                              | otherwise  return $ Yield p' s'
                           Skip    s'  return $ Skip s'
                           Done        return Done

strictHead  G.Bitstream (Packet d)  Bitstream d  Bool
{-# RULES "head → strictHead" [2]
    ∀(v ∷ G.Bitstream (Packet d) ⇒ Bitstream d).
    head v = strictHead v #-}
{-# INLINE strictHead #-}
strictHead (Bitstream v) = head (SV.head v)

strictLast  G.Bitstream (Packet d)  Bitstream d  Bool
{-# RULES "last → strictLast" [2]
    ∀(v ∷ G.Bitstream (Packet d) ⇒ Bitstream d).
    last v = strictLast v #-}
{-# INLINE strictLast #-}
strictLast (Bitstream v) = last (SV.last v)

strictNull  Bitstream d  Bool
{-# RULES "null → strictNull" [2] null = strictNull #-}
{-# INLINE strictNull #-}
strictNull (Bitstream v) = SV.null v

strictLength  (G.Bitstream (Packet d), Num n)  Bitstream d  n
{-# RULES "length → strictLength" [2]
    ∀(v ∷ G.Bitstream (Packet d) ⇒ Bitstream d).
    length v = strictLength v #-}
{-# INLINEABLE strictLength #-}
strictLength (Bitstream v)
    = SV.foldl' (\n p  n + length p) 0 v

strictAnd  G.Bitstream (Packet d)  Bitstream d  Bool
{-# RULES "and → strictAnd" [2]
    ∀(v ∷ G.Bitstream (Packet d) ⇒ Bitstream d).
    and v = strictAnd v #-}
{-# INLINE strictAnd #-}
strictAnd (Bitstream v)
    = SV.all and v

strictOr  G.Bitstream (Packet d)  Bitstream d  Bool
{-# RULES "or → strictOr" [2]
    ∀(v ∷ G.Bitstream (Packet d) ⇒ Bitstream d).
    or v = strictOr v #-}
{-# INLINE strictOr #-}
strictOr (Bitstream v)
    = SV.any or v

strictIndex  (G.Bitstream (Packet d), Integral n)  Bitstream d  n  Bool
{-# RULES "(!!) → strictIndex" [2]
    ∀(v ∷ G.Bitstream (Packet d) ⇒ Bitstream d) n.
    v !! n = strictIndex v n #-}
{-# INLINEABLE strictIndex #-}
strictIndex (Bitstream v0) i0
    | i0 < 0    = indexOutOfRange i0
    | otherwise = go v0 i0
    where
      {-# INLINE go #-}
      go v i
          | SV.null v = indexOutOfRange i
          | otherwise = case SV.head v of
                          p | i < length p  p !! i
                            | otherwise     go (SV.tail v) (i - length p)

emptyStream  α
emptyStream
    = error "Data.Bitstream: empty stream"

{-# INLINE indexOutOfRange #-}
indexOutOfRange  Integral n  n  α
indexOutOfRange n = error ("Data.Bitstream: index out of range: " L.++ show n)

-- | /O(n)/ Convert a strict 'BS.ByteString' into a strict
-- 'Bitstream'.
{-# INLINE fromByteString #-}
fromByteString  BS.ByteString  Bitstream d
fromByteString bs0 = Bitstream (SV.unfoldrN nOctets go bs0)
    where
      {-# INLINE nOctets #-}
      nOctets  Int
      nOctets = BS.length bs0
      {-# INLINE go #-}
      go bs = do (o, bs')  BS.uncons bs
                 return (fromOctet o, bs')

-- | /O(n)/ @'toByteString' bits@ converts a strict 'Bitstream' @bits@
-- into a strict 'BS.ByteString'. The resulting octets will be padded
-- with zeroes if the 'length' of @bs@ is not multiple of 8.
{-# INLINEABLE toByteString #-}
toByteString  d. G.Bitstream (Packet d)  Bitstream d  BS.ByteString
toByteString = unstreamBS
              (packPackets  Stream Id Bool  Stream Id (Packet d))
              stream

unstreamBS  Stream Id (Packet d)  BS.ByteString
{-# INLINE unstreamBS #-}
unstreamBS (Stream step s0 sz)
    = case upperBound sz of
        Just n   fst $ BS.unfoldrN n (unId  go) s0
        Nothing  BS.unfoldr (unId  go) s0
      where
        {-# INLINE go #-}
        go s = do r  step s
                  case r of
                    Yield p s'  return $ Just (toOctet p, s')
                    Skip    s'  go s'
                    Done        return Nothing

-- | /O(1)/ Convert a 'SV.Vector' of 'Packet's into a 'Bitstream'.
fromPackets  SV.Vector (Packet d)  Bitstream d
{-# INLINE fromPackets #-}
fromPackets = Bitstream

-- | /O(1)/ Convert a 'Bitstream' into a 'SV.Vector' of 'Packet's.
toPackets  Bitstream d  SV.Vector (Packet d)
{-# INLINE toPackets #-}
toPackets (Bitstream d) = d

-- | /O(n)/ Convert a @'Bitstream' 'Left'@ into a @'Bitstream'
-- 'Right'@. Bit directions only affect octet-based operations such as
-- 'toByteString'.
directionLToR  Bitstream Left  Bitstream Right
{-# INLINE directionLToR #-}
directionLToR (Bitstream v) = Bitstream (SV.map packetLToR v)

-- | /O(n)/ Convert a @'Bitstream' 'Right'@ into a @'Bitstream'
-- 'Left'@. Bit directions only affect octet-based operations such as
-- 'toByteString'.
directionRToL  Bitstream Right  Bitstream Left
{-# INLINE directionRToL #-}
directionRToL (Bitstream v) = Bitstream (SV.map packetRToL v)

-- | /O(n)/ Read a 'Bitstream' from the stdin strictly, equivalent to
-- 'hGetContents' @stdin@. The 'Handle' is closed after the contents
-- have been read.
getContents  G.Bitstream (Packet d)  IO (Bitstream d)
{-# INLINE getContents #-}
getContents = fmap fromByteString BS.getContents

-- | /O(n)/ Write a 'Bitstream' to the stdout, equivalent to 'hPut'
-- @stdout@.
putBits  G.Bitstream (Packet d)  Bitstream d  IO ()
{-# INLINE putBits #-}
putBits = BS.putStr  toByteString

-- | The 'interact' function takes a function of type @'Bitstream' d
-- -> 'Bitstream' d@ as its argument. The entire input from the stdin
-- is passed to this function as its argument, and the resulting
-- 'Bitstream' is output on the stdout.
interact  G.Bitstream (Packet d)  (Bitstream d  Bitstream d)  IO ()
{-# INLINE interact #-}
interact = BS.interact  lift'
    where
      {-# INLINE lift' #-}
      lift' f = toByteString  f  fromByteString

-- | /O(n)/ Read an entire file strictly into a 'Bitstream'.
readFile  G.Bitstream (Packet d)  FilePath  IO (Bitstream d)
{-# INLINE readFile #-}
readFile = fmap fromByteString  BS.readFile

-- | /O(n)/ Write a 'Bitstream' to a file.
writeFile  G.Bitstream (Packet d)  FilePath  Bitstream d  IO ()
{-# INLINE writeFile #-}
writeFile = ( toByteString)  BS.writeFile

-- | /O(n)/ Append a 'Bitstream' to a file.
appendFile  G.Bitstream (Packet d)  FilePath  Bitstream d  IO ()
{-# INLINE appendFile #-}
appendFile = ( toByteString)  BS.appendFile

-- | /O(n)/ Read entire handle contents strictly into a 'Bitstream'.
--
-- This function reads chunks at a time, doubling the chunksize on each
-- read. The final buffer is then realloced to the appropriate size. For
-- files > half of available memory, this may lead to memory exhaustion.
-- Consider using 'readFile' in this case.
--
-- The 'Handle' is closed once the contents have been read, or if an
-- exception is thrown.
hGetContents  G.Bitstream (Packet d)  Handle  IO (Bitstream d)
{-# INLINE hGetContents #-}
hGetContents = fmap fromByteString  BS.hGetContents

-- | /O(n)/ @'hGet' h n@ reads a 'Bitstream' directly from the
-- specified 'Handle' @h@. First argument @h@ is the 'Handle' to read
-- from, and the second @n@ is the number of /octets/ to read, not
-- /bits/. It returns the octets read, up to @n@, or null if EOF has
-- been reached.
--
-- If the handle is a pipe or socket, and the writing end is closed,
-- 'hGet' will behave as if EOF was reached.
hGet  G.Bitstream (Packet d)  Handle  Int  IO (Bitstream d)
{-# INLINE hGet #-}
hGet = (fmap fromByteString )  BS.hGet

-- | /O(n)/ Like 'hGet', except that a shorter 'Bitstream' may be
-- returned if there are not enough octets immediately available to
-- satisfy the whole request. 'hGetSome' only blocks if there is no
-- data available, and EOF has not yet been reached.
hGetSome  G.Bitstream (Packet d)  Handle  Int  IO (Bitstream d)
{-# INLINE hGetSome #-}
hGetSome = (fmap fromByteString )  BS.hGetSome

-- | /O(n)/ 'hGetNonBlocking' is similar to 'hGet', except that it
-- will never block waiting for data to become available. If there is
-- no data available to be read, 'hGetNonBlocking' returns 'empty'.
hGetNonBlocking  G.Bitstream (Packet d)  Handle  Int  IO (Bitstream d)
{-# INLINE hGetNonBlocking #-}
hGetNonBlocking = (fmap fromByteString )  BS.hGetNonBlocking

-- | /O(n)/ Write a 'Bitstream' to the given 'Handle'.
hPut  G.Bitstream (Packet d)  Handle  Bitstream d  IO ()
{-# INLINE hPut #-}
hPut = ( toByteString)  BS.hPut