{-# LANGUAGE LambdaCase #-}

module Di.Backend
 ( mkDiStringStderr
 , mkDiStringHandle
 ) where

import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Monoid (mconcat, mappend, (<>))
import Data.String (IsString(fromString))
import qualified Data.Time as Time
import Prelude hiding (log, filter)
import qualified System.IO as IO

import Di.Core (Di, mkDi, contrapath)

--------------------------------------------------------------------------------

-- | Strings separated by a forward slash. Doesn't contain white space.
--
-- Use 'fromString' (GHC's @OverloadedStrings@ extension) to construct a
-- 'StringPath'.
newtype StringPath = StringPath { unStringPath :: String }
  deriving (Eq, Ord, Show)

instance IsString StringPath where
  fromString = stringPathSingleton
  {-# INLINE fromString #-}

stringPathSingleton :: String -> StringPath
stringPathSingleton = \s -> StringPath (map f s)
  where f :: Char -> Char
        f = \case '/'  -> '.'
                  ' '  -> '_'
                  '\n' -> '_'
                  '\r' -> '_'
                  c    -> c

instance Monoid StringPath where
  mempty = StringPath ""
  mappend (StringPath "") b = b
  mappend a (StringPath "") = a
  mappend (StringPath a) (StringPath b) = StringPath (a <> "/" <> b)

-- | 'String's are written to 'IO.Handle' using the 'IO.Handle''s locale
-- encoding.
mkDiStringHandle
  :: (MonadIO m)
  => IO.Handle
  -> m (Di String String String)
mkDiStringHandle h = liftIO $ do
    IO.hSetBuffering h IO.LineBuffering
    fmap (contrapath stringPathSingleton) $ mkDi $ \ts l p m -> do
       IO.hPutStrLn h $ mconcat
          [ l, " ", renderIso8601 ts
          , if p == mempty then "" else (" " <> unStringPath p)
          , ": ", noBreaks m ]
       IO.hFlush h
  where
    noBreaks :: String -> String
    noBreaks = concatMap $ \case
      '\n' -> "\\n"
      '\r' -> "\\r"
      c -> [c]

-- | 'String' is written to 'IO.stderr' using the system's locale encoding.
mkDiStringStderr :: MonadIO m => m (Di String String String)
mkDiStringStderr = mkDiStringHandle IO.stderr

--------------------------------------------------------------------------------

renderIso8601 :: Time.UTCTime -> String
renderIso8601 = Time.formatTime Time.defaultTimeLocale "%Y-%m-%dT%H:%M:%S.%qZ"