    -- | Folds for text streams

    module Control.Foldl.Text (
        -- * Folding
          fold

        -- * Folds
        , head
        , last
        , null
        , length
        , any
        , all
        , maximum
        , minimum
        , elem
        , notElem
        , find
        , index
        , elemIndex
        , findIndex
        , count

        -- * Re-exports
        -- $reexports
        , module Control.Foldl
        , module Data.Text
        ) where

    import Control.Foldl (Fold)
    import Control.Foldl.Internal (Maybe'(..), lazy, strict, Either'(..), hush)
    import qualified Control.Foldl as L
    import Data.Text (Text)
    import qualified Data.Text as T
    import qualified Data.Text.Lazy as Lazy
    import Prelude hiding (
        head, last, null, length, any, all, maximum, minimum, elem, notElem )

    -- | Apply a strict left 'Fold' to lazy text
    fold :: Fold Text a -> Lazy.Text -> a
    fold (L.Fold step begin done) as = done (Lazy.foldlChunks step begin as)
    {-# INLINABLE fold #-}

    {-| Get the first character of a text stream or return 'Nothing' if the stream
        is empty
    -}
    head :: Fold Text (Maybe Char)
    head = L.Fold step Nothing' lazy
      where
        step mc txt =
            if T.null txt
            then mc
            else case mc of
                Just' _  -> mc
                Nothing' -> Just' (T.head txt)
    {-# INLINABLE head #-}

    {-| Get the last character of a text stream or return 'Nothing' if the text
        stream is empty
    -}
    last :: Fold Text (Maybe Char)
    last = L.Fold step Nothing' lazy
      where
        step mc txt =
            if T.null txt
            then mc
            else Just' (T.last txt)
            -- TODO: Use `unsafeLast` when Debian Stable Haskell Platform has it
    {-# INLINABLE last #-}

    -- | Returns 'True' if the text stream is empty, 'False' otherwise
    null :: Fold Text Bool
    null = L.Fold step True id
      where
        step isNull txt = isNull && T.null txt
    {-# INLINABLE null #-}

    -- | Return the length of the text stream in characters
    length :: Num n => Fold Text n
    length = L.Fold (\n txt -> n + fromIntegral (T.length txt)) 0 id
    {-# INLINABLE length #-}

    {-| @(all predicate)@ returns 'True' if all characters satisfy the predicate,
        'False' otherwise
    -}
    all :: (Char -> Bool) -> Fold Text Bool
    all predicate = L.Fold (\b txt -> b && T.all predicate txt) True id
    {-# INLINABLE all #-}

    {-| @(any predicate)@ returns 'True' if any character satisfies the predicate,
        'False' otherwise
    -}
    any :: (Char -> Bool) -> Fold Text Bool
    any predicate = L.Fold (\b txt -> b || T.any predicate txt) False id
    {-# INLINABLE any #-}

    -- | Computes the maximum character
    maximum :: Fold Text (Maybe Char)
    maximum = L.Fold step Nothing' lazy
      where
        step mc txt =
            if T.null txt
            then mc
            else Just' (case mc of
                Nothing' -> T.maximum txt
                Just' c -> max c (T.maximum txt) )
    {-# INLINABLE maximum #-}

    -- | Computes the minimum character
    minimum :: Fold Text (Maybe Char)
    minimum = L.Fold step Nothing' lazy
      where
        step mc txt =
            if T.null txt
            then mc
            else Just' (case mc of
                Nothing' -> T.minimum txt
                Just' c -> min c (T.minimum txt) )
    {-# INLINABLE minimum #-}

    {-| @(elem c)@ returns 'True' if the text stream has a character equal to @c@,
        'False' otherwise
    -}
    elem :: Char -> Fold Text Bool
    elem c = any (c ==)
    {-# INLINABLE elem #-}

    {-| @(notElem c)@ returns 'False' if the text stream has a character equal to
        @c@, 'True' otherwise
    -}
    notElem :: Char -> Fold Text Bool
    notElem c = all (c /=)
    {-# INLINABLE notElem #-}

    {-| @(find predicate)@ returns the first character that satisfies the predicate
        or 'Nothing' if no character satisfies the predicate
    -}
    find :: (Char -> Bool) -> Fold Text (Maybe Char)
    find predicate = L.Fold step Nothing' lazy
      where
        step mc txt = case mc of
            Nothing' -> strict (T.find predicate txt)
            Just' _  -> mc
    {-# INLINABLE find #-}

    {-| @(index n)@ returns the @n@th character of the text stream, or 'Nothing' if
        the stream has an insufficient number of characters
    -}
    index :: Integral n => n -> Fold Text (Maybe Char)
    index i = L.Fold step (Left' (fromIntegral i)) hush
      where
        step x txt = case x of
            Left' remainder ->
                let len = T.length txt
                in  if remainder < len
                    then Right' (T.index txt remainder)
                    else Left'  (remainder - len)
            _               -> x
    {-# INLINABLE index #-}

    {-| @(elemIndex c)@ returns the index of the first character that equals @c@,
        or 'Nothing' if no character matches
    -}
    elemIndex :: Num n => Char -> Fold Text (Maybe n)
    elemIndex c = findIndex (c ==)
    {-# INLINABLE elemIndex #-}

    {-| @(findIndex predicate)@ returns the index of the first character that
        satisfies the predicate, or 'Nothing' if no character satisfies the
        predicate
    -}
    findIndex :: Num n => (Char -> Bool) -> Fold Text (Maybe n)
    findIndex predicate = L.Fold step (Left' 0) hush
      where
        step x txt = case x of
            Left' m -> case T.findIndex predicate txt of
                Nothing -> Left'  (m + fromIntegral (T.length txt))
                Just n  -> Right' (m + fromIntegral n)
            _       -> x
    {-# INLINABLE findIndex #-}

    -- | @(count c)@ returns the number of times @c@ appears
    count :: Num n => Char -> Fold Text n
    count c = L.Fold step 0 id
      where
        step n txt = n + fromIntegral (T.count (T.singleton c) txt)
    {-# INLINABLE count #-}

    {- $reexports
        "Control.Foldl" re-exports the 'Fold' type

        @Data.Text@ re-exports the 'Text' type
    -}