{-# LANGUAGE OverloadedStrings #-}

-- | Convenience module for debugging streams. Provides stream transformers
-- that wrap 'InputStream's and 'OutputStream's, sending a description of all
-- data to an 'OutputStream' for debugging.

module System.IO.Streams.Debug
 ( -- * Debuggers
   debugInput
 , debugOutput
 , debugInputBS
 , debugOutputBS
 ) where

------------------------------------------------------------------------------
import           Data.ByteString.Char8      (ByteString)
import qualified Data.ByteString.Char8      as S
------------------------------------------------------------------------------
import           System.IO.Streams.Internal (InputStream (..), OutputStream)
import qualified System.IO.Streams.Internal as Streams


------------------------------------------------------------------------------
debugInput ::
     (a -> ByteString)         -- ^ function to convert stream elements to
                               --   'ByteString'
  -> ByteString                -- ^ name of this debug stream, will be
                               --   prepended to debug output
  -> OutputStream ByteString   -- ^ stream the debug info will be sent to
  -> InputStream a             -- ^ input stream
  -> IO (InputStream a)
debugInput toBS name debugStream inputStream = return $ InputStream produce pb
  where
    produce = do
        m <- Streams.read inputStream
        Streams.write (Just $! describe m) debugStream
        return m

    pb c = do
        let s = S.concat [name, ": pushback: ", toBS c, "\n"]
        Streams.write (Just s) debugStream
        Streams.unRead c inputStream

    describe m = S.concat [name, ": got ", describeChunk m, "\n"]

    describeChunk Nothing = "EOF"
    describeChunk (Just s) = S.concat [ "chunk: ", toBS s ]


------------------------------------------------------------------------------
debugInputBS ::
     ByteString                -- ^ name of this debug stream, will be
                               --   prepended to debug output
  -> OutputStream ByteString   -- ^ stream the debug info will be sent to
  -> InputStream ByteString    -- ^ input stream
  -> IO (InputStream ByteString)
debugInputBS = debugInput condense


------------------------------------------------------------------------------
debugOutput :: (a -> ByteString)        -- ^ function to convert stream
                                        --   elements to 'ByteString'
            -> ByteString               -- ^ name of this debug stream, will be
                                        --   prepended to debug output
            -> OutputStream ByteString  -- ^ debug stream
            -> OutputStream a           -- ^ output stream
            -> IO (OutputStream a)
debugOutput toBS name debugStream outputStream =
    Streams.makeOutputStream f
  where
    f m = do
        Streams.write (Just $ describe m) debugStream
        Streams.write m outputStream

    describe m = S.concat [name, ": got ", describeChunk m, "\n"]

    describeChunk Nothing = "EOF"
    describeChunk (Just s) = S.concat [ "chunk: ", toBS s]


------------------------------------------------------------------------------
debugOutputBS ::
     ByteString                -- ^ name of this debug stream, will be
                               --   prepended to debug output
  -> OutputStream ByteString   -- ^ stream the debug info will be sent to
  -> OutputStream ByteString    -- ^ output stream
  -> IO (OutputStream ByteString)
debugOutputBS = debugOutput condense


------------------------------------------------------------------------------
condense :: ByteString -> ByteString
condense s | l < 32 = S.concat [ "\"", s, "\"" ]
           | otherwise = S.concat [
                           "\""
                         , S.take k s
                         , " ... "
                         , S.drop (l - k) s
                         , "\" ("
                         , S.pack (show l)
                         , " bytes)"
                         ]
  where
    k = 14
    l = S.length s
