{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE FlexibleContexts  #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}

-- | Handy path information.
module Stack.Path
  ( path
  , pathParser
  ) where

import           Data.List ( intercalate )
import qualified Data.Text as T
import qualified Data.Text.IO as T
import qualified Options.Applicative as OA
import           Path ( (</>), parent )
import           Path.Extra ( toFilePathNoTrailingSep )
import           RIO.Process ( HasProcessContext (..), exeSearchPathL )
import           Stack.Constants
import           Stack.Constants.Config
import           Stack.GhcPkg as GhcPkg
import           Stack.Prelude
import           Stack.Runners
import           Stack.Types.Config
import qualified System.FilePath as FP

-- | Print out useful path information in a human-readable format (and
-- support others later).
path :: [Text] -> RIO Runner ()
path keys = do
  let deprecated = filter ((`elem` keys) . fst) deprecatedPathKeys
  forM_ deprecated $ \(oldOption, newOption) ->
    logWarn $
         "\n"
      <> "'--"
      <> display oldOption
      <> "' will be removed in a future release.\n"
      <> "Please use '--"
      <> display newOption
      <> "' instead.\n"
      <> "\n"
  let -- filter the chosen paths in flags (keys),
      -- or show all of them if no specific paths chosen.
      goodPaths = filter
        ( \(_, key, _) ->
               (null keys && key /= T.pack deprecatedStackRootOptionName)
            || elem key keys
        )
        paths
      singlePath = length goodPaths == 1
      toEither (_, k, UseHaddocks p) = Left (k, p)
      toEither (_, k, WithoutHaddocks p) = Right (k, p)
      (with, without) = partitionEithers $ map toEither goodPaths
  runHaddock True $ printKeys with singlePath
  runHaddock False $ printKeys without singlePath

printKeys ::
     HasEnvConfig env
  => [(Text, PathInfo -> Text)]
  -> Bool
  -> RIO env ()
printKeys extractors single = do
  pathInfo <- fillPathInfo
  liftIO $ forM_ extractors $ \(key, extractPath) -> do
    let prefix = if single then "" else key <> ": "
    T.putStrLn $ prefix <> extractPath pathInfo

runHaddock :: Bool -> RIO EnvConfig () -> RIO Runner ()
runHaddock x action = local modifyConfig $
  withConfig YesReexec $
    withDefaultEnvConfig action
 where
  modifyConfig = set
    (globalOptsL.globalOptsBuildOptsMonoidL.buildOptsMonoidHaddockL) (Just x)

fillPathInfo :: HasEnvConfig env => RIO env PathInfo
fillPathInfo = do
  -- We must use a BuildConfig from an EnvConfig to ensure that it contains the
  -- full environment info including GHC paths etc.
  piBuildConfig <- view $ envConfigL.buildConfigL
  -- This is the modified 'bin-path',
  -- including the local GHC or MSYS if not configured to operate on
  -- global GHC.
  -- It was set up in 'withBuildConfigAndLock -> withBuildConfigExt -> setupEnv'.
  -- So it's not the *minimal* override path.
  piSnapDb <- packageDatabaseDeps
  piLocalDb <- packageDatabaseLocal
  piExtraDbs <- packageDatabaseExtra
  piGlobalDb <- view $ compilerPathsL.to cpGlobalDB
  piSnapRoot <- installationRootDeps
  piLocalRoot <- installationRootLocal
  piToolsDir <- bindirCompilerTools
  piHoogleRoot <- hoogleRoot
  piDistDir <- distRelativeDir
  piHpcDir <- hpcReportDir
  piCompiler <- getCompilerPath
  pure PathInfo {..}

pathParser :: OA.Parser [Text]
pathParser =
  mapMaybeA
    ( \(desc,name,_) ->
        OA.flag Nothing
                (Just name)
                (  OA.long (T.unpack name)
                <> OA.help desc
                )
    )
    paths

-- | Passed to all the path printers as a source of info.
data PathInfo = PathInfo
  { piBuildConfig  :: !BuildConfig
  , piSnapDb       :: !(Path Abs Dir)
  , piLocalDb      :: !(Path Abs Dir)
  , piGlobalDb     :: !(Path Abs Dir)
  , piSnapRoot     :: !(Path Abs Dir)
  , piLocalRoot    :: !(Path Abs Dir)
  , piToolsDir     :: !(Path Abs Dir)
  , piHoogleRoot   :: !(Path Abs Dir)
  , piDistDir      :: Path Rel Dir
  , piHpcDir       :: !(Path Abs Dir)
  , piExtraDbs     :: ![Path Abs Dir]
  , piCompiler     :: !(Path Abs File)
  }

instance HasPlatform PathInfo
instance HasLogFunc PathInfo where
  logFuncL = configL.logFuncL
instance HasRunner PathInfo where
  runnerL = configL.runnerL
instance HasStylesUpdate PathInfo where
  stylesUpdateL = runnerL.stylesUpdateL
instance HasTerm PathInfo where
  useColorL = runnerL.useColorL
  termWidthL = runnerL.termWidthL
instance HasGHCVariant PathInfo
instance HasConfig PathInfo
instance HasPantryConfig PathInfo where
  pantryConfigL = configL.pantryConfigL
instance HasProcessContext PathInfo where
  processContextL = configL.processContextL
instance HasBuildConfig PathInfo where
  buildConfigL = lens piBuildConfig (\x y -> x { piBuildConfig = y })
                 . buildConfigL

data UseHaddocks a = UseHaddocks a | WithoutHaddocks a

-- | The paths of interest to a user. The first tuple string is used
-- for a description that the optparse flag uses, and the second
-- string as a machine-readable key and also for @--foo@ flags. The user
-- can choose a specific path to list like @--stack-root@. But
-- really it's mainly for the documentation aspect.
--
-- When printing output we generate @PathInfo@ and pass it to the
-- function to generate an appropriate string.  Trailing slashes are
-- removed, see #506
paths :: [(String, Text, UseHaddocks (PathInfo -> Text))]
paths =
  [ ( "Global Stack root directory"
    , T.pack stackRootOptionName
    , WithoutHaddocks $ view (stackRootL.to toFilePathNoTrailingSep.to T.pack))
  , ( "Global Stack configuration file"
    , T.pack stackGlobalConfigOptionName
    , WithoutHaddocks $ view (stackGlobalConfigL.to toFilePath.to T.pack))
  , ( "Project root (derived from stack.yaml file)"
    , "project-root"
    , WithoutHaddocks $ view (projectRootL.to toFilePathNoTrailingSep.to T.pack))
  , ( "Configuration location (where the stack.yaml file is)"
    , "config-location"
    , WithoutHaddocks $ view (stackYamlL.to toFilePath.to T.pack))
  , ( "PATH environment variable"
    , "bin-path"
    , WithoutHaddocks $
        T.pack . intercalate [FP.searchPathSeparator] . view exeSearchPathL)
  , ( "Install location for GHC and other core tools (see 'stack ls tools' command)"
    , "programs"
    , WithoutHaddocks $
        view (configL.to configLocalPrograms.to toFilePathNoTrailingSep.to T.pack))
  , ( "Compiler binary (e.g. ghc)"
    , "compiler-exe"
    , WithoutHaddocks $ T.pack . toFilePath . piCompiler )
  , ( "Directory containing the compiler binary (e.g. ghc)"
    , "compiler-bin"
    , WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . parent . piCompiler )
  , ( "Directory containing binaries specific to a particular compiler (e.g. intero)"
    , "compiler-tools-bin"
    , WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . piToolsDir )
  , ( "Directory where Stack installs executables (e.g. ~/.local/bin (Unix-like OSs) or %APPDATA%\\local\\bin (Windows))"
    , "local-bin"
    , WithoutHaddocks $
        view $ configL.to configLocalBin.to toFilePathNoTrailingSep.to T.pack)
  , ( "Extra include directories"
    , "extra-include-dirs"
    , WithoutHaddocks $
        T.intercalate ", " . map T.pack . configExtraIncludeDirs . view configL )
  , ( "Extra library directories"
    , "extra-library-dirs"
    , WithoutHaddocks $
        T.intercalate ", " . map T.pack . configExtraLibDirs . view configL )
  , ( "Snapshot package database"
    , "snapshot-pkg-db"
    , WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . piSnapDb )
  , ( "Local project package database"
    , "local-pkg-db"
    , WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . piLocalDb )
  , ( "Global package database"
    , "global-pkg-db"
    , WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . piGlobalDb )
  , ( "GHC_PACKAGE_PATH environment variable"
    , "ghc-package-path"
    , WithoutHaddocks $
        \pi' -> mkGhcPackagePath
                  True
                  (piLocalDb pi')
                  (piSnapDb pi')
                  (piExtraDbs pi')
                  (piGlobalDb pi')
    )
  , ( "Snapshot installation root"
    , "snapshot-install-root"
    , WithoutHaddocks $
        T.pack . toFilePathNoTrailingSep . piSnapRoot )
  , ( "Local project installation root"
    , "local-install-root"
    , WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . piLocalRoot )
  , ( "Snapshot documentation root"
    , "snapshot-doc-root"
    , UseHaddocks $
        \pi' -> T.pack (toFilePathNoTrailingSep (piSnapRoot pi' </> docDirSuffix))
    )
  , ( "Local project documentation root"
    , "local-doc-root"
    , UseHaddocks $
        \pi' -> T.pack (toFilePathNoTrailingSep (piLocalRoot pi' </> docDirSuffix))
    )
  , ( "Local project documentation root"
    , "local-hoogle-root"
    , UseHaddocks $ T.pack . toFilePathNoTrailingSep . piHoogleRoot)
  , ( "Dist work directory, relative to package directory"
    , "dist-dir"
    , WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . piDistDir )
  , ( "Where HPC reports and tix files are stored"
    , "local-hpc-root"
    , WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . piHpcDir )
  , ( "DEPRECATED: Use '--local-bin' instead"
    , "local-bin-path"
    , WithoutHaddocks $
        T.pack . toFilePathNoTrailingSep . configLocalBin . view configL )
  , ( "DEPRECATED: Use '--programs' instead"
    , "ghc-paths"
    , WithoutHaddocks $
        T.pack . toFilePathNoTrailingSep . configLocalPrograms . view configL )
  , ( "DEPRECATED: Use '--" <> stackRootOptionName <> "' instead"
    , T.pack deprecatedStackRootOptionName
    , WithoutHaddocks $ T.pack . toFilePathNoTrailingSep . view stackRootL )
  ]

deprecatedPathKeys :: [(Text, Text)]
deprecatedPathKeys =
  [ (T.pack deprecatedStackRootOptionName, T.pack stackRootOptionName)
  , ("ghc-paths", "programs")
  , ("local-bin-path", "local-bin")
  ]
