content: {- |
  
  File paths of interest to Dyre, and related values.
  
  -}
  module Config.Dyre.Paths where
  
  import Control.Monad ( filterM )
  import Data.List ( isSuffixOf )
  import System.Info                    (os, arch)
  import System.FilePath
    ( (</>), (<.>), takeExtension, splitExtension )
  import System.Directory
    ( doesDirectoryExist
    , doesFileExist
    , getCurrentDirectory
    , getDirectoryContents
    , getModificationTime
    )
  import System.Environment.XDG.BaseDir (getUserCacheDir, getUserConfigDir)
  import System.Environment.Executable  (getExecutablePath)
  import Data.Time
  
  import Config.Dyre.Params
  import Config.Dyre.Options
  
  
  -- | Data type to make it harder to confuse which path is which.
  data PathsConfig = PathsConfig
    { runningExecutable :: FilePath
    , customExecutable :: FilePath
    , configFile :: FilePath
    -- ^ Where Dyre looks for the custom configuration file.
    , libsDirectory :: FilePath
    -- ^ @<configDir>/libs@.  This directory gets added to the GHC
    -- include path during compilation, so use configurations can be
    -- split up into modules.  Changes to files under this directory
    -- trigger recompilation.
    , cacheDirectory :: FilePath
    -- ^ Where the custom executable, object and interface files, errors
    -- file and other metadata get stored.
    }
  
  -- | Determine a file name for the compiler to write to, based on
  -- the 'customExecutable' path.
  --
  outputExecutable :: FilePath -> FilePath
  outputExecutable path =
    let (base, ext) = splitExtension path
    in base <.> "tmp" <.> ext
  
  -- | Return a 'PathsConfig', which records the current binary, the custom
  --   binary, the config file, and the cache directory.
  getPaths :: Params c r -> IO (FilePath, FilePath, FilePath, FilePath, FilePath)
  getPaths params@Params{projectName = pName} = do
      thisBinary <- getExecutablePath
      debugMode  <- getDebug
      cwd <- getCurrentDirectory
      cacheDir' <- case (debugMode, cacheDir params) of
                        (True,  _      ) -> return $ cwd </> "cache"
                        (False, Nothing) -> getUserCacheDir pName
                        (False, Just cd) -> cd
      confDir   <- case (debugMode, configDir params) of
                        (True,  _      ) -> return cwd
                        (False, Nothing) -> getUserConfigDir pName
                        (False, Just cd) -> cd
      let
        tempBinary =
          cacheDir' </> pName ++ "-" ++ os ++ "-" ++ arch <.> takeExtension thisBinary
        configFile' = confDir </> pName ++ ".hs"
        libsDir = confDir </> "lib"
      pure (thisBinary, tempBinary, configFile', cacheDir', libsDir)
  
  getPathsConfig :: Params cfg a -> IO PathsConfig
  getPathsConfig params = do
    (cur, custom, conf, cache, libs) <- getPaths params
    pure $ PathsConfig cur custom conf libs cache
  
  -- | Check if a file exists. If it exists, return Just the modification
  --   time. If it doesn't exist, return Nothing.
  maybeModTime :: FilePath -> IO (Maybe UTCTime)
  maybeModTime path = do
      fileExists <- doesFileExist path
      if fileExists
         then Just <$> getModificationTime path
         else return Nothing
  
  checkFilesModified :: PathsConfig -> IO Bool
  checkFilesModified paths = do
    confTime <- maybeModTime (configFile paths)
    libFiles <- findHaskellFiles (libsDirectory paths)
    libTimes <- traverse maybeModTime libFiles
    thisTime <- maybeModTime (runningExecutable paths)
    tempTime <- maybeModTime (customExecutable paths)
    pure $
      tempTime < confTime     -- config newer than custom bin
      || tempTime < thisTime  -- main bin newer than custom bin
      || any (tempTime <) libTimes
  
  -- | Recursively find Haskell files (@.hs@, @.lhs@) at the given
  -- location.
  findHaskellFiles :: FilePath -> IO [FilePath]
  findHaskellFiles d = do
    exists <- doesDirectoryExist d
    if exists
      then do
        nodes <- getDirectoryContents d
        let nodes' = map (d </>) . filter (`notElem` [".", ".."]) $ nodes
        files <- filterM isHaskellFile nodes'
        dirs  <- filterM doesDirectoryExist nodes'
        subfiles <- concat <$> traverse findHaskellFiles dirs
        pure $ files ++ subfiles
      else pure []
    where
      isHaskellFile f
        | any (`isSuffixOf` f) [".hs", ".lhs"] = doesFileExist f
        | otherwise = pure False
  