content: module Stack2nix.Util
    ( assertMinVer
    , extractVersion
    , mapPool
    , logDebug
    , ensureExecutableExists
    , ensureExecutable
    ) where
  
  import           Control.Concurrent.Async
  import           Control.Concurrent.MSem
  import           Control.Exception            (onException)
  import           Control.Monad                (unless)
  import           Data.Maybe                   (listToMaybe)
  import qualified Data.Traversable             as T
  import           Data.Version                 (Version (..), parseVersion,
                                                 showVersion)
  import           Data.Text                    (pack, strip, unpack)
  import           GHC.Exts                     (sortWith)
  import           Stack2nix.External.Util      (runCmd)
  import           Stack2nix.Types              (Args, argVerbose)
  import           System.Directory             (findExecutable)
  import           System.Environment           (getEnv, setEnv)
  import           System.Exit                  (ExitCode (..))
  import           System.IO                    (hPutStrLn, stderr)
  import           System.Process               (readProcessWithExitCode)
  import           Text.ParserCombinators.ReadP (readP_to_S)
  import           Text.Regex.PCRE              (AllTextMatches (..),
                                                 getAllTextMatches, (=~))
  
  -- Credit: https://stackoverflow.com/a/18898822/204305
  mapPool :: T.Traversable t => Int -> (a -> IO b) -> t a -> IO (t b)
  mapPool max' f xs = do
    sem <- new max'
    mapConcurrently (with sem . f) xs
  
  -- heuristic for parsing version from stdout
  extractVersion :: String -> Maybe Version
  extractVersion str = ver
    where
      firstLine = head . lines $ str
      candidateVers = getAllTextMatches (firstLine =~ "[\\d\\.]+" :: AllTextMatches [] String)
      bestMatch = head . reverse . sortWith length $ candidateVers
      ver = fmap fst . listToMaybe . reverse . readP_to_S parseVersion $ bestMatch
  
  assertMinVer :: String -> String -> IO ()
  assertMinVer prog minVer = do
    hPutStrLn stderr $ unwords ["Ensuring", prog, "version is >=", minVer, "..."]
    result <- runCmd prog ["--version"] `onException` error ("Failed to run " ++ prog ++ ". Not found in PATH.")
    case result of
      (ExitSuccess, out, _) ->
        let ver = extractVersion out in
          unless (ver >= extractVersion minVer) $ error $ unwords ["ERROR:", prog, "version must be", minVer, "or higher. Current version:", maybe "[parse failure]" showVersion ver]
      (ExitFailure _, _, err)  -> error err
  
  logDebug :: Args -> String -> IO ()
  logDebug args msg
    | argVerbose args = hPutStrLn stderr msg
    | otherwise = return ()
  
  
  -- check if executable is present, if not provision it with nix
  ensureExecutableExists :: String -> String -> IO ()
  ensureExecutableExists executable nixAttr = do
    exec <- findExecutable executable
    case exec of
      Just _ -> return ()
      Nothing -> ensureExecutable nixAttr
  
  -- given nixAttr, build it and add $out/bin to $PATH
  ensureExecutable :: String -> IO ()
  ensureExecutable nixAttr = do
    (exitcode2, stdout, err2) <- readProcessWithExitCode "nix-build" ["-A", nixAttr, "<nixpkgs>", "--no-build-output", "--no-out-link"] mempty
    case exitcode2 of
      ExitSuccess -> do
        hPutStrLn stderr $ err2
        path <- getEnv "PATH"
        setEnv "PATH" (unpack (strip (pack stdout)) ++ "/bin" ++ ":" ++ path)
      ExitFailure _ -> error $ nixAttr ++ " failed to build via nix:\n" ++ err2
  