-- |
-- 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
    (
    -- * Read
      read
    , getBytes
    , getChars
    , readChunks
    , getChunks
    -- , 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

    -- * Write
    , write
    , writeErr
    , putBytes  -- Buffered (32K)
    , putChars
    , writeChunks
    , writeErrChunks
    , 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.Foreign.Type (Array(..))
import Streamly.Internal.Data.Stream.Serial (SerialT)
import Streamly.Internal.Data.Unfold (Unfold)
import Streamly.Internal.Data.Fold (Fold)

import qualified Streamly.Internal.Data.Array.Foreign as Array
import qualified Streamly.Internal.Data.Stream.IsStream as Stream
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'.
--
-- @since 0.8.0
{-# INLINE read #-}
read :: MonadIO m => Unfold m () Word8
read :: forall (m :: * -> *). MonadIO m => Unfold m () Word8
read = forall a c (m :: * -> *) b.
(a -> c) -> Unfold m c b -> Unfold m a b
Unfold.lmap (\() -> Handle
stdin) forall (m :: * -> *). MonadIO m => Unfold m Handle Word8
Handle.read

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

-- | Read a character stream from Utf8 encoded standard input.
--
-- > getChars = Unicode.decodeUtf8 Stdio.getBytes
--
-- /Pre-release/
--
{-# INLINE getChars #-}
getChars :: MonadIO m => SerialT m Char
getChars :: forall (m :: * -> *). MonadIO m => SerialT m Char
getChars = forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(Monad m, IsStream t) =>
t m Word8 -> t m Char
Unicode.decodeUtf8 forall (m :: * -> *). MonadIO m => SerialT m Word8
getBytes

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

-- | 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@.
--
-- > getChunks = Handle.toChunks stdin
-- > getChunks = Stream.unfold Stdio.readChunks ()
--
-- /Pre-release/
--
{-# INLINE getChunks #-}
getChunks :: MonadIO m => SerialT m (Array Word8)
getChunks :: forall (m :: * -> *). MonadIO m => SerialT m (Array Word8)
getChunks = forall (t :: (* -> *) -> * -> *) (m :: * -> *).
(IsStream t, MonadIO m) =>
Handle -> t m (Array Word8)
Handle.toChunks 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 => SerialT 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.
--
-- @since 0.8.0
{-# INLINE write #-}
write :: MonadIO m => Fold m Word8 ()
write :: forall (m :: * -> *). MonadIO m => Fold m Word8 ()
write = forall (m :: * -> *). MonadIO m => Handle -> Fold m Word8 ()
Handle.write Handle
stdout

-- | Fold a stream of 'Word8' to standard error.
--
-- @since 0.8.0
{-# INLINE writeErr #-}
writeErr :: MonadIO m => Fold m Word8 ()
writeErr :: forall (m :: * -> *). MonadIO m => Fold m Word8 ()
writeErr = 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 => SerialT m Word8 -> m ()
putBytes :: forall (m :: * -> *). MonadIO m => SerialT m Word8 -> m ()
putBytes = forall (m :: * -> *).
MonadIO m =>
Handle -> SerialT 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 => SerialT m Char -> m ()
putChars :: forall (m :: * -> *). MonadIO m => SerialT m Char -> m ()
putChars = forall (m :: * -> *). MonadIO m => SerialT m Word8 -> m ()
putBytes forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(Monad m, IsStream t) =>
t m Char -> t m Word8
Unicode.encodeUtf8

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

-- | Fold a stream of @Array Word8@ to standard error.
--
-- @since 0.8.0
{-# INLINE writeErrChunks #-}
writeErrChunks :: MonadIO m => Fold m (Array Word8) ()
writeErrChunks :: forall (m :: * -> *). MonadIO m => Fold m (Array Word8) ()
writeErrChunks = forall (m :: * -> *) a.
(MonadIO m, Storable a) =>
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 => SerialT m (Array Word8) -> m ()
putChunks :: forall (m :: * -> *). MonadIO m => SerialT m (Array Word8) -> m ()
putChunks = forall (m :: * -> *) a.
(MonadIO m, Storable a) =>
Handle -> SerialT 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
    => (SerialT m Char -> SerialT m Word8) -> SerialT m String -> m ()
putStringsWith :: forall (m :: * -> *).
MonadIO m =>
(SerialT m Char -> SerialT m Word8) -> SerialT m String -> m ()
putStringsWith SerialT m Char -> SerialT m Word8
encode = forall (m :: * -> *). MonadIO m => SerialT m (Array Word8) -> m ()
putChunks forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(MonadIO m, IsStream t) =>
(SerialT m Char -> SerialT m Word8)
-> t m String -> t m (Array Word8)
Unicode.encodeStrings SerialT m Char -> SerialT 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 => SerialT m String -> m ()
putStrings :: forall (m :: * -> *). MonadIO m => SerialT m String -> m ()
putStrings = forall (m :: * -> *).
MonadIO m =>
(SerialT m Char -> SerialT m Word8) -> SerialT m String -> m ()
putStringsWith forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(Monad m, IsStream t) =>
t m Char -> t 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 => SerialT m String -> m ()
putStringsLn :: forall (m :: * -> *). MonadIO m => SerialT m String -> m ()
putStringsLn =
      forall (m :: * -> *). MonadIO m => SerialT m (Array Word8) -> m ()
putChunks
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(IsStream t, Monad m) =>
m a -> t m a -> t m a
Stream.intersperseSuffix (forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. Storable a => [a] -> Array a
Array.fromList [Word8
10])
    forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(MonadIO m, IsStream t) =>
(SerialT m Char -> SerialT m Word8)
-> t m String -> t m (Array Word8)
Unicode.encodeStrings forall (m :: * -> *) (t :: (* -> *) -> * -> *).
(Monad m, IsStream t) =>
t m Char -> t m Word8
Unicode.encodeUtf8