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

-- | Install GHC/GHCJS and Cabal.
module Stack.SetupCmd
    ( setup
    , setupParser
    , SetupCmdOpts(..)
    ) where

import           Control.Applicative
import           Control.Monad.Logger ()
import           Control.Monad.Reader
import qualified Data.Text as T
import qualified Options.Applicative as OA
import qualified Options.Applicative.Builder.Extra as OA
import qualified Options.Applicative.Types as OA
import           Path
import           Stack.Prelude
import           Stack.Setup
import           Stack.Types.Compiler
import           Stack.Types.Config
import           Stack.Types.Version

data SetupCmdOpts = SetupCmdOpts
    { scoCompilerVersion :: !(Maybe (CompilerVersion 'CVWanted))
    , scoForceReinstall  :: !Bool
    , scoUpgradeCabal    :: !(Maybe UpgradeTo)
    , scoSetupInfoYaml   :: !String
    , scoGHCBindistURL   :: !(Maybe String)
    , scoGHCJSBootOpts   :: ![String]
    , scoGHCJSBootClean  :: !Bool
    }

setupYamlCompatParser :: OA.Parser String
setupYamlCompatParser = stackSetupYaml <|> setupInfoYaml
    where stackSetupYaml = OA.strOption (
               OA.long "stack-setup-yaml"
            <> OA.help "DEPRECATED: Use 'setup-info-yaml' instead"
            <> OA.metavar "URL"
            <> OA.hidden )
          setupInfoYaml  = OA.strOption (
               OA.long "setup-info-yaml"
            <> OA.help "Alternate URL or absolute path for stack dependencies"
            <> OA.metavar "URL"
            <> OA.value defaultSetupInfoYaml )

cabalUpgradeParser :: OA.Parser UpgradeTo
cabalUpgradeParser = Specific <$> version' <|> latestParser
    where
        versionReader = do
            s <- OA.readerAsk
            case parseVersion (T.pack s) of
                Nothing -> OA.readerError $ "Invalid version: " ++ s
                Just v  -> return v
        version' = OA.option versionReader (
            OA.long "install-cabal"
         <> OA.metavar "VERSION"
         <> OA.help "Install a specific version of Cabal" )
        latestParser = OA.flag' Latest (
            OA.long "upgrade-cabal"
         <> OA.help "Install latest version of Cabal globally" )

setupParser :: OA.Parser SetupCmdOpts
setupParser = SetupCmdOpts
    <$> OA.optional (OA.argument readVersion
            (OA.metavar "GHC_VERSION" <>
             OA.help ("Version of GHC to install, e.g. 7.10.2. " ++
                      "The default is to install the version implied by the resolver.")))
    <*> OA.boolFlags False
            "reinstall"
            "reinstalling GHC, even if available (incompatible with --system-ghc)"
            OA.idm
    <*> OA.optional cabalUpgradeParser
    <*> setupYamlCompatParser
    <*> OA.optional (OA.strOption
            (OA.long "ghc-bindist"
           <> OA.metavar "URL"
           <> OA.help "Alternate GHC binary distribution (requires custom --ghc-variant)"))
    <*> OA.many (OA.strOption
            (OA.long "ghcjs-boot-options"
           <> OA.metavar "GHCJS_BOOT"
           <> OA.help "Additional ghcjs-boot options"))
    <*> OA.boolFlags True
            "ghcjs-boot-clean"
            "Control if ghcjs-boot should have --clean option present"
            OA.idm
  where
    readVersion = do
        s <- OA.readerAsk
        case parseCompilerVersion ("ghc-" <> T.pack s) of
            Nothing ->
                case parseCompilerVersion (T.pack s) of
                    Nothing -> OA.readerError $ "Invalid version: " ++ s
                    Just x -> return x
            Just x -> return x

setup
    :: (HasConfig env, HasGHCVariant env)
    => SetupCmdOpts
    -> CompilerVersion 'CVWanted
    -> VersionCheck
    -> Maybe (Path Abs File)
    -> RIO env ()
setup SetupCmdOpts{..} wantedCompiler compilerCheck mstack = do
    Config{..} <- view configL
    (_, _, sandboxedGhc) <- ensureCompiler SetupOpts
        { soptsInstallIfMissing = True
        , soptsUseSystem = configSystemGHC && not scoForceReinstall
        , soptsWantedCompiler = wantedCompiler
        , soptsCompilerCheck = compilerCheck
        , soptsStackYaml = mstack
        , soptsForceReinstall = scoForceReinstall
        , soptsSanityCheck = True
        , soptsSkipGhcCheck = False
        , soptsSkipMsys = configSkipMsys
        , soptsUpgradeCabal = scoUpgradeCabal
        , soptsResolveMissingGHC = Nothing
        , soptsSetupInfoYaml = scoSetupInfoYaml
        , soptsGHCBindistURL = scoGHCBindistURL
        , soptsGHCJSBootOpts = scoGHCJSBootOpts ++ ["--clean" | scoGHCJSBootClean]
        }
    let compiler = case wantedCompiler of
            GhcVersion _ -> "GHC"
            GhcjsVersion {} -> "GHCJS"
    if sandboxedGhc
        then logInfo $ "stack will use a sandboxed " <> compiler <> " it installed"
        else logInfo $ "stack will use the " <> compiler <> " on your PATH"
    logInfo "For more information on paths, see 'stack path' and 'stack exec env'"
    logInfo $ "To use this " <> compiler <> " and packages outside of a project, consider using:"
    logInfo "stack ghc, stack ghci, stack runghc, or stack exec"