-- |
-- Module      : Foundation.Format.CSV.Builder
-- License     : BSD-style
-- Maintainer  : Foundation
-- Stability   : experimental
-- Portability : portable
--
-- Provies the support for Comma Separated Value

{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}

module Foundation.Format.CSV.Builder
    ( -- * String Bulider
      csvStringBuilder
    , rowStringBuilder
    , fieldStringBuilder
      -- * Block Builder
    , csvBlockBuilder
    , rowBlockBuilder
    , fieldBlockBuilder
      -- * Conduit
    , rowC
    ) where

import           Basement.Imports
import           Basement.String                  (replace)
import           Foundation.Collection.Sequential (Sequential(intersperse))
import           Foundation.Conduit.Internal

import qualified Foundation.String.Builder as String
import           Basement.Block              (Block)
import qualified Basement.Block.Builder    as Block

import           GHC.ST (runST)

import           Foundation.Format.CSV.Types

-- | serialise the CSV document into a UTF8 string
csvStringBuilder :: CSV -> String.Builder
csvStringBuilder = String.unsafeStringBuilder . csvBlockBuilder

rowStringBuilder :: Row -> String.Builder
rowStringBuilder = String.unsafeStringBuilder . rowBlockBuilder

fieldStringBuilder :: Field -> String.Builder
fieldStringBuilder = String.unsafeStringBuilder . fieldBlockBuilder

-- | serialise the CSV document into a UTF8 encoded (Block Word8)
csvBlockBuilder :: CSV -> Block.Builder
csvBlockBuilder = mconcat . intersperse (Block.emitString "\r\n") . fmap rowBlockBuilder . toList . unCSV

rowBlockBuilder :: Row -> Block.Builder
rowBlockBuilder = mconcat . intersperse (Block.emitUTF8Char ',') . fmap fieldBlockBuilder . toList . unRow

fieldBlockBuilder :: Field -> Block.Builder
fieldBlockBuilder (FieldInteger i) = Block.emitString $ show i
fieldBlockBuilder (FieldDouble  d) = Block.emitString $ show d
fieldBlockBuilder (FieldString  s e) = case e of
    NoEscape     -> Block.emitString s
    Escape       -> Block.emitUTF8Char '"' <> Block.emitString s <> Block.emitUTF8Char '"'
    DoubleEscape -> Block.emitUTF8Char '"' <> Block.emitString (replace "\"" "\"\"" s) <> Block.emitUTF8Char '"'

rowC :: (Record row, Monad m) => Conduit row (Block Word8) m ()
rowC = await >>= go
  where
    go Nothing  = pure ()
    go (Just r) =
      let bytes = runST (Block.run $ rowBlockBuilder (toRow r) <> Block.emitString "\r\n")
         in yield bytes >> await >>= go