-- | Semantics of human player commands.
module Game.LambdaHack.Client.UI.HandleHumanM
  ( cmdSemInCxtOfKM, updateKeyLast
#ifdef EXPOSE_INTERNAL
    -- * Internal operations
  , noRemoteHumanCmd, CmdLeaderNeed, cmdSemantics, cmdSemanticsLeader
  , addNoError, addLeader, weaveLeader
#endif
  ) where

import Prelude ()

import Game.LambdaHack.Core.Prelude

import           Game.LambdaHack.Client.MonadClient
import           Game.LambdaHack.Client.Request
import           Game.LambdaHack.Client.State
import           Game.LambdaHack.Client.UI.HandleHelperM
import           Game.LambdaHack.Client.UI.HandleHumanGlobalM
import           Game.LambdaHack.Client.UI.HandleHumanLocalM
import           Game.LambdaHack.Client.UI.HumanCmd
import qualified Game.LambdaHack.Client.UI.Key as K
import           Game.LambdaHack.Client.UI.MonadClientUI
import           Game.LambdaHack.Client.UI.SessionUI
import           Game.LambdaHack.Common.Types

-- | Commands that are forbidden on a remote level, because they
-- would usually take time when invoked on one, but not necessarily do
-- what the player expects. Note that some commands that normally take time
-- are not included, because they don't take time in aiming mode
-- or their individual sanity conditions include a remote level check.
noRemoteHumanCmd :: HumanCmd -> Bool
noRemoteHumanCmd cmd = case cmd of
  Wait          -> True
  Wait10        -> True
  MoveItem{}    -> True
  Apply{}       -> True
  AlterDir{}    -> True
  AlterWithPointer{} -> True
  MoveOnceToXhair -> True
  RunOnceToXhair -> True
  ContinueToXhair -> True
  _ -> False

updateKeyLast :: K.KM -> HumanCmd -> KeyMacroFrame -> KeyMacroFrame
updateKeyLast km cmd macroFrame = case cmd of
  RepeatLast{} -> macroFrame
  Record{} -> macroFrame
  _ -> macroFrame {keyLast = Just km}

-- | The semantics of human player commands in terms of the client monad,
-- in context of the given @km@ as the last action.
--
-- Some time cosuming commands are enabled even in aiming mode, but cannot be
-- invoked in aiming mode on a remote level (level different than
-- the level of the leader). Commands that require a pointman fail
-- when no leader is designated.
cmdSemInCxtOfKM :: (MonadClient m, MonadClientUI m)
                => K.KM -> HumanCmd -> m (Either MError ReqUI)
cmdSemInCxtOfKM km cmd = do
  modifySession $ \sess ->
    sess {smacroFrame = updateKeyLast km cmd $ smacroFrame sess}
  cmdSemantics cmd

data CmdLeaderNeed m =
    CmdNoNeed (m (Either MError ReqUI))
  | CmdLeader (ActorId -> m (Either MError ReqUI))

cmdSemantics :: (MonadClient m, MonadClientUI m)
             => HumanCmd -> m (Either MError ReqUI)
cmdSemantics cmd = case cmdSemanticsLeader cmd of
  CmdNoNeed mreq -> mreq
  CmdLeader f -> do
    mleader <- getsClient sleader
    case mleader of
      Nothing -> weaveJust <$> failWith
        "command disabled when no pointman designated, choose another command"
      Just leader -> do
        if noRemoteHumanCmd cmd then do
          -- If in aiming mode, check if the current level is the same
          -- as player level and refuse performing the action otherwise.
          arena <- getArenaUI
          lidV <- viewedLevelUI
          if arena /= lidV then
            weaveJust <$> failWith
              "command disabled on a remote level, press ESC to switch back"
          else f leader
        else f leader

cmdSemanticsLeader :: (MonadClient m, MonadClientUI m)
                   => HumanCmd -> CmdLeaderNeed m
