content: -- | Possible causes of failure of request.
  module Game.LambdaHack.Common.ReqFailure
    ( ReqFailure(..)
    , impossibleReqFailure, showReqFailure
    , permittedPrecious, permittedProject, permittedProjectAI, permittedApply
  #ifdef EXPOSE_INTERNAL
      -- * Internal operations
  #endif
    ) where
  
  import Prelude ()
  
  import Game.LambdaHack.Core.Prelude
  
  import           Game.LambdaHack.Common.Item
  import qualified Game.LambdaHack.Common.ItemAspect as IA
  import           Game.LambdaHack.Common.Time
  import qualified Game.LambdaHack.Content.ItemKind as IK
  import qualified Game.LambdaHack.Content.RuleKind as RK
  import qualified Game.LambdaHack.Definition.Ability as Ability
  import           Game.LambdaHack.Definition.Defs
  
  -- | Possible causes of failure of request.
  data ReqFailure =
      MoveUnskilled
    | MoveUnskilledAsleep
    | MoveNothing
    | MeleeUnskilled
    | MeleeSelf
    | MeleeDistant
    | MeleeNotWeapon
    | DisplaceUnskilled
    | DisplaceDistant
    | DisplaceAccess
    | DisplaceMultiple
    | DisplaceDying
    | DisplaceBraced
    | DisplaceImmobile
    | DisplaceSupported
    | AlterUnskilled
    | AlterUnwalked
    | AlterDistant
    | AlterBlockActor
    | AlterBlockItem
    | AlterNothing
    | CloseDistant
    | CloseClosed
    | CloseNothing
    | CloseNonClosable
    | WaitUnskilled
    | YellUnskilled
    | MoveItemUnskilled
    | EqpOverfull
    | EqpStackFull
    | ApplyUnskilled
    | ApplyFood
    | ApplyRead
    | ApplyPeriodic
    | ApplyOutOfReach
    | ApplyCharging
    | ApplyNoEffects
    | ItemNothing
    | ItemNotCalm
    | ItemOverStash
    | NotCalmPrecious
    | ProjectUnskilled
    | ProjectAimOnself
    | ProjectBlockTerrain
    | ProjectBlockActor
    | ProjectLobable
    | ProjectOutOfReach
    | ProjectFinderKeeper
    | NoChangeDunLeader
    deriving (Show, Eq)
  
  impossibleReqFailure :: ReqFailure -> Bool
  impossibleReqFailure reqFailure = case reqFailure of
    MoveUnskilled -> False  -- unidentified skill items
    MoveUnskilledAsleep -> False  -- unidentified skill items
    MoveNothing -> True
    MeleeUnskilled -> False  -- unidentified skill items
    MeleeSelf -> True
    MeleeDistant -> True
    MeleeNotWeapon -> False  -- unidentified items
    DisplaceUnskilled -> False  -- unidentified skill items
    DisplaceDistant -> True
    DisplaceAccess -> True
    DisplaceMultiple -> True
    DisplaceDying -> True
    DisplaceBraced -> True
    DisplaceImmobile -> False  -- unidentified skill items
    DisplaceSupported -> False
    AlterUnskilled -> False  -- unidentified skill items
    AlterUnwalked -> False
    AlterDistant -> True
    AlterBlockActor -> True  -- adjacent actor always visible
    AlterBlockItem -> True  -- adjacent item always visible
    AlterNothing -> True  -- if tile known, its properties known
    CloseDistant -> True
    CloseClosed -> True
    CloseNothing -> True
    CloseNonClosable -> True
    WaitUnskilled -> False  -- unidentified skill items
    YellUnskilled -> False  -- unidentified skill items
    MoveItemUnskilled -> False  -- unidentified skill items
    EqpOverfull -> True
    EqpStackFull -> True
    ApplyUnskilled -> False  -- unidentified skill items
    ApplyFood -> False  -- unidentified skill items
    ApplyRead -> False  -- unidentified skill items
    ApplyPeriodic -> False  -- unidentified skill items
    ApplyOutOfReach -> True
    ApplyCharging -> False  -- if aspect record unknown, charging unknown
    ApplyNoEffects -> False  -- if effects unknown, can't prevent it
    ItemNothing -> True
    ItemNotCalm -> False  -- unidentified skill items
    ItemOverStash -> True
    NotCalmPrecious -> False  -- unidentified skill items
    ProjectUnskilled -> False  -- unidentified skill items
    ProjectAimOnself -> True
    ProjectBlockTerrain -> True  -- adjacent terrain always visible
    ProjectBlockActor -> True  -- adjacent actor always visible
    ProjectLobable -> False  -- unidentified skill items
    ProjectOutOfReach -> True
    ProjectFinderKeeper -> False
    NoChangeDunLeader -> True
  
  showReqFailure :: ReqFailure -> Text
  showReqFailure reqFailure = case reqFailure of
    MoveUnskilled -> "too low movement stat; use equipment menu to take off stat draining gear or switch to another teammate or wait until a stat draining condition passes as seen in organ menu"
    MoveUnskilledAsleep -> "actor asleep; yawn to wake up"
    MoveNothing -> "wasting time on moving into obstacle"
    MeleeUnskilled -> "too low melee combat stat"
    MeleeSelf -> "trying to melee oneself"
    MeleeDistant -> "trying to melee a distant foe"
    MeleeNotWeapon -> "trying to melee with not a weapon"
    DisplaceUnskilled -> "too low actor displacing stat"
    DisplaceDistant -> "trying to displace a distant actor"
    DisplaceAccess -> "trying to switch places without access"
    DisplaceMultiple -> "trying to displace multiple actors"
    DisplaceDying -> "trying to displace a dying foe"
    DisplaceBraced -> "trying to displace a braced foe"
    DisplaceImmobile -> "trying to displace an immobile foe"
    DisplaceSupported -> "trying to displace a foe supported by teammates or supply stash"
    AlterUnskilled -> "modify stat is needed to search or activate or transform terrain"
    AlterUnwalked -> "too low modify stat to enter or activate or transform terrain; find and equip gear that improves the stat or try with a teammate whose skill menu shows a higher stat"
    AlterDistant -> "trying to modify distant terrain"
    AlterBlockActor -> "blocked by an actor"
    AlterBlockItem -> "jammed by an item"
    AlterNothing -> "wasting time on modifying nothing"
    CloseDistant -> "trying to close a distant terrain"
    CloseClosed -> "already closed"
    CloseNothing -> "no adjacent terrain can be closed"
    CloseNonClosable -> "cannot be closed"
    WaitUnskilled -> "too low wait stat"
    YellUnskilled -> "actors unskilled in waiting cannot yell/yawn"
    MoveItemUnskilled -> "too low item moving stat"
    EqpOverfull -> "cannot equip any more items"
    EqpStackFull -> "cannot equip the whole item stack"
    ApplyUnskilled -> "too low item triggering stat"
    ApplyFood -> "trigger stat 1 is enough only to eat food from the ground and trigger simple appendages"
    ApplyRead -> "activating cultural artifacts requires trigger stat 3"
    ApplyPeriodic -> "manually activating periodic items requires trigger stat 4"
    ApplyOutOfReach -> "cannot trigger an item out of reach"
    ApplyCharging -> "cannot trigger an item that is still charging"
    ApplyNoEffects -> "cannot trigger an item that produces no effect"
    ItemNothing -> "wasting time on void item manipulation"
    ItemNotCalm -> "you try to focus on your equipment but your calm fails you"
    ItemOverStash -> "you roll in your hoard a little"
    NotCalmPrecious -> "you are too distracted to handle such an exquisite item"
    ProjectUnskilled -> "too low item flinging stat"
    ProjectAimOnself -> "cannot aim at oneself"
    ProjectBlockTerrain -> "aiming obstructed by terrain"
    ProjectBlockActor -> "aiming blocked by an actor"
    ProjectLobable -> "flinging a lobable item that stops at target position requires fling stat 3"
    ProjectOutOfReach -> "cannot aim an item out of reach"
    ProjectFinderKeeper -> "flinging any projectile you've found is out of the question; you prefer to keep them pristine and safe"
    NoChangeDunLeader -> "no manual level change for your team"
  
  -- The item should not be applied nor thrown because it's too delicate
  -- to operate when not calm or because it's too precious to identify by use.
  permittedPrecious :: Bool -> Bool -> ItemFull -> Either ReqFailure Bool
  permittedPrecious forced calmE itemFull@ItemFull{itemDisco} =
    let arItem = aspectRecordFull itemFull
        isPrecious = IA.checkFlag Ability.Precious arItem
    in if not forced && not calmE && isPrecious
       then Left NotCalmPrecious
       else Right $ IA.checkFlag Ability.Durable arItem
                    || case itemDisco of
                         ItemDiscoFull{} -> True
                         ItemDiscoMean itemAspectMean ->
                           IA.kmConst itemAspectMean || not isPrecious
  
  -- Simplified, faster version, for inner AI loop.
  permittedPreciousAI :: Bool -> ItemFull -> Bool
  permittedPreciousAI calmE itemFull@ItemFull{itemDisco} =
    let arItem = aspectRecordFull itemFull
        isPrecious = IA.checkFlag Ability.Precious arItem
    in (calmE || not isPrecious)
       && IA.checkFlag Ability.Durable arItem
          || case itemDisco of
               ItemDiscoFull{} -> True
               ItemDiscoMean itemAspectMean ->
                 IA.kmConst itemAspectMean || not isPrecious
  
  permittedProject :: Bool -> Int -> Bool -> ItemFull -> Either ReqFailure Bool
  permittedProject forced skill calmE itemFull =
   let arItem = aspectRecordFull itemFull
   in if | not forced && skill < 1 -> Left ProjectUnskilled
         | not forced
           && IA.checkFlag Ability.Lobable arItem
           && skill < 3 -> Left ProjectLobable
         | otherwise -> permittedPrecious forced calmE itemFull
  
  -- Simplified, faster and more permissive version, for inner AI loop.
  permittedProjectAI :: Int -> Bool -> ItemFull -> Bool
  permittedProjectAI skill calmE itemFull =
   let arItem = aspectRecordFull itemFull
   in if | skill < 1 -> False
         | IA.checkFlag Ability.Lobable arItem
           && skill < 3 -> False
         | otherwise -> permittedPreciousAI calmE itemFull
  
  permittedApply :: RK.RuleContent -> Time -> Int -> Bool -> Maybe CStore
                 -> ItemFull -> ItemQuant
                 -> Either ReqFailure Bool
  permittedApply corule localTime skill calmE mstore
                 itemFull@ItemFull{itemKind, itemSuspect} kit =
    if | skill < 1 -> Left ApplyUnskilled
       | skill < 2
         && IK.isymbol itemKind /= IK.rsymbolNecklace (RK.ritemSymbols corule)
         && (IK.isymbol itemKind /= IK.rsymbolFood (RK.ritemSymbols corule)
             || mstore /= Just CGround) -> Left ApplyFood
       | skill < 3
         && IK.isymbol itemKind == IK.rsymbolScroll (RK.ritemSymbols corule) ->
           Left ApplyRead
       | skill < 4
         && let arItem = aspectRecordFull itemFull
            in IA.checkFlag Ability.Periodic arItem -> Left ApplyPeriodic
       -- If the item is discharged, neither the kinetic hit nor
       -- any effects activate, so there's no point triggering.
       -- Note that if client doesn't know the timeout, here we may leak the fact
       -- that the item is still charging, but the client risks destruction
       -- if the item is, in fact, recharged and is not durable
       -- (likely in case of jewellery). So it's OK (the message may be
       -- somewhat alarming though).
       | not $ hasCharge localTime kit -> Left ApplyCharging
       | otherwise ->
         if null (IK.ieffects itemKind)
            && (not itemSuspect || IA.isHumanTrinket itemKind)
         then Left ApplyNoEffects
         else permittedPrecious False calmE itemFull
  