{-# LANGUAGE ApplicativeDo #-}
-- | Parsing of commandline arguments.
module Game.LambdaHack.Server.Commandline
  ( serverOptionsPI
#ifdef EXPOSE_INTERNAL
    -- * Internal operations
  , serverOptionsP
      -- other internal operations too numerous and changing, so not listed
#endif
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude
-- Cabal
import qualified Paths_LambdaHack as Self (version)

import qualified Data.Text as T
import           Data.Version
import           Options.Applicative
import qualified System.Random.SplitMix32 as SM

-- Dependence on ClientOptions is an anomaly. Instead, probably the raw
-- remaining commandline should be passed and parsed by the client to extract
-- client and ui options from and singnal an error if anything was left.

import           Game.LambdaHack.Common.ClientOptions
import           Game.LambdaHack.Common.Faction
import           Game.LambdaHack.Content.ModeKind
import           Game.LambdaHack.Definition.Defs
import qualified Game.LambdaHack.Definition.DefsInternal as DefsInternal
import           Game.LambdaHack.Server.ServerOptions

-- | Parser for server options from commandline arguments.
serverOptionsPI :: ParserInfo ServerOptions
serverOptionsPI = info (serverOptionsP <**> helper <**> version)
                  $ fullDesc
                    <> progDesc "Configure debug options here, gameplay options in configuration file."

version :: Parser (a -> a)
version = infoOption (showVersion Self.version)
  (long "version"
   <> help "Print engine version information")

serverOptionsP :: Parser ServerOptions
serverOptionsP = do
  ~(snewGameSer, scurChalSer)
                    <- serToChallenge <$> newGameP
  sfullscreenMode   <- fullscreenModeP
  knowMap           <- knowMapP
  knowEvents        <- knowEventsP
  knowItems         <- knowItemsP
  showItemSamples   <- showItemSamplesP
  sexposePlaces     <- exposePlacesP
  sexposeItems      <- exposeItemsP
  sexposeActors     <- exposeActorsP
  sniff             <- sniffP
  sallClear         <- allClearP
  sboostRandomItem  <- boostRandItemP
  sgameMode         <- gameModeP
  sautomateAll      <- automateAllP
  skeepAutomated    <- keepAutomatedP
  sstopAfterSeconds <- stopAfterSecsP
  sstopAfterFrames  <- stopAfterFramesP
  sstopAfterGameOver <- stopAfterGameOverP
  sprintEachScreen  <- printEachScreenP
  sbenchmark        <- benchmarkP
  sbenchMessages    <- benchMessagesP
  sdungeonRng       <- setDungeonRngP
  smainRng          <- setMainRngP
  sdumpInitRngs     <- dumpInitRngsP
  sdbgMsgCli        <- dbgMsgCliP
  sdbgMsgSer        <- dbgMsgSerP
  slogPriority      <- logPriorityP
  sassertExplored   <- assertExploredP
  schosenFontset    <- chosenFontsetP
  sallFontsScale    <- allFontsScaleP
  smaxFps           <- maxFpsP
  sdisableAutoYes   <- disableAutoYesP
  snoAnim           <- noAnimP
  ssavePrefixSer    <- savePrefixP
  sfrontendANSI     <- frontendANSIP
  sfrontendTeletype <- frontendTeletypeP
  sfrontendNull     <- frontendNullP
  sfrontendLazy     <- frontendLazyP

  pure ServerOptions
    {
      sclientOptions = ClientOptions
        { sfonts         = []  -- comes only from config file
        , sfontsets      = []  -- comes only from config file
        , stitle         = Nothing
        , snewGameCli    = snewGameSer
        , ssavePrefixCli = ssavePrefixSer
        , ..
        }
    , sknowMap = knowMap || knowEvents || knowItems
    , sknowEvents = knowEvents || knowItems
    , sknowItems = knowItems
    , sshowItemSamples = not (knowEvents || knowItems) && showItemSamples
    , ..
    }
 where
   serToChallenge :: Maybe Int -> (Bool, Challenge)
   serToChallenge Nothing      = (False, defaultChallenge)
   serToChallenge (Just cdiff) = (True, defaultChallenge {cdiff})

knowMapP :: Parser Bool
knowMapP =
  switch (  long "knowMap"
         <> help "Reveal map for all clients in the next game" )

knowEventsP :: Parser Bool
knowEventsP =
  switch (  long "knowEvents"
         <> help "Show all events in the next game (implies --knowMap)" )

knowItemsP :: Parser Bool
knowItemsP =
  switch (  long "knowItems"
         <> help "Auto-identify all items in the next game (implies --knowEvents)" )

exposePlacesP :: Parser Bool
exposePlacesP =
  switch (  long "exposePlaces"
         <> help "Expose all possible places in the next game" )

exposeItemsP :: Parser Bool
exposeItemsP =
  switch (  long "exposeItems"
         <> help "Expose all possible items in the next game" )

exposeActorsP :: Parser Bool
exposeActorsP =
  switch (  long "exposeActors"
         <> help "Expose all killable actors in the next game" )

showItemSamplesP :: Parser Bool
showItemSamplesP =
  switch (  long "showItemSamples"
         <> help "At game over show samples of all items (--knowEvents disables this)" )

sniffP :: Parser Bool
sniffP =
  switch (  long "sniff"
         <> help "Monitor all trafic between server and clients" )

allClearP :: Parser Bool
allClearP =
  switch (  long "allClear"
         <> help "Let all map tiles be translucent" )

boostRandItemP :: Parser Bool
boostRandItemP =
  switch (  long "boostRandomItem"
         <> help "Pick a random item and make it very common" )

gameModeP :: Parser (Maybe (GroupName ModeKind))
gameModeP = optional $ toGameMode <$>
  option nonEmptyStr
         (  long "gameMode"
            <> metavar "MODE"
            <> help "Start next game in the scenario indicated by MODE" )
 where
  -- This ignores all but the first word of a game mode name
  -- and assumes the fist word is present among its frequencies.
  toGameMode :: String -> GroupName ModeKind
  toGameMode = DefsInternal.GroupName . head . T.words . T.pack
  nonEmptyStr :: ReadM String
  nonEmptyStr = eitherReader $ \case
    "" -> Left "name of game mode cannot be empty"
    ns -> Right ns

automateAllP :: Parser Bool
automateAllP =
  switch (  long "automateAll"
         <> help "Give control of all UI teams to computer" )

keepAutomatedP :: Parser Bool
keepAutomatedP =
  switch (  long "keepAutomated"
         <> help "Keep factions automated after game over" )

newGameP :: Parser (Maybe Int)
newGameP = optional $ max 1 . min difficultyBound <$>
  option auto (  long "newGame"
              <> help "Start a new game, overwriting the save file and often forgetting history, with difficulty for all UI players set to N"
              <> metavar "N" )

fullscreenModeP :: Parser (Maybe FullscreenMode)
fullscreenModeP = optional $
  option auto (  long "fullscreenMode"
              <> short 'f'
              <> metavar "MODE"
              <> help "Display in MODE, one of NotFullscreen (default), BigBorderlessWindow (preferred), ModeChange" )

stopAfterSecsP :: Parser (Maybe Int)
stopAfterSecsP = optional $ max 0 <$>
  option auto (  long "stopAfterSeconds"
              <> help "Exit game session after around N seconds"
              <> metavar "N" )

stopAfterFramesP :: Parser (Maybe Int)
stopAfterFramesP = optional $ max 0 <$>
  option auto (  long "stopAfterFrames"
              <> help "Exit game session after around N frames"
              <> metavar "N" )

stopAfterGameOverP :: Parser Bool
stopAfterGameOverP =
  switch (  long "stopAfterGameOver"
         <> help "Exit the application after game over" )

printEachScreenP :: Parser Bool
printEachScreenP =
  switch (  long "printEachScreen"
         <> help "Take a screenshot of each rendered distinct frame (SDL only)" )

benchmarkP :: Parser Bool
benchmarkP =
  switch (  long "benchmark"
         <> help "Restrict file IO, print timing stats" )

benchMessagesP :: Parser Bool
benchMessagesP =
  switch (  long "benchMessages"
         <> help "Display messages in realistic was under AI control (for benchmarks)" )

setDungeonRngP :: Parser (Maybe SM.SMGen)
setDungeonRngP = optional $
  option auto (  long "setDungeonRng"
              <> metavar "RNG_SEED"
              <> help "Set dungeon generation RNG seed to string RNG_SEED" )

setMainRngP :: Parser (Maybe SM.SMGen)
setMainRngP = optional $
  option auto (  long "setMainRng"
              <> metavar "RNG_SEED"
              <> help "Set the main game RNG seed to string RNG_SEED" )

dumpInitRngsP :: Parser Bool
dumpInitRngsP =
  switch (  long "dumpInitRngs"
         <> help "Dump the RNG seeds used to initialize the game" )

dbgMsgCliP :: Parser Bool
dbgMsgCliP =
  switch (  long "dbgMsgCli"
         <> help "Emit extra internal client debug messages" )

dbgMsgSerP :: Parser Bool
dbgMsgSerP =
  switch (  long "dbgMsgSer"
         <> help "Emit extra internal server debug messages" )

logPriorityP :: Parser (Maybe Int)
logPriorityP = optional $
  option (auto >>= verifyLogPriority) $
       long "logPriority"
    <> showDefault
    <> value 5
    <> metavar "N"
    <> help ( "Log only messages of priority at least N, where 1 (all) is "
           ++ "the lowest and 5 logs errors only; use value 0 for testing on "
           ++ "CIs without graphics access; setting priority to 0 causes "
           ++ "SDL frontend to init and quit at once" )
  where
    verifyLogPriority n =
      if n >= 0 && n <= 5
      then return n
      else readerError "N has to be 0 or a positive integer not larger than 5"

assertExploredP :: Parser (Maybe Int)
assertExploredP = optional $ max 1 <$>
  option auto (  long "assertExplored"
              <> help "Check that when the session ends, the indicated level has been explored"
              <> metavar "N" )

chosenFontsetP :: Parser (Maybe Text)
chosenFontsetP = optional $ T.pack <$>
  strOption (  long "fontset"
            <> metavar "FONTSET_ID"
            <> help "Render UI using the given fontset from config file" )

allFontsScaleP :: Parser (Maybe Double)
allFontsScaleP = optional $ max 0 <$>
  option auto (  long "allFontsScale"
              <> metavar "D"
              <> help "Scale all fonts by D, resizing the whole UI" )

maxFpsP :: Parser (Maybe Double)
maxFpsP = optional $ max 0 <$>
  option auto (  long "maxFps"
              <> metavar "D"
              <> help "Display at most D frames per second" )

disableAutoYesP :: Parser Bool
disableAutoYesP =
  switch (  long "disableAutoYes"
         <> help "Never auto-answer prompts, not even when UI faction is automated" )

noAnimP :: Parser (Maybe Bool)
noAnimP =
  flag Nothing (Just True)
       (  long "noAnim"
       <> help "Don't show any animations" )

savePrefixP :: Parser String
savePrefixP =
  strOption (  long "savePrefix"
            <> metavar "PREFIX"
            <> showDefault
            <> value ""
            <> help "Prepend PREFIX to all savefile names" )

frontendANSIP :: Parser Bool
frontendANSIP =
  switch (  long "frontendANSI"
         <> help "Use the ANSI terminal frontend (best for screen readers)" )

frontendTeletypeP :: Parser Bool
frontendTeletypeP =
  switch (  long "frontendTeletype"
         <> help "Use the line terminal frontend (for tests)" )

frontendNullP :: Parser Bool
frontendNullP =
  switch (  long "frontendNull"
         <> help "Use frontend with no display (for benchmarks)" )

frontendLazyP :: Parser Bool
frontendLazyP =
  switch (  long "frontendLazy"
         <> help "Use frontend that not even computes frames (for benchmarks)" )
