{-# language TemplateHaskell #-}

-- | Code that configures presentation parser for the CLI options
module Nix.Options.Parser where

import           Nix.Prelude
import           Relude.Unsafe                  ( read )
import           GHC.Err                        ( errorWithoutStackTrace )
import           Data.Char                      ( isDigit )
import qualified Data.Text                     as Text
import           Data.Time                      ( UTCTime
                                                , defaultTimeLocale
                                                , parseTimeOrError
                                                )
import           Nix.Options
import           Options.Applicative     hiding ( ParserResult(..) )
import           Data.Version                   ( showVersion )
import           Development.GitRev             ( gitCommitDate
                                                , gitBranch
                                                , gitHash )
import           Paths_hnix                     ( version )

decodeVerbosity :: Int -> Verbosity
decodeVerbosity 0 = ErrorsOnly
decodeVerbosity 1 = Informational
decodeVerbosity 2 = Talkative
decodeVerbosity 3 = Chatty
decodeVerbosity 4 = DebugInfo
decodeVerbosity _ = Vomit

argPair :: Mod OptionFields (Text, Text) -> Parser (Text, Text)
argPair =
  option $
    do
      s <- str
      maybe
        (errorWithoutStackTrace "Format of --arg/--argstr in hnix is: name=expr")
        (pure . second Text.tail . (`Text.splitAt` s))
        (Text.findIndex (== '=') s)

nixOptions :: UTCTime -> Parser Options
nixOptions current =
  Options <$>
    (fromMaybe Informational <$>
      optional
        (option

          (do
            a <- str
            bool
              (fail "Argument to -v/--verbose must be a number")
              (pure $ decodeVerbosity $ read a)
              (all isDigit a)
          )

          (  short 'v'
          <> long "verbose"
          <> help "Verbose output"
          )

        )
      )
    <*> switch
        (  long "trace"
        <> help "Enable tracing code (even more can be seen if built with --flags=tracing)"
        )
    <*> switch
        (  long "thunks"
        <> help "Enable reporting of thunk tracing as well as regular evaluation"
        )
    <*> switch
        (  long "values"
        <> help "Enable reporting of value provenance in error messages"
        )
    <*> switch
        (  long "scopes"
        <> help "Enable reporting of scopes in evaluation traces"
        )
    <*> optional
        (strOption
          (  long "reduce"
          <> help "When done evaluating, output the evaluated part of the expression to FILE"
          )
        )
    <*> switch
        (  long "reduce-sets"
        <> help "Reduce set members that aren't used; breaks if hasAttr is used"
        )
    <*> switch
        (  long "reduce-lists"
        <> help "Reduce list members that aren't used; breaks if elemAt is used"
        )
    <*> switch
        (  long "parse"
        <> help "Whether to parse the file (also the default right now)"
        )
    <*> switch
        (  long "parse-only"
        <> help "Whether to parse only, no pretty printing or checking"
        )
    <*> switch
        (  long "find"
        <> help "If selected, find paths within attr trees"
        )
    <*> optional
        (strOption
          (  long "find-file"
          <> help "Look up the given files in Nix's search path"
          )
        )
    <*> switch
        (  long "strict"
        <> help "When used with --eval, recursively evaluate list elements and attributes"
        )
    <*> switch
        (  long "eval"
        <> help "Whether to evaluate, or just pretty-print"
        )
    <*> switch
        (  long "json"
        <> help "Print the resulting value as an JSON representation"
        )
    <*> switch
        (  long "xml"
        <> help "Print the resulting value as an XML representation"
        )
    <*> optional
        (strOption
          (  short 'A'
          <> long "attr"
          <> help "Select an attribute from the top-level Nix expression being evaluated"
          )
        )
    <*> many
        (strOption
          (  short 'I'
          <> long "include"
          <> help "Add a path to the Nix expression search path"
          )
        )
    <*> switch
        (  long "check"
        <> help "Whether to check for syntax fails after parsing"
        )
    <*> optional
        (strOption
          (  long "read"
          <> help "Read in an expression tree from a binary cache"
          )
        )
    <*> switch
        (  long "cache"
        <> help "Write out the parsed expression tree to a binary cache"
        )
    <*> switch
        (  long "repl"
        <> help "After performing any indicated actions, enter the REPL"
        )
    <*> switch
        (  long "ignore-fails"
        <> help "Continue parsing files, even if there are fails"
        )
    <*> optional
        (strOption
          (  short 'E'
          <> long "expr"
          <> help "Expression to parse or evaluate")
        )
    <*> many
        (argPair
          (  long "arg"
          <> help "Argument to pass to an evaluated lambda")
        )
    <*> many
        (argPair
          (  long "argstr"
          <> help "Argument string to pass to an evaluated lambda"
          )
        )
    <*> optional
        (strOption
          (  short 'f'
          <> long "file"
          <> help "Parse all of the files given in FILE; - means stdin"
          )
        )
    <*> option
        (parseTimeOrError True defaultTimeLocale "%Y/%m/%d %H:%M:%S" <$> str)
        (  long "now"
        <> value current
        <> help "Set current time for testing purposes"
        )
    <*> many
        (strArgument
          (  metavar "FILE"
          <> help "Path of file to parse"
          )
        )

--  2020-09-12: CLI --version option mechanism is tied to meta modules specificly generated by Cabal. It is possible to use Template Haskell to resolve the version, as also a g
versionOpt :: Parser (a -> a)
versionOpt = shortVersionOpt <*> debugVersionOpt
 where
  shortVersionOpt :: Parser (a -> a)
  shortVersionOpt =
    infoOption
      (showVersion version)
      (  long "version"
      <> help "Show release version"
      )

  --  2020-09-13: NOTE: Does not work for direct `nix-build`s, works for `nix-shell` `cabal` builds.
  debugVersionOpt :: Parser (a -> a)
  debugVersionOpt =
    infoOption
      ( fold
          [ "Version: ", showVersion version
          , "\nCommit: ", $(gitHash)
          , "\n  date: ", $(gitCommitDate)
          , "\n  branch: ", $(gitBranch)
          ]
      )
      (  long "long-version"
      <> help "Show long debug version form"
      )

nixOptionsInfo :: UTCTime -> ParserInfo Options
nixOptionsInfo current =
  info
    (helper <*> versionOpt <*> nixOptions current)
    (fullDesc <> progDesc mempty <> header "hnix")
