module Dodge.Creature.State 
    ( stateUpdate
    , doDamage
    ) where
import Dodge.Data
import Dodge.Damage
import Dodge.Hammer
import Dodge.Reloading
import Dodge.Prop.Gib
--import Dodge.Base.Arithmetic
import Dodge.Base
import Dodge.Creature.State.WalkCycle
import Dodge.Creature.Impulse.Movement
--import Dodge.Creature.Test
--import Dodge.Base
import Dodge.SoundLogic
import RandomHelp
import Dodge.WorldEvent
--import Dodge.WallCreatureCollisions
import Dodge.Creature.Action
--import Dodge.Hammer
import Geometry
import Picture
import Shape
import ShapePicture
import qualified IntMapHelp as IM
--import StrictHelp
--import FoldableHelp


import Data.Function
import Control.Lens
--import qualified Data.IntSet as IS
--import Data.Maybe
--import Control.Applicative
--import qualified SDL
--import qualified SDL.Mixer as Mix
--import qualified Data.Set as S
--import qualified Data.Map as M
--import qualified Data.Vector as V
--import qualified Data.Vector.Mutable as MV

foldCr :: [Creature -> World -> World]
    -> Creature -> World -> World
--foldCr xs cr w = foldr ($ cr) w xs
foldCr xs cr w = foldr f w xs
  where
    f g w' = case w' ^? creatures . ix (_crID cr) of
        Just cr' -> g cr' w'
        Nothing  -> w'

-- | this seems to work, but I am not sure about the ordering:
-- previously, the movement was updated before the ai in order to correctly set the oldpos.
-- This should be made more sensible: should the movement side effects apply to
-- the creature before or after it has moved?
-- at what point invSideEffects is applied wrt to when the creature moves
-- may affect whether the shield moves correctly
stateUpdate :: (Creature -> World -> World) -> Creature -> World -> World
stateUpdate f = foldCr
    [ equipmentEffects
    , invSideEff
    , upInv      -- upInv must be called before invSideEff 22.05.23
    , movementSideEff
    , f
    , internalUpdate 
    , clearDamage   -- these three
    , checkDeath    -- must be in
    , doDamage      -- this order 22/06/05
    ]

checkDeath :: Creature -> World -> World
checkDeath cr w
    | _crHP cr > 0 = w
    | otherwise = w 
--        & creatures . at (_crID cr) .~ Nothing
        & dropByState cr
        & removecr
        & stopSoundFrom (CrWeaponSound (_crID cr) 0) 
        & corpseOrGib cr
  where
    removecr
        | _crID cr == 0 = (creatures . ix (_crID cr) . crUpdate .~ const id)
            . (creatures . ix (_crID cr) . crPict .~ const mempty)
            . (creatures . ix (_crID cr) . crHP   .~ 0) 
            -- hack to get around player creature being killed but left with more than 0 hp
        | otherwise  = creatures . at (_crID cr) .~ Nothing
corpseOrGib :: Creature -> World -> World
corpseOrGib cr w = case maxDamageType (_csDamage (_crState cr)) of
    Just (FLAMING,_) -> w & plNew corpses cpID (thecorpse & cpPict %~ fmap scorchSPic)
    Just (ELECTRICAL,_) -> w & plNew corpses cpID thecorpse
    Just (POISONDAM,_) -> w & plNew corpses cpID (thecorpse & cpPict %~ fmap poisonSPic)
    _ | _crPastDamage cr > 200 -> w & addCrGibs cr
        & bloodPuddleAt cpos
        & bloodPuddleAt cpos
        & bloodPuddleAt cpos
    _ -> w
        & bloodPuddleAt cpos
        & bloodPuddleAt cpos
        & plNew corpses cpID thecorpse
  where
    cpos = _crPos cr
    thecorpse = Corpse
        { _cpID = 0
        , _cpPos = _crPos cr
        , _cpDir = _crDir cr
        , _cpPict = _crCorpse cr cr
        , _cpRes = Nothing
        }

