-- |
-- Module      : Streamly.Internal.Console.Stdio
-- Copyright   : (c) 2018 Composewell Technologies
--
-- License     : BSD-3-Clause
-- Maintainer  : streamly@composewell.com
-- Stability   : experimental
-- Portability : GHC

module Streamly.Internal.Console.Stdio
    (
    -- * Streams
      read
    , readChars
    , readChunks
    -- , getChunksLn
    -- , getStringsWith -- get strings using the supplied decoding
    -- , getStrings -- get strings of complete chars,
                  -- leave any partial chars for next string
    -- , getStringsLn -- get lines decoded as char strings

    -- * Unfolds
    , reader
    , chunkReader

    -- * Folds
    , write
    , writeChunks
    , writeErr
    , writeErrChunks

    -- * Stream writes
    , putBytes  -- Buffered (32K)
    , putChars
    , putChunks -- Unbuffered
    , putStringsWith
    , putStrings
    , putStringsLn
    )
where

#include "inline.hs"

import Control.Monad.IO.Class (MonadIO(..))
import Data.Word (Word8)
import System.IO (stdin, stdout, stderr)
import Prelude hiding (read)

import Streamly.Internal.Data.Array.Type (Array(..))
import Streamly.Internal.Data.Stream (Stream)
import Streamly.Internal.Data.Unfold (Unfold)
import Streamly.Internal.Data.Fold (Fold)

import qualified Streamly.Internal.Data.Array as Array
import qualified Streamly.Internal.Data.Stream as Stream
    (intersperseMSuffix)
import qualified Streamly.Internal.Data.Unfold as Unfold
import qualified Streamly.Internal.FileSystem.Handle as Handle
import qualified Streamly.Internal.Unicode.Stream as Unicode

-------------------------------------------------------------------------------
-- Reads
-------------------------------------------------------------------------------