cmdSemanticsLeader cmd = case cmd of
  Macro kms -> addNoError $ macroHuman kms
  ByArea l -> CmdNoNeed $ byAreaHuman cmdSemInCxtOfKM l
  ByAimMode AimModeCmd{..} ->
    CmdNoNeed $ byAimModeHuman (cmdSemantics exploration) (cmdSemantics aiming)
  ComposeIfLocal cmd1 cmd2 ->
    CmdNoNeed $ composeIfLocalHuman (cmdSemantics cmd1) (cmdSemantics cmd2)
  ComposeUnlessError cmd1 cmd2 ->
    CmdNoNeed $ composeUnlessErrorHuman (cmdSemantics cmd1) (cmdSemantics cmd2)
  Compose2ndLocal cmd1 cmd2 ->
    CmdNoNeed $ compose2ndLocalHuman (cmdSemantics cmd1) (cmdSemantics cmd2)
  LoopOnNothing cmd1 -> CmdNoNeed $ loopOnNothingHuman (cmdSemantics cmd1)
  ExecuteIfClear cmd1 -> CmdNoNeed $ executeIfClearHuman (cmdSemantics cmd1)

  Wait -> weaveLeader $ \leader -> ReqUITimed <$$> waitHuman leader
  Wait10 -> weaveLeader $ \leader -> ReqUITimed <$$> waitHuman10 leader
  Yell -> weaveLeader $ \leader -> ReqUITimed <$$> yellHuman leader
  MoveDir v -> weaveLeader $ \leader ->
                 ReqUITimed <$$> moveRunHuman leader True True False False v
  RunDir v -> weaveLeader $ \leader ->
                ReqUITimed <$$> moveRunHuman leader True True True True v
  RunOnceAhead ->
    CmdLeader $ \leader -> ReqUITimed <$$> runOnceAheadHuman leader
  MoveOnceToXhair -> weaveLeader $ \leader ->
                       ReqUITimed <$$> moveOnceToXhairHuman leader
  RunOnceToXhair  -> weaveLeader $ \leader ->
                       ReqUITimed <$$> runOnceToXhairHuman leader
  ContinueToXhair -> weaveLeader $ \leader ->
                       ReqUITimed <$$> continueToXhairHuman leader
  MoveItem stores toCStore mverb auto ->
    weaveLeader $ \leader ->
      ReqUITimed <$$> moveItemHuman leader stores toCStore mverb auto
  Project -> weaveLeader $ \leader -> ReqUITimed <$$> projectHuman leader
  Apply -> weaveLeader $ \leader -> ReqUITimed <$$> applyHuman leader
  AlterDir -> weaveLeader $ \leader -> ReqUITimed <$$> alterDirHuman leader
  AlterWithPointer ->
    weaveLeader $ \leader -> ReqUITimed <$$> alterWithPointerHuman leader
  CloseDir -> weaveLeader $ \leader -> ReqUITimed <$$> closeDirHuman leader
  Help -> CmdNoNeed $ helpHuman cmdSemInCxtOfKM
  Hint -> CmdNoNeed $ hintHuman cmdSemInCxtOfKM
  ItemMenu -> CmdLeader $ \leader -> itemMenuHuman leader cmdSemInCxtOfKM
  ChooseItemMenu dialogMode ->
    CmdLeader $ \leader -> chooseItemMenuHuman leader cmdSemInCxtOfKM dialogMode
  MainMenu -> CmdNoNeed $ mainMenuHuman cmdSemInCxtOfKM
  MainMenuAutoOn -> CmdNoNeed $ mainMenuAutoOnHuman cmdSemInCxtOfKM
  MainMenuAutoOff -> CmdNoNeed $ mainMenuAutoOffHuman cmdSemInCxtOfKM
  Dashboard -> CmdNoNeed $ dashboardHuman cmdSemInCxtOfKM
  GameDifficultyIncr delta ->
    CmdNoNeed $ gameDifficultyIncr delta >> challengeMenuHuman cmdSemInCxtOfKM
  GameFishToggle ->
    CmdNoNeed $ gameFishToggle >> challengeMenuHuman cmdSemInCxtOfKM
  GameGoodsToggle ->
    CmdNoNeed $ gameGoodsToggle >> challengeMenuHuman cmdSemInCxtOfKM
  GameWolfToggle ->
    CmdNoNeed $ gameWolfToggle >> challengeMenuHuman cmdSemInCxtOfKM
  GameKeeperToggle ->
    CmdNoNeed $ gameKeeperToggle >> challengeMenuHuman cmdSemInCxtOfKM
  GameScenarioIncr delta ->
    CmdNoNeed $ gameScenarioIncr delta >> challengeMenuHuman cmdSemInCxtOfKM

  GameRestart -> CmdNoNeed $ weaveJust <$> gameExitWithHuman Restart
  GameQuit -> CmdNoNeed $ weaveJust <$> gameExitWithHuman Quit
  GameDrop -> CmdNoNeed $ weaveJust <$> fmap Right gameDropHuman
  GameExit -> CmdNoNeed $ weaveJust <$> fmap Right gameExitHuman
  GameSave -> CmdNoNeed $ weaveJust <$> fmap Right gameSaveHuman
  Doctrine -> CmdNoNeed $ weaveJust <$> doctrineHuman
  Automate -> CmdNoNeed $ weaveJust <$> automateHuman
  AutomateToggle -> CmdNoNeed $ weaveJust <$> automateToggleHuman
  AutomateBack -> CmdNoNeed automateBackHuman

  ChooseItem dialogMode ->
    CmdLeader $ \leader -> Left <$> chooseItemHuman leader dialogMode
  ChooseItemProject ts ->
    CmdLeader $ \leader -> Left <$> chooseItemProjectHuman leader ts
  ChooseItemApply ts ->
    CmdLeader $ \leader -> Left <$> chooseItemApplyHuman leader ts
  PickLeader k -> CmdNoNeed $ Left <$> pickLeaderHuman k
  PickLeaderWithPointer ->
    CmdLeader $ fmap Left . pickLeaderWithPointerHuman
  PointmanCycle direction ->
    CmdLeader $ \leader -> Left <$> pointmanCycleHuman leader direction
  PointmanCycleLevel direction ->
    CmdLeader $ \leader -> Left <$> pointmanCycleLevelHuman leader direction
  SelectActor -> addLeader selectActorHuman
  SelectNone -> addNoError selectNoneHuman
  SelectWithPointer -> CmdNoNeed $ Left <$> selectWithPointerHuman
  Repeat n -> addNoError $ repeatHuman n
  RepeatLast n -> addNoError $ repeatLastHuman n
  Record -> addNoError recordHuman
  AllHistory -> addNoError allHistoryHuman
  MarkVision delta ->
    CmdNoNeed $ markVisionHuman delta >> settingsMenuHuman cmdSemInCxtOfKM
  MarkSmell ->
    CmdNoNeed $ markSmellHuman >> settingsMenuHuman cmdSemInCxtOfKM
  MarkSuspect delta ->
    CmdNoNeed $ markSuspectHuman delta >> settingsMenuHuman cmdSemInCxtOfKM
  MarkAnim ->
    CmdNoNeed $ markAnimHuman >> settingsMenuHuman cmdSemInCxtOfKM
  OverrideTut delta ->
    CmdNoNeed $ overrideTutHuman delta >> settingsMenuHuman cmdSemInCxtOfKM
  SettingsMenu -> CmdNoNeed $ settingsMenuHuman cmdSemInCxtOfKM
  ChallengeMenu -> CmdNoNeed $ challengeMenuHuman cmdSemInCxtOfKM
  PrintScreen -> addNoError printScreenHuman

  Cancel -> addNoError cancelHuman
  Accept -> addLeader acceptHuman
  DetailCycle -> addNoError detailCycleHuman
  ClearTargetIfItemClear -> addLeader clearTargetIfItemClearHuman
  ItemClear -> addNoError itemClearHuman
  MoveXhair v k -> CmdNoNeed $ Left <$> moveXhairHuman v k
  AimTgt -> addNoError aimTgtHuman
  AimFloor -> addNoError aimFloorHuman
  AimEnemy -> addNoError aimEnemyHuman
  AimItem -> addNoError aimItemHuman
  AimAscend k -> CmdNoNeed $ Left <$> aimAscendHuman k
  EpsIncr b -> addNoError $ epsIncrHuman b
  XhairUnknown -> CmdLeader $ fmap Left . xhairUnknownHuman
  XhairItem -> CmdLeader $ fmap Left . xhairItemHuman
  XhairStair up -> CmdLeader $ \leader -> Left <$> xhairStairHuman leader up
  XhairPointerFloor -> addNoError xhairPointerFloorHuman
  XhairPointerMute -> addNoError xhairPointerMuteHuman
  XhairPointerEnemy -> addNoError xhairPointerEnemyHuman
  AimPointerFloor -> addNoError aimPointerFloorHuman
  AimPointerEnemy -> addNoError aimPointerEnemyHuman

addNoError :: Monad m => m () -> CmdLeaderNeed m
addNoError cmdCli = CmdNoNeed $ cmdCli >> return (Left Nothing)

addLeader :: Monad m => (ActorId -> m ()) -> CmdLeaderNeed m
addLeader cmdCli =
  CmdLeader $ \leader -> cmdCli leader >> return (Left Nothing)

weaveLeader :: Monad m => (ActorId -> m (FailOrCmd ReqUI)) -> CmdLeaderNeed m
weaveLeader cmdCli = CmdLeader $ fmap weaveJust . cmdCli
