{-|
Module      : Pipes.KeyValueCsv.Cell
Copyright   : (c) Marcin Mrotek, 2015
License     : BSD3
Maintainer  : marcin.jan.mrotek@gmail.com
Stability   : experimental

Helper functions

-}

{-# LANGUAGE RankNTypes #-}

module Pipes.KeyValueCsv.Internal where

import Pipes.KeyValueCsv.Internal.Types

import Control.Lens
import Control.Monad.State.Strict
import Data.Char
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text.Lazy as Lazy
import Data.Text.Lazy.Builder
import Pipes
import qualified Pipes.Prelude as Pipes
import Pipes.Group (FreeT(..), FreeF(..))
import Pipes.Parse (Parser)
import qualified Pipes.Parse as Parse
import qualified Pipes.Text as PipesText

build :: Monad m => Parser Text m Lazy.Text
-- ^Build a lazy 'Text' from chunks obtained from a pipe.
build = 
  Parse.foldAll 
    (\b t -> b <> fromText t) 
    mempty 
    toLazyText

skipSpace :: Monad m => Parser Text m ()
skipSpace = zoom (PipesText.span isSpace) Parse.skipAll

drawText :: Monad m => Parser Text m Lazy.Text
{-^ 
Draw text that may be enclosed in quotes. 
Skips initial whitespace.
If the first non-white character is not a quote mark, the function draws only the part of the text upto the next white character.
-}
drawText = do
  skipSpace
  quote'm <- PipesText.peekChar
  case quote'm of
    Just '"' -> do
      drawn <- Parse.draw *> zoom (PipesText.break isQuote) build
      pure drawn
    Just  _  -> zoom (PipesText.break isSpace) build
    Nothing  -> pure $ Lazy.empty
 where
  isQuote = (== '"')

breakLines'
  :: Monad m
  => (Text -> Bool)
  -> FreeT (Line m) m r
  -> FreeT (Line m) m (FreeT (Line m) m r)
{-^ 
Break a 'FreeT'-delimited stream of lines into two parts, on a line that satisfies the given predicate.
All input lines up to the breaking one will be fully read, and each (not including the breaking one) will be re-'yield'ed.
-}
breakLines' predicate ls = FreeT $ do
  ft <- runFreeT ls
  case ft of
    p@(Pure _) -> pure . Pure . FreeT $ pure p
    Free (Line line) -> do
      (text, leftovers) <- runStateT (Lazy.toStrict <$> build) line
      r <- runEffect $ leftovers >-> Pipes.drain
      pure $ if predicate text
        then Pure r
        else Free . Line $ do 
          yield text 
          pure $ breakLines' predicate r