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

-- | Nix configuration
module Stack.Config.Nix
  ( nixCompiler
  , nixCompilerVersion
  , nixOptsFromMonoid
  ) where

import           Control.Monad.Extra ( ifM )
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import           Distribution.System ( OS (..) )
import           Stack.Constants ( osIsWindows )
import           Stack.Prelude
import           Stack.Types.Config ( HasRunner )
import           Stack.Types.Nix ( NixOpts (..), NixOptsMonoid (..) )
import           System.Directory ( doesFileExist )

-- | Type representing exceptions thrown by functions exported by the
-- "Stack.Config.Nix" module.
data ConfigNixException
  = NixCannotUseShellFileAndPackagesException
    -- ^ Nix can't be given packages and a shell file at the same time
  | GHCMajorVersionUnspecified
  | OnlyGHCSupported
  deriving (Show, Typeable)

instance Exception ConfigNixException where
  displayException NixCannotUseShellFileAndPackagesException =
    "Error: [S-2726]\n"
    ++ "You cannot have packages and a shell-file filled at the same time \
       \in your nix-shell configuration."
  displayException GHCMajorVersionUnspecified =
    "Error: [S-9317]\n"
    ++ "GHC major version not specified."
  displayException OnlyGHCSupported =
    "Error: [S-8605]\n"
    ++ "Only GHC is supported by 'stack --nix'."

-- | Interprets NixOptsMonoid options.
nixOptsFromMonoid ::
     HasRunner env
  => NixOptsMonoid
  -> OS
  -> RIO env NixOpts
nixOptsFromMonoid NixOptsMonoid{..} os = do
  let defaultPure = case os of
        OSX -> False
        _ -> True
      nixPureShell = fromFirst defaultPure nixMonoidPureShell
      nixPackages = fromFirst [] nixMonoidPackages
      nixInitFile = getFirst nixMonoidInitFile
      nixShellOptions = fromFirst [] nixMonoidShellOptions
                        ++ prefixAll (T.pack "-I") (fromFirst [] nixMonoidPath)
      nixAddGCRoots   = fromFirstFalse nixMonoidAddGCRoots

  -- Enable Nix-mode by default on NixOS, unless Docker-mode was specified
  osIsNixOS <- isNixOS
  let nixEnable0 = fromFirst osIsNixOS nixMonoidEnable

  nixEnable <- case () of
    _
      | nixEnable0 && osIsWindows -> do
          logInfo
            "Note: Disabling nix integration, since this is being run in Windows"
          pure False
      | otherwise -> pure nixEnable0

  when (not (null nixPackages) && isJust nixInitFile) $
    throwIO NixCannotUseShellFileAndPackagesException
  pure NixOpts{..}
 where
  prefixAll p (x:xs) = p : x : prefixAll p xs
  prefixAll _ _      = []

nixCompiler :: WantedCompiler -> Either ConfigNixException T.Text
nixCompiler compilerVersion =
  case compilerVersion of
    WCGhc version ->
      case T.split (== '.') (fromString $ versionString version) of
        x : y : minor ->
          Right $
          case minor of
            [] ->
              -- The minor version is not specified. Select the latest minor
              -- version in Nixpkgs corresponding to the requested major
              -- version.
              let major = T.concat [x, y] in
              "(let compilers = builtins.filter \
              \(name: builtins.match \
              \\"ghc" <> major <> "[[:digit:]]*\" name != null) \
              \(lib.attrNames haskell.compiler); in \
              \if compilers == [] \
              \then abort \"No compiler found for GHC "
              <> T.pack (versionString version) <> "\"\
              \else haskell.compiler.${builtins.head compilers})"
            _ -> "haskell.compiler.ghc" <> T.concat (x : y : minor)
        _ -> Left GHCMajorVersionUnspecified
    WCGhcjs{} -> Left OnlyGHCSupported
    WCGhcGit{} -> Left OnlyGHCSupported

nixCompilerVersion :: WantedCompiler -> Either ConfigNixException T.Text
nixCompilerVersion compilerVersion =
  case compilerVersion of
    WCGhc version ->
      case T.split (== '.') (fromString $ versionString version) of
        x : y : minor -> Right $ "ghc" <> T.concat (x : y : minor)
        _ -> Left GHCMajorVersionUnspecified
    WCGhcjs{} -> Left OnlyGHCSupported
    WCGhcGit{} -> Left OnlyGHCSupported

isNixOS :: MonadIO m => m Bool
isNixOS = liftIO $ do
  let fp = "/etc/os-release"
  ifM (doesFileExist fp)
      (T.isInfixOf "ID=nixos" <$> TIO.readFile fp)
      (pure False)
