{-|
Module      : Data.Niagra.Builder
Description : Lazy/Eager Text builder
Copyright   : (c) Nathaniel Symer, 2015
License     : MIT
Maintainer  : nate@symer.io
Stability   : experimental
Portability : POSIX

Lazy/Eager 'Text' builder built on top of
text technologies using better data structures
than the original 'Data.Text.Lazy.Builder'.

It uses the 'Seq' data structure to hold chunks
rather than a List, because 'Seq's have O(1)
access to either end of the structure. This is
crucial for accumulating chunks in as little time
possible.

Furthermore, this builder only copies a given sequence
of bytes at most once: from a 'Text','String', or 'Char'
to a buffer.

-}

{-# LANGUAGE Rank2Types, TupleSections #-}
module Data.Niagra.Builder
(
  Builder(..),
  singleton,
  fromString,
  fromText,
  fromLazyText,
  toText,
  toLazyText
)
where

import Data.Niagra.Builder.Internal

import Control.Monad.ST

import Data.Text (Text)
import qualified Data.Text.Lazy as TL

import Data.Foldable
import qualified Data.String as STR

import Data.Sequence (Seq(..), (|>))
import qualified Data.Sequence as S

-- TODO:
-- * Faster string-centric routine

-- |Wrapper around a function that applies changes
-- to a sequence of mutable buffers in 'ST'.
data Builder = Builder {
  runBuilder :: forall s. ((Buffer s, Seq Text) -> ST s (Buffer s, Seq Text))
             -> (Buffer s, Seq Text)
             -> ST s (Buffer s, Seq Text)
}

evalBuilder :: Builder -> ST s [Text]
evalBuilder (Builder f) = do
  (h,t) <- unsafeNewBuffer >>= f return . (,S.empty)
  flushed <- bufferToText h
  return $ toList $ t |> flushed

instance Monoid Builder where
  mempty  = empty
  mappend = appendBuilder
  
instance STR.IsString Builder where
  fromString = fromString

-- | O(1) create an empty 'Builder'
empty :: Builder
empty = Builder $ \f v -> f v

-- biggest overhead comes from the binding operator
-- | O(1) create a 'Builder' from a single 'Char'.
singleton :: Char -> Builder
singleton c = Builder $ \f tup -> snocVec c tup >>= f

-- | O(1) create a 'Builder' from a 'String'.
fromString :: String -> Builder
fromString [] = empty
fromString [x] = singleton x
fromString s = Builder $ \f tup -> foldlM (flip snocVec) tup s >>= f

-- | O(1) create a 'Builder' from a 'Text'.
fromText :: Text -> Builder
fromText t = Builder $ \f tup -> appendVec t tup >>= f

-- | O(1) create a 'Builder' from a lazy 'Text'.
fromLazyText :: TL.Text -> Builder
fromLazyText t = Builder $ \f tup -> foldlM (flip appendVec) tup (TL.toChunks t) >>= f

-- | O(1) append two 'Builder's.
appendBuilder :: Builder -> Builder -> Builder
appendBuilder (Builder a) (Builder b) = Builder $ a . b
  
-- | O(n) Turn a 'Builder' into a 'Text'. While 'singleton', 'fromString',
-- 'fromText', 'empty', and 'appendBuilder' don't do any direct processing,
-- /the function they construct gets evaluated here/. @n@ is the length of
-- the accumulated data to be built into a 'Text'
toText :: Builder -> Text
toText = TL.toStrict . toLazyText 

-- |Lazy version of 'toText'.
toLazyText :: Builder -> TL.Text
toLazyText b = runST $ TL.fromChunks <$> evalBuilder b