{- | Functions updating a creature in a Reader World environment -}
module Dodge.Creature.ReaderUpdate
    ( doStrategyActions
    , setTargetMv
    , targetYouWhenCognizant
    , overrideMeleeCloseTarget
    , watchUpdateStrat
    , reloadOverride
    , overrideInternal
    , turnIfDamaged
    , goToTarget
    , flockACC
    , chaseCritMv
    , setMvPos
    , setViewPos
    ) where
import Dodge.Data
import Dodge.Creature.Test
import Dodge.Creature.Volition
import Dodge.Base
import Dodge.Zone
import Geometry

import qualified Data.IntMap.Strict as IM
import Control.Lens
import Control.Applicative
import Data.Maybe
import Data.Bifunctor
--import StreamingHelp
import qualified Streaming.Prelude as S

overrideMeleeCloseTarget :: Creature -> Creature
overrideMeleeCloseTarget cr = maybe cr (tryMeleeAttack cr) (_targetCr $ _crIntention cr)

tryMeleeAttack :: Creature -> Creature -> Creature
tryMeleeAttack cr tcr
    | _crMeleeCooldown cr == 0
        && dist (_crPos tcr) cpos < _crRad cr + _crRad tcr + 5 
        && abs (_crDir cr - argV (_crPos tcr -.- cpos)) < pi/4
                = cr & crActionPlan . apImpulse .~ [Melee $ _crID tcr]
    | otherwise = cr
  where
    cpos = _crPos cr

setMvPos :: World -> Creature -> Creature
setMvPos w cr = cr & crIntention . mvToPoint .~ mpos
  where
    int = _crIntention cr
    mtpos = do
        tpos <- _crPos <$> _targetCr int
        if hasLOSIndirect (_crPos cr) tpos w
            then Just tpos
            else Nothing
    mpos = mtpos 
        <|> _mvToPoint int 
--        <|> listToMaybe (_soundsToInvestigate $ _crMemory cr)

setViewPos :: Creature -> Creature
setViewPos cr = cr & crIntention . viewPoint %~ (<|> mpos)
  where
    mpos = listToMaybe (_soundsToInvestigate $ _crMemory cr)

setTargetMv :: (World -> Creature -> Maybe Creature)    -- ^ Function for determining target
    -> World -> Creature -> Creature 
setTargetMv targFunc w cr = maybe 
    cr 
    (\ctarg -> cr & crIntention . mvToPoint ?~ _crPos ctarg)
    (targFunc w cr)

-- ugly
flockACC :: World -> Creature -> Creature
flockACC w cr = case cr ^? crIntention . targetCr . _Just of
    Nothing -> cr
    Just tcr -> 
        let tpos = _crPos tcr
            cpos = _crPos cr
            isFarACC cr' = _crGroup cr' == _crGroup cr 
                && _crID cr' /= _crID cr
                && dist (_crPos cr') tpos > dist cpos tpos
            macr = minStreamOn (dist cpos . _crPos) . S.filter isFarACC $ crsInsideCirc cpos 50 w
            in case macr of
                Nothing  -> cr
                Just acr ->
                    let r = _crRad acr + _crRad cr + 10
                        horDir = normalizeV (vNormal (cpos -.- tpos))
                        horShift = if isLHS tpos cpos (_crPos acr)
                            then r *.* horDir
                            else negate r *.* horDir
                    in cr & crIntention . mvToPoint ?~ tpos +.+ horShift

chaseCritMv :: World -> Creature -> Creature
chaseCritMv w cr = case _apStrategy (_crActionPlan cr) of
    StrategyActions _ _ -> cr
    WarningCry -> cr
    _ -> case cr ^? crIntention . mvToPoint . _Just of
        Just p -> cr & crActionPlan . apAction .~ [PathTo p]
        _ -> viewTarget w cr

goToTarget :: World -> Creature -> Creature
goToTarget w cr =
    case cr ^? crIntention . mvToPoint . _Just of
        Just p -> cr & crActionPlan . apAction .~ [PathTo p]
        _ -> viewTarget w cr

viewTarget :: World -> Creature -> Creature
viewTarget w cr = do
    case cr ^? crIntention . viewPoint . _Just of
        Just p | hasLOSIndirect p (_crPos cr) w -> cr'
                    & crActionPlan . apAction %~ replaceNullWith (TurnToA p)
                    & crIntention . viewPoint .~ Nothing
               | otherwise -> cr' & crActionPlan . apAction %~ replaceNullWith (PathTo p)
        Nothing -> cr-- & crPerception . crAwakeLevel .~ Lethargic
  where
    cr' = cr & crPerception . cpVigilance .~ Vigilant

replaceNullWith :: a -> [a] -> [a]
replaceNullWith x [] = [x]
replaceNullWith _ xs = xs
    
doStrategyActions :: Creature -> Creature
doStrategyActions cr = case cr ^? crActionPlan . apStrategy of
    Just (StrategyActions strat acs) -> cr & crActionPlan . apAction .~ acs
        & crActionPlan . apStrategy .~ strat
    _ -> cr

reloadOverride :: Creature -> Creature
reloadOverride cr
    | cr ^? crInv . ix (crSel cr) . itConsumption . laLoaded == Just 0 
            && cr ^. crStance . posture == Aiming
        = cr & crActionPlan . apStrategy .~ StrategyActions Reload reloadActions
    | otherwise = cr
  where
    reloadActions = 
        [ holsterWeapon
        , 1 `WaitThen` 
            DoActionWhileInterrupt NoAction (const crIsReloading) 
                (DoImpulses [ChangeStrategy WatchAndWait])
        ]

overrideInternal :: (Creature -> Bool) -> (Creature -> Creature) -> Creature -> Creature
overrideInternal test update cr
    | test cr = update cr
    | otherwise = cr

watchUpdateStrat
    :: [ (World -> Creature -> Bool, World -> Creature -> Strategy) ]
    -> World
    -> Creature
    -> Creature
watchUpdateStrat fs w cr = case cr ^? crActionPlan . apStrategy of
    Just WatchAndWait -> cr 
        & crActionPlan . apStrategy .~ listGuard (map (first uncurry) fs, \_ _ -> WatchAndWait) (w, cr) w cr
    _ -> cr

listGuard :: ([(a -> Bool, b)] , b) -> a -> b
listGuard ( (test,y):ps, z ) x
    | test x = y
    | otherwise = listGuard (ps, z) x
listGuard (_,z) _ = z

targetYouWhenCognizant :: World -> Creature -> Creature
targetYouWhenCognizant w cr = case cr ^? crPerception . cpAwareness . ix 0 of
-- so this caused a space leak: be careful with ?~
-- consider changing targeted creature to be just an index
    Just (Cognizant _)  -> _creatures w IM.! 0 `seq` cr & crIntention . targetCr ?~ _creatures w IM.! 0
    _                   -> cr & crIntention . targetCr .~ Nothing

turnIfDamaged :: Creature -> Creature
turnIfDamaged cr
    | _crPastDamage cr > 0 = case _apStrategy (_crActionPlan cr) of
        WatchAndWait -> cr & crPerception . cpVigilance .~ Vigilant
            & crActionPlan . apStrategy .~ StrategyActions LookAround [TurnToA (_crPos cr -.- unitVectorAtAngle (_crDir cr))]
        _ -> cr
    | otherwise = cr