-- | Unfold standard input into a stream of 'Word8'.
--
{-# INLINE reader #-}
reader :: MonadIO m => Unfold m () Word8
reader :: forall (m :: * -> *). MonadIO m => Unfold m () Word8
reader = (() -> Handle) -> Unfold m Handle Word8 -> Unfold m () Word8
forall a c (m :: * -> *) b.
(a -> c) -> Unfold m c b -> Unfold m a b
Unfold.lmap (\() -> Handle
stdin) Unfold m Handle Word8
forall (m :: * -> *). MonadIO m => Unfold m Handle Word8
Handle.reader

-- | Read a byte stream from standard input.
--
-- > read = Handle.read stdin
-- > read = Stream.unfold Stdio.reader ()
--
-- /Pre-release/
--
{-# INLINE read #-}
read :: MonadIO m => Stream m Word8
read :: forall (m :: * -> *). MonadIO m => Stream m Word8
read = Handle -> Stream m Word8
forall (m :: * -> *). MonadIO m => Handle -> Stream m Word8
Handle.read Handle
stdin

-- | Read a character stream from Utf8 encoded standard input.
--
-- > readChars = Unicode.decodeUtf8 Stdio.read
--
-- /Pre-release/
--
{-# INLINE readChars #-}
readChars :: MonadIO m => Stream m Char
readChars :: forall (m :: * -> *). MonadIO m => Stream m Char
readChars = Stream m Word8 -> Stream m Char
forall (m :: * -> *). Monad m => Stream m Word8 -> Stream m Char
Unicode.decodeUtf8 Stream m Word8
forall (m :: * -> *). MonadIO m => Stream m Word8
read

-- | Unfolds standard input into a stream of 'Word8' arrays.
--
{-# INLINE chunkReader #-}
chunkReader :: MonadIO m => Unfold m () (Array Word8)
chunkReader :: forall (m :: * -> *). MonadIO m => Unfold m () (Array Word8)
chunkReader = (() -> Handle)
-> Unfold m Handle (Array Word8) -> Unfold m () (Array Word8)
forall a c (m :: * -> *) b.
(a -> c) -> Unfold m c b -> Unfold m a b
Unfold.lmap (\() -> Handle
stdin) Unfold m Handle (Array Word8)
forall (m :: * -> *). MonadIO m => Unfold m Handle (Array Word8)
Handle.chunkReader

-- | Read a stream of chunks from standard input.  The maximum size of a single
-- chunk is limited to @defaultChunkSize@. The actual size read may be less
-- than @defaultChunkSize@.
--
-- > readChunks = Handle.readChunks stdin
-- > readChunks = Stream.unfold Stdio.chunkReader ()
--
-- /Pre-release/
--
{-# INLINE readChunks #-}
readChunks :: MonadIO m => Stream m (Array Word8)
readChunks :: forall (m :: * -> *). MonadIO m => Stream m (Array Word8)
readChunks = Handle -> Stream m (Array Word8)
forall (m :: * -> *). MonadIO m => Handle -> Stream m (Array Word8)
Handle.readChunks Handle
stdin

{-
-- | Read UTF8 encoded lines from standard input.
--
-- You may want to process the input byte stream directly using appropriate
-- folds for more efficient processing.
--
-- /Pre-release/
--
{-# INLINE getChunksLn #-}
getChunksLn :: MonadIO m => Stream m (Array Word8)
getChunksLn = (Stream.splitWithSuffix (== '\n') f) getChars

    -- XXX Need to implement Fold.unfoldMany, should be easy for
    -- non-terminating folds, but may be tricky for terminating folds. See
    -- Array Stream folds.
    where f = Fold.unfoldMany Unicode.readCharUtf8 Array.write
-}

-------------------------------------------------------------------------------
-- Writes
-------------------------------------------------------------------------------

-- | Fold a stream of 'Word8' to standard output.
--
{-# INLINE write #-}
write :: MonadIO m => Fold m Word8 ()
write :: forall (m :: * -> *). MonadIO m => Fold m Word8 ()
write = Handle -> Fold m Word8 ()
forall (m :: * -> *). MonadIO m => Handle -> Fold m Word8 ()
Handle.write Handle
stdout

-- | Fold a stream of 'Word8' to standard error.
--
{-# INLINE writeErr #-}
writeErr :: MonadIO m => Fold m Word8 ()
writeErr :: forall (m :: * -> *). MonadIO m => Fold m Word8 ()
writeErr = Handle -> Fold m Word8 ()
forall (m :: * -> *). MonadIO m => Handle -> Fold m Word8 ()
Handle.write Handle
stderr

-- | Write a stream of bytes to standard output.
--
-- > putBytes = Handle.putBytes stdout
-- > putBytes = Stream.fold Stdio.write
--
-- /Pre-release/
--
{-# INLINE putBytes #-}
putBytes :: MonadIO m => Stream m Word8 -> m ()
putBytes :: forall (m :: * -> *). MonadIO m => Stream m Word8 -> m ()
putBytes = Handle -> Stream m Word8 -> m ()
forall (m :: * -> *). MonadIO m => Handle -> Stream m Word8 -> m ()
Handle.putBytes Handle
stdout

-- | Encode a character stream to Utf8 and write it to standard output.
--
-- > putChars = Stdio.putBytes . Unicode.encodeUtf8
--
-- /Pre-release/
--
{-# INLINE putChars #-}
putChars :: MonadIO m => Stream m Char -> m ()
putChars :: forall (m :: * -> *). MonadIO m => Stream m Char -> m ()
putChars = Stream m Word8 -> m ()
forall (m :: * -> *). MonadIO m => Stream m Word8 -> m ()
putBytes (Stream m Word8 -> m ())
-> (Stream m Char -> Stream m Word8) -> Stream m Char -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Stream m Char -> Stream m Word8
forall (m :: * -> *). Monad m => Stream m Char -> Stream m Word8
Unicode.encodeUtf8

-- | Fold a stream of @Array Word8@ to standard output.
--
{-# INLINE writeChunks #-}
writeChunks :: MonadIO m => Fold m (Array Word8) ()
writeChunks :: forall (m :: * -> *). MonadIO m => Fold m (Array Word8) ()
writeChunks = Handle -> Fold m (Array Word8) ()
forall (m :: * -> *) a. MonadIO m => Handle -> Fold m (Array a) ()
Handle.writeChunks Handle
stdout

-- | Fold a stream of @Array Word8@ to standard error.
--
{-# INLINE writeErrChunks #-}
writeErrChunks :: MonadIO m => Fold m (Array Word8) ()
writeErrChunks :: forall (m :: * -> *). MonadIO m => Fold m (Array Word8) ()
writeErrChunks = Handle -> Fold m (Array Word8) ()
forall (m :: * -> *) a. MonadIO m => Handle -> Fold m (Array a) ()
Handle.writeChunks Handle
stderr

-- | Write a stream of chunks to standard output.
--
-- > putChunks = Handle.putChunks stdout
-- > putChunks = Stream.fold Stdio.writeChunks
--
-- /Pre-release/
--
{-# INLINE putChunks #-}
putChunks :: MonadIO m => Stream m (Array Word8) -> m ()
putChunks :: forall (m :: * -> *). MonadIO m => Stream m (Array Word8) -> m ()
putChunks = Handle -> Stream m (Array Word8) -> m ()
forall (m :: * -> *) a.
MonadIO m =>
Handle -> Stream m (Array a) -> m ()
Handle.putChunks Handle
stdout

-------------------------------------------------------------------------------
-- Line buffered
-------------------------------------------------------------------------------

-- XXX We need to write transformations as pipes so that they can be applied to
-- folds as well as unfolds/streams. Non-backtracking (one-to-one, one-to-many,
-- filters, reducers) transformations may be easy so we can possibly start with
-- those.
--
-- | Write a stream of strings to standard output using the supplied encoding.
-- Output is flushed to the device for each string.
--
-- /Pre-release/
--
{-# INLINE putStringsWith #-}
putStringsWith :: MonadIO m
    => (Stream m Char -> Stream m Word8) -> Stream m String -> m ()
putStringsWith :: forall (m :: * -> *).
MonadIO m =>
(Stream m Char -> Stream m Word8) -> Stream m String -> m ()
putStringsWith Stream m Char -> Stream m Word8
encode = Stream m (Array Word8) -> m ()
forall (m :: * -> *). MonadIO m => Stream m (Array Word8) -> m ()
putChunks (Stream m (Array Word8) -> m ())
-> (Stream m String -> Stream m (Array Word8))
-> Stream m String
-> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Stream m Char -> Stream m Word8)
-> Stream m String -> Stream m (Array Word8)
forall (m :: * -> *).
MonadIO m =>
(Stream m Char -> Stream m Word8)
-> Stream m String -> Stream m (Array Word8)
Unicode.encodeStrings Stream m Char -> Stream m Word8
encode

-- | Write a stream of strings to standard output using UTF8 encoding.  Output
-- is flushed to the device for each string.
--
-- /Pre-release/
--
{-# INLINE putStrings #-}
putStrings :: MonadIO m => Stream m String -> m ()
putStrings :: forall (m :: * -> *). MonadIO m => Stream m String -> m ()
putStrings = (Stream m Char -> Stream m Word8) -> Stream m String -> m ()
forall (m :: * -> *).
MonadIO m =>
(Stream m Char -> Stream m Word8) -> Stream m String -> m ()
putStringsWith Stream m Char -> Stream m Word8
forall (m :: * -> *). Monad m => Stream m Char -> Stream m Word8
Unicode.encodeUtf8

-- | Like 'putStrings' but adds a newline at the end of each string.
--
-- XXX This is not portable, on Windows we need to use "\r\n" instead.
--
-- /Pre-release/
--
{-# INLINE putStringsLn #-}
putStringsLn :: MonadIO m => Stream m String -> m ()
putStringsLn :: forall (m :: * -> *). MonadIO m => Stream m String -> m ()
putStringsLn =
      Stream m (Array Word8) -> m ()
forall (m :: * -> *). MonadIO m => Stream m (Array Word8) -> m ()
putChunks
    (Stream m (Array Word8) -> m ())
-> (Stream m String -> Stream m (Array Word8))
-> Stream m String
-> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. m (Array Word8) -> Stream m (Array Word8) -> Stream m (Array Word8)
forall (m :: * -> *) a. Monad m => m a -> Stream m a -> Stream m a
Stream.intersperseMSuffix (Array Word8 -> m (Array Word8)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (Array Word8 -> m (Array Word8)) -> Array Word8 -> m (Array Word8)
forall a b. (a -> b) -> a -> b
$ [Word8] -> Array Word8
forall a. Unbox a => [a] -> Array a
Array.fromList [Word8
10])
    (Stream m (Array Word8) -> Stream m (Array Word8))
-> (Stream m String -> Stream m (Array Word8))
-> Stream m String
-> Stream m (Array Word8)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Stream m Char -> Stream m Word8)
-> Stream m String -> Stream m (Array Word8)
forall (m :: * -> *).
MonadIO m =>
(Stream m Char -> Stream m Word8)
-> Stream m String -> Stream m (Array Word8)
Unicode.encodeStrings Stream m Char -> Stream m Word8
forall (m :: * -> *). Monad m => Stream m Char -> Stream m Word8
Unicode.encodeUtf8