{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}

module Stack.Options.DotParser
  ( dotOptsParser
  , formatSubCommand
  , licenseParser
  , listDepsConstraintsParser
  , listDepsFormatOptsParser
  , listDepsJsonParser
  , listDepsOptsParser
  , listDepsTextParser
  , listDepsTreeParser
  , separatorParser
  , toListDepsOptsParser
  ) where

import           Data.Char ( isSpace )
import           Data.List.Split ( splitOn )
import qualified Data.Set as Set
import qualified Data.Text as T
import           Distribution.Types.PackageName ( mkPackageName )
import           Options.Applicative
                   ( CommandFields, Mod, Parser, auto, command, help, idm, info
                   , long, metavar, option, progDesc, showDefault, strOption
                   , subparser, switch, value
                   )
import           Options.Applicative.Builder.Extra ( boolFlags, textOption )
import           Stack.Dot
                   ( DotOpts (..), ListDepsFormat (..), ListDepsFormatOpts (..)
                   , ListDepsOpts (..)
                   )
import           Stack.Options.BuildParser ( flagsParser, targetsParser )
import           Stack.Prelude

-- | Parser for arguments to `stack dot`
dotOptsParser :: Bool -> Parser DotOpts
dotOptsParser externalDefault = DotOpts
  <$> includeExternal
  <*> includeBase
  <*> depthLimit
  <*> fmap (maybe Set.empty $ Set.fromList . splitNames) prunedPkgs
  <*> targetsParser
  <*> flagsParser
  <*> testTargets
  <*> benchTargets
  <*> globalHints
 where
  includeExternal = boolFlags externalDefault
    "external"
    "inclusion of external dependencies"
    idm
  includeBase = boolFlags True
    "include-base"
    "inclusion of dependencies on base"
    idm
  depthLimit = optional (option auto
    (  long "depth"
    <> metavar "DEPTH"
    <> help "Limit the depth of dependency resolution (Default: No limit)"
    ))
  prunedPkgs = optional (strOption
    (  long "prune"
    <> metavar "PACKAGES"
    <> help "Prune each package name from the comma separated list of package \
            \names PACKAGES"
    ))
  testTargets = switch
    (  long "test"
    <> help "Consider dependencies of test components"
    )
  benchTargets = switch
    (  long "bench"
    <> help "Consider dependencies of benchmark components"
    )

  splitNames :: String -> [PackageName]
  splitNames = map
      ( mkPackageName
      . takeWhile (not . isSpace)
      . dropWhile isSpace
      )
    . splitOn ","

  globalHints = switch
    (  long "global-hints"
    <> help "Do not require an install GHC; instead, use a hints file for \
            \global packages"
    )

separatorParser :: Parser Text
separatorParser = fmap
  escapeSep
  ( textOption
      (  long "separator"
      <> metavar "SEP"
      <> help "Separator between package name and package version."
      <> value " "
      <> showDefault
      )
  )
 where
  escapeSep s = T.replace "\\t" "\t" (T.replace "\\n" "\n" s)

licenseParser :: Parser Bool
licenseParser = boolFlags False
  "license"
  "printing of dependency licenses instead of versions"
  idm

listDepsFormatOptsParser :: Parser ListDepsFormatOpts
listDepsFormatOptsParser = ListDepsFormatOpts
  <$> separatorParser
  <*> licenseParser

listDepsTreeParser :: Parser ListDepsFormat
listDepsTreeParser =  ListDepsTree <$> listDepsFormatOptsParser

listDepsTextParser :: Parser ListDepsFormat
listDepsTextParser = ListDepsText <$> listDepsFormatOptsParser

listDepsJsonParser :: Parser ListDepsFormat
listDepsJsonParser = pure ListDepsJSON

listDepsConstraintsParser :: Parser ListDepsFormat
listDepsConstraintsParser = pure ListDepsConstraints

toListDepsOptsParser :: Parser ListDepsFormat -> Parser ListDepsOpts
toListDepsOptsParser formatParser = ListDepsOpts
  <$> formatParser
  <*> dotOptsParser True

formatSubCommand ::
     String
  -> String
  -> Parser ListDepsFormat
  -> Mod CommandFields ListDepsOpts
formatSubCommand cmd desc formatParser =
  command cmd (info (toListDepsOptsParser formatParser) (progDesc desc))

-- | Parser for arguments to `stack ls dependencies`.
listDepsOptsParser :: Parser ListDepsOpts
listDepsOptsParser = subparser
      (  formatSubCommand
           "text"
           "Print dependencies as text (default)"
           listDepsTextParser
      <> formatSubCommand
           "cabal"
           "Print dependencies as exact Cabal constraints"
           listDepsConstraintsParser
      <> formatSubCommand
           "tree"
           "Print dependencies as tree"
           listDepsTreeParser
      <> formatSubCommand
           "json"
           "Print dependencies as JSON"
           listDepsJsonParser
      )
  <|> toListDepsOptsParser listDepsTextParser
