module Reanimate.Misc
  ( requireExecutable
  , runCmd
  , runCmd_
  , runCmdLazy
  , withTempDir
  , withTempFile
  ) where

import           Control.Exception (evaluate, finally)
import qualified Data.Text         as T
import qualified Data.Text.IO      as T
import           System.Directory  (createDirectory, findExecutable,
                                    getTemporaryDirectory, removeFile)
import           System.Exit       (ExitCode (..))
import           System.FilePath   ((<.>), (</>))
import           System.IO         (hClose, hGetContents, hIsEOF, openTempFile)
import           System.Process    (readProcessWithExitCode,
                                    runInteractiveProcess, showCommandForUser,
                                    terminateProcess, waitForProcess)

requireExecutable :: String -> IO FilePath
requireExecutable exec = do
  mbPath <- findExecutable exec
  case mbPath of
    Nothing   -> error $ "Couldn't find executable: " ++ exec
    Just path -> return path

runCmd :: FilePath -> [String] -> IO ()
runCmd exec args = do
  _ <- runCmd_ exec args
  return ()

runCmd_ :: FilePath -> [String] -> IO (Either String String)
runCmd_ exec args = do
  (ret, stdout, stderr) <- readProcessWithExitCode exec args ""
  _ <- evaluate (length stdout + length stderr)
  case ret of
    ExitSuccess -> return (Right stdout)
    ExitFailure err -> do
      return $ Left $
        "Failed to run: " ++ showCommandForUser exec args ++ "\n" ++
        "Error code: " ++ show err ++ "\n" ++
        "stderr: " ++ stderr

runCmdLazy :: FilePath -> [String] -> (IO (Either String T.Text) -> IO a) -> IO a
runCmdLazy exec args handler = do
  (inp, out, err, pid) <- runInteractiveProcess exec args Nothing Nothing
  hClose inp
  let fetch = do
        eof <- hIsEOF out
        if eof
          then do
            stderr <- hGetContents err
            _ <- evaluate (length stderr)
            ret <- waitForProcess pid
            case ret of
              ExitSuccess -> return (Left "")
              ExitFailure errMsg -> do
                return $ Left $
                  "Failed to run: " ++ showCommandForUser exec args ++ "\n" ++
                  "Error code: " ++ show errMsg ++ "\n" ++
                  "stderr: " ++ stderr
          else do
            line <- T.hGetLine out
            return (Right line)
  handler fetch `finally` do
    terminateProcess pid
    _ <- waitForProcess pid
    return ()

withTempDir :: (FilePath -> IO a) -> IO a
withTempDir action = do
  dir <- getTemporaryDirectory
  (path, handle) <- openTempFile dir "reanimate"
  hClose handle
  removeFile path
  createDirectory (dir </> path)
  action (dir </> path) -- `finally` removeDirectoryRecursive (dir </> path)

withTempFile :: String -> (FilePath -> IO a) -> IO a
withTempFile ext action = do
  dir <- getTemporaryDirectory
  (path, handle) <- openTempFile dir ("reanimate" <.> ext)
  hClose handle
  action path -- `finally` removeFile path