{-

This file is part of the Haskell package cassava-streams. It is
subject to the license terms in the LICENSE file found in the
top-level directory of this distribution and at
git://pmade.com/cassava-streams/LICENSE. No part of cassava-streams
package, including this file, may be copied, modified, propagated, or
distributed except according to the terms contained in the LICENSE
file.

-}

--------------------------------------------------------------------------------
module System.IO.Streams.Csv.Encode
       ( encodeStream
       , encodeStreamWith
       , encodeStreamByName
       , encodeStreamByNameWith
       ) where

--------------------------------------------------------------------------------
import Control.Monad (when)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Lazy as BL
import Data.Csv
import Data.IORef
import System.IO.Streams (OutputStream, makeOutputStream)
import qualified System.IO.Streams as Streams

--------------------------------------------------------------------------------
-- | Create a new @OutputStream@ that can be fed @ToRecord@ values
-- which are converted to CSV.  The records are encoded into
-- @ByteString@s and passed on to the given downstream @OutputStream@.
--
-- Equivalent to @encodeStreamWith defaultEncodeOptions@.
encodeStream :: ToRecord a
             => OutputStream ByteString -- ^ Downstream.
             -> IO (OutputStream a)     -- ^ New @OutputStream@.
encodeStream = encodeStreamWith defaultEncodeOptions

--------------------------------------------------------------------------------
-- | Create a new @OutputStream@ that can be fed @ToRecord@ values
-- which are converted to CSV.  The records are encoded into
-- @ByteString@s and passed on to the given downstream @OutputStream@.
encodeStreamWith :: ToRecord a
                 => EncodeOptions           -- ^ Encoding options.
                 -> OutputStream ByteString -- ^ Downstream.
                 -> IO (OutputStream a)     -- ^ New @OutputStream@.
encodeStreamWith opts output = do
  ref <- newIORef opts
  makeOutputStream (dispatch encodeWith ref output)

--------------------------------------------------------------------------------
-- | Create a new @OutputStream@ which can be fed @ToNamedRecord@
-- values that will be converted into CSV.  The records are encoded
-- into @ByteString@s and passed on to the given downstream
-- @OutputStream@.
--
-- Equivalent to @encodeStreamByNameWith defaultEncodeOptions@.
encodeStreamByName :: ToNamedRecord a
                   => Header                   -- ^ CSV Header.
                   -> OutputStream ByteString  -- ^ Downstream.
                   -> IO (OutputStream a)      -- ^ New @OutputStream@.
encodeStreamByName = encodeStreamByNameWith defaultEncodeOptions

--------------------------------------------------------------------------------
-- | Create a new @OutputStream@ which can be fed @ToNamedRecord@
-- values that will be converted into CSV.  The records are encoded
-- into @ByteString@s and passed on to the given downstream
-- @OutputStream@.
encodeStreamByNameWith :: ToNamedRecord a
                       => EncodeOptions            -- ^ Encoding options.
                       -> Header                   -- ^ CSV Header.
                       -> OutputStream ByteString  -- ^ Downstream.
                       -> IO (OutputStream a)      -- ^ New @OutputStream@.
encodeStreamByNameWith opts hdr output = do
  ref <- newIORef opts
  makeOutputStream $ dispatch (`encodeByNameWith` hdr) ref output

--------------------------------------------------------------------------------
-- | Encode records, ensuring that the header is written no more than once.
dispatch :: (EncodeOptions -> [a] -> BL.ByteString) -- ^ Encoding function.
         -> IORef EncodeOptions                     -- ^ Encoding options.
         -> OutputStream ByteString                 -- ^ Downstream.
         -> Maybe a                                 -- ^ Record to write.
         -> IO ()
dispatch _   _   output Nothing  = Streams.write Nothing output
dispatch enc ref output (Just x) = do
  opts <- readIORef ref
  when (encIncludeHeader opts) $ writeIORef ref (opts {encIncludeHeader = False})
  Streams.writeLazyByteString (enc opts [x]) output