{- |
Module testing for properties of creatures, whether they are reloading, etc.
Each function takes a creature, and possibly other parameters, and returns a bool.
Note that if there is a world parameter, 
the creature NEED NOT be the same as the creature with that id in the world, 
in fact a creature with that id need not exist.
-}
module Dodge.Creature.Test
    ( crIsAiming
    , crIsReloading
    , crIsArmouredFrom
    , oneH
    , twists
    , twoFlat
    , crWeaponReady
    , crInAimStance
    , crNearPoint
    , isAnimate
    , crCanShoot
    , crHasTargetLOS
    , crAwayFromPost
    , crHasTarget
    , crStratConMatches
    , crSafeDistFromTarg
    )
    where
import Dodge.Data
import Dodge.Base.Collide
import Geometry
import SameConstr

import Data.List (find)
import Data.Maybe
--import qualified Data.IntMap.Strict as IM
import Control.Lens

crIsReloading :: Creature -> Bool
crIsReloading cr = case cr ^? crInvSel . iselAction of
    Just ReloadAction {} -> True
    _ -> False

crWeaponReady :: Creature -> Bool
crWeaponReady cr = fromMaybe False $ do
    ic <- cr ^? crInv . ix (crSel cr) . itConsumption
    return (_laLoaded ic > 0 && _laPrimed ic)

crCanSeeCr :: Creature -> (World, Creature) -> Bool
crCanSeeCr tcr (w,cr) = hasLOS (_crPos cr) (_crPos tcr) w

crIsAiming :: Creature -> Bool
crIsAiming cr = _posture (_crStance cr) == Aiming

crHasTarget :: Creature -> Bool
crHasTarget cr = isJust $ cr ^? crIntention . targetCr . _Just

crHasTargetLOS :: World -> Creature -> Bool
crHasTargetLOS w cr = case cr ^? crIntention . targetCr . _Just of
    Just i -> crCanSeeCr i (w,cr) 
    Nothing -> False

crSafeDistFromTarg :: Float -> Creature -> Bool
crSafeDistFromTarg d cr = case cr ^? crIntention . targetCr . _Just of
    Just tcr -> dist (_crPos cr) (_crPos tcr) > d
    Nothing -> True

crStratConMatches :: Strategy -> Creature -> Bool
crStratConMatches strat cr = eqConstr strat (_apStrategy $ _crActionPlan cr)

crAwayFromPost :: Creature -> Bool
crAwayFromPost cr = case find sentinelGoal . _apGoal $ _crActionPlan cr of
    Just (SentinelAt p _) -> dist p (_crPos cr) > 15
    _ -> False
  where
    sentinelGoal (SentinelAt _ _) = True
    sentinelGoal _ = False

crCanShoot :: Creature -> Bool
crCanShoot cr = crIsAiming cr && crWeaponReady cr

crInAimStance :: AimStance -> Creature -> Bool
crInAimStance as cr = crIsAiming cr
    && cr ^? crInv . ix (crSel cr) . itUse . useAim . aimStance == Just as

oneH :: Creature -> Bool
oneH = crInAimStance OneHand

twoFlat :: Creature -> Bool
twoFlat = crInAimStance TwoHandFlat

twists :: Creature -> Bool
twists = crInAimStance TwoHandTwist

-- the use of crOldPos is because the damage position is calculated on the
-- previous frame
-- Not sure if it is a good idea
crIsArmouredFrom :: Point2 -> Creature -> Bool
crIsArmouredFrom = hasFrontArmour

hasFrontArmour :: Point2 -> Creature -> Bool
hasFrontArmour p cr = fromMaybe False $ do
    invid <- cr ^? crEquipment . ix OnChest
    ittype <- cr ^? crInv . ix invid . itType . iyBase
    return $ FRONTARMOUR == ittype
        && p /= _crOldPos cr 
        && angleVV (unitVectorAtAngle (_crDir cr + frontarmdirection)) (p -.- _crOldPos cr) < pi/2
-- even though angleVV can generate NaN, the comparison seems to deal with it
  where
    frontarmdirection
        | crInAimStance OneHand cr = 0.5
        | crInAimStance TwoHandTwist cr = negate 1
        | otherwise = 0

--crOnSeg :: Point2 -> Point2 -> Creature -> Bool
--crOnSeg p1 p2 cr = circOnSeg p1 p2 (_crPos cr) (_crRad cr)

--crNearSeg :: Float -> Point2 -> Point2 -> Creature -> Bool
--crNearSeg d p1 p2 cr = circOnSeg p1 p2 (_crPos cr) (_crRad cr + d)

crNearPoint :: Float -> Point2 -> Creature -> Bool
crNearPoint d p cr = dist (_crPos cr) p < d + _crRad cr

isAnimate :: Creature -> Bool
{-# INLINE isAnimate #-}
isAnimate cr = case _crActionPlan cr of
    Inanimate -> False
    _   -> True