scorchSPic :: SPic -> SPic
scorchSPic = over _1 $
    overColSH (mixColors 0.9 0.1 black . normalizeColor) 

poisonSPic :: SPic -> SPic
poisonSPic = over _1 $
    overColSH (mixColors 0.5 0.5 green . normalizeColor) 

--scorchSPic :: World -> SPic -> SPic
--scorchSPic w sp = evalState (scorchSPic' sp) (_randGen w)
--
--scorchSPic' :: SPic -> State StdGen SPic
--scorchSPic' = _1 $ overColSHM $ \col -> takeOne [col,black]
----    (return . mixColorsLinear 0.9 0.1 black) 

bloodPuddleAt :: Point2 -> World -> World
bloodPuddleAt p w = w
    & snd . plNewID decorations 
        (color (dark $ dark red) . setDepth 01 . uncurryV translate (p +.+ q) $ circleSolid 10)
    & randGen .~ g
  where
    (q,g) = randInCirc 10 & runState $ _randGen w

internalUpdate :: Creature -> World -> World
internalUpdate cr = creatures . ix (_crID cr) %~ 
    ( (crHammerPosition %~ moveHammerUp)
    . stepReloading
    . updateMovement
    )

-- | Drop items according to the creature state.
-- TODO make sure this doesn't mess up any ItemPosition
dropByState :: Creature -> World -> World
dropByState cr w = foldr (dropItem cr) w $ case cr ^. crState . csDropsOnDeath of
    DropAll -> IM.keys $ _crInv cr
    DropSpecific xs -> xs
    DropAmount n -> take n $ evalState (shuffle $ IM.keys $ _crInv cr) (_randGen w)

clearDamage :: Creature -> World -> World
clearDamage cr w = w
    & creatures . ix (_crID cr) . crState . csDamage .~ [] 

doDamage :: Creature -> World -> World
doDamage cr w = w
    & applyPastDamages cr
    & _crApplyDamage cr dams cr
  where 
    dams = _csDamage $ _crState cr

-- TODO generalise shake to arbitrary damage amounts
applyPastDamages :: Creature -> World -> World
applyPastDamages cr w
    | _crPastDamage cr > 200 = let (p,g) = runState (randInCirc 3) (_randGen w)
        in w & creatures . ix (_crID cr) %~ (crMvBy p . (crPastDamage -~ 100))
            & randGen .~ g
    | _crPastDamage cr > 20 = let (p,g) = runState (randInCirc 2) (_randGen w)
        in w & creatures . ix (_crID cr) %~ (crMvBy p . (crPastDamage -~ 10))
            & randGen .~ g
    | _crPastDamage cr > 0 = let (p,g) = runState (randInCirc 1) (_randGen w)
        in w & creatures . ix (_crID cr) %~ (crMvBy p . (crPastDamage -~ 1))
            & randGen .~ g
    | otherwise = w

movementSideEff :: Creature -> World -> World
movementSideEff cr w
    | hasJetPack = case cr ^? crStance . carriage of
        Just (Boosting v) -> w
            & randGen .~ g
            & makeFlamelet 
                (oldPos +.+ (_crRad cr + 3) *.* unitVectorAtAngle (_crDir cr + pi))
                20
                (momentum +.+ 1 *.* rotateV randDir (vInverse v)) 
                Nothing
                1
                20
        _   -> w
    | otherwise = footstepSideEffect cr w
  where 
    hasJetPack = any (\it -> it ^? itType . iyBase == Just JETPACK) $ _crInv cr
    oldPos = _crOldPos cr
    momentum' = 0.97 *.* (_crPos cr -.- _crOldPos cr)
    momentum'' | magV momentum' > 3 = 3 *.* normalizeV momentum'
               | otherwise          = momentum'
    momentum = momentum'' +.+ 0.01 *.* unitVectorAtAngle randAng
    (randDir,g) = randomR (-0.5,0.5) $ _randGen w
    (randAng,_) = randomR (0,2*pi) $ _randGen w

useUpdate :: ItemUse -> ItemUse
useUpdate = (useHammer . hammerPosition %~ moveHammerUp)
    . (useDelay . warmTime %~ decreaseToZero)
    . (useDelay . rateTime %~ decreaseToZero)

useEquipment :: Creature -> Int -> World -> World
useEquipment cr i = _eqUse (_eqEq $ _itUse itm) itm cr
  where
    itm = _crInv cr IM.! i
-- a map updating all inventory items
upInv :: Creature -> World -> World
upInv cr = creatures . ix (_crID cr) . crInv %~ IM.mapWithKey (itemUpdate cr)
-- a loop going over equipped items
equipmentEffects :: Creature -> World -> World
equipmentEffects cr = flip (foldr $ useEquipment cr) (IM.keys $ _crInvEquipped cr)
-- a loop going over all inventory items
invSideEff :: Creature -> World -> World
invSideEff cr w = weaponReloadSounds cr $ IM.foldrWithKey f w (_crInv cr)
  where 
    f i it w' = doItemTargeting i cr w' & maybe id (\g -> g it cr) (it ^? itEffect . ieInv)

itemUpdate :: Creature -> Int -> Item -> Item
itemUpdate cr i
    | i == crSel cr = baseupdate True
    | otherwise     = baseupdate False
  where
    baseupdate bool = updateAutoRecharge . (itUse %~ useUpdate) . (itInvPos ?~ i) . (itIsHeld .~ bool)
updateAutoRecharge :: Item -> Item
updateAutoRecharge it = case _itConsumption it of
    AutoRecharging l m t p
        | l < m && p <= 0 -> it & itConsumption .~ AutoRecharging (l+1) m t t
        | l < m -> it & itConsumption . arProgress -~ 1
    _ -> it
doItemTargeting :: Int -> Creature -> World -> World
doItemTargeting invid cr w = case cr ^? crInv . ix invid . itTargeting of
    Nothing -> w
    Just NoTargeting -> w
    Just t  -> let (w',t') = _tgUpdate t (_crInv cr IM.! invid) cr w t
        in w' & creatures . ix (_crID cr) . crInv . ix invid . itTargeting .~ t'

weaponReloadSounds :: Creature -> World -> World
weaponReloadSounds cr w = case cr ^? crInvSel . iselAction of
    Just ReloadAction{_reloadAction = la} -> soundContinue 
        (CrReloadSound cid) (_crPos cr) (_actionSound la) (Just 1) w
    _ -> w
--        PassiveReload stype | Just (_laReloadTime am) == am ^? laTransfer . transferTime
--                                        -> soundContinue (CrReloadSound 0) (_crPos cr) stype Nothing w
--        PassiveReload _ -> w 
--        ActiveClear | am ^? laTransfer . transferTime == Just 0 -> stopSoundFrom (CrReloadSound cid) w
--        ActiveClear | _laTransfer am == NoTransfer -> w
--        ActiveClear -> soundContinue (CrReloadSound cid) (_crPos cr) reloadS (Just 1) w
--        ActivePartial{} | am ^? laTransfer . transferTime == Just 0 -> stopSoundFrom (CrReloadSound cid) w
--        ActivePartial{} | _laTransfer am == NoTransfer -> w
--        ActivePartial{} -> soundContinue (CrReloadSound cid) (_crPos cr) reloadS (Just 1) w
--    Just ChargeableAmmo {} -> w
--    Just NoConsumption {} -> w
--    Just ItemItselfConsumable {} -> w
--    Nothing -> w
  where 
    cid = _crID cr

updateMovement :: Creature -> Creature
updateMovement cr 
    | isFrictionless cr = over crPos (+.+ momentum) cr
    | otherwise = updateWalkCycle cr
  where
    momentum' = 0.98 *.* (_crPos cr -.- _crOldPos cr)
    momentum'' | magV momentum' > 1 = 1 *.* normalizeV momentum'
               | otherwise          = momentum'
    momentum = momentum''

isFrictionless :: Creature -> Bool
isFrictionless cr = case cr ^? crStance . carriage of
    Just (Boosting _) -> True
    Just Floating     -> True
    _                 -> False
