{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Houseman.Internal
  ( terminateAndWaitForProcess
  , withAllExit
  , withAnyExit
  , withProcess
  ) where

import           Control.Concurrent
import           Control.Exception
import           Control.Monad
import           Data.Maybe
import           GHC.IO.Exception
import           GHC.IO.Handle
import           System.IO
import           System.Process

import           Data.Streaming.Process (Inherited (..), StreamingProcessHandle,
                                         getStreamingProcessExitCode,
                                         streamingProcess,
                                         streamingProcessHandleRaw,
                                         waitForStreamingProcess)

-- | Watch exit of given process handles, and if any of them exited and
-- passed given predicate, runs action.
withAnyExit
  :: (ExitCode -> Bool) -- ^ Predicate
  -> [StreamingProcessHandle] -- ^ Process handles to watch
  -> IO a -- ^ Action to run
  -> IO a
withAnyExit predicate phs action = go
  where
    go = do
      exits <- catMaybes <$> mapM getStreamingProcessExitCode phs
      if any predicate exits
        then action
        else threadDelay 1000 >> go

-- | Watch exit of given process handles, and if all of them exited and
-- passed given predicate, runs action.
withAllExit
  :: (ExitCode -> Bool) -- ^ Predicate
  -> [StreamingProcessHandle] -- ^ Process handles to watch
  -> IO a -- ^ Action to run
  -> IO a
withAllExit predicate phs action = go
  where
    go = do
      exits <- mapM getStreamingProcessExitCode phs
      if all isJust exits && all predicate (catMaybes exits)
        then action
        else threadDelay 1000 >> go

-- | Terminates and waits for given process.
terminateAndWaitForProcess :: StreamingProcessHandle -> IO ExitCode
terminateAndWaitForProcess ph = do
  let ph' = streamingProcessHandleRaw ph
  b <- getStreamingProcessExitCode ph
  case b of
    Just exitcode -> return exitcode
    Nothing -> do
      terminateProcess ph'
      waitForStreamingProcess ph

-- | Runs given process and invoke action with stdout, stderr, process handle from it.
-- stdout and stderr will be closed after action.
withProcess
  :: CreateProcess -- ^ The process
  -> ((Handle, Handle, StreamingProcessHandle) -> IO a) -- ^ Action takes stdout, stderr and process handle
  -> IO a
withProcess p action = bracket before restore action
  where
    before = do
      (Inherited, out :: Handle, err :: Handle, ph) <-
        streamingProcess p { std_out = CreatePipe
                           , std_err = CreatePipe
                           }
      encoding <- mkTextEncoding "utf8"
      forM_ [out,err] $ \h -> do
        hSetBuffering h NoBuffering
        hSetEncoding h encoding
        hSetNewlineMode h universalNewlineMode
      return (out,err,ph)
    restore (out,err,_) = do
      close out
      close err
    close x = do
      c <- hIsClosed x
      unless c $ hClose x