module Dodge.Creature.State where
import Dodge.Data
import Dodge.Data.DamageType
import Dodge.Data.SoundOrigin
import Dodge.Item.Data
import Dodge.Creature.State.Data
import Dodge.Creature.State.WalkCycle
import Dodge.Creature.Stance.Data
--import Dodge.Creature.Test
--import Dodge.Base
import Dodge.SoundLogic
import Dodge.SoundLogic.LoadSound
import Dodge.RandomHelp
import Dodge.WorldEvent
--import Dodge.WallCreatureCollisions
import Dodge.Creature.Action
import Geometry
import Picture
import qualified IntMapHelp as IM
--import StrictHelp

import Data.List
--import Data.Char
--import Data.Maybe
import Data.Function
import Control.Lens
--import Control.Applicative
import Control.Monad.State
--import Control.Monad
--import qualified SDL
--import qualified SDL.Mixer as Mix
import System.Random
--import qualified Data.Set as S
--import qualified Data.Map as M
import Data.Monoid
import qualified Data.Vector as V
import qualified Data.Vector.Mutable as MV

dummyUpdate :: CRUpdate
dummyUpdate cr _ = (Endo id, Just cr)

-- | The movement is updated before the ai in order to correctly set the oldpos.
stateUpdate :: CRUpdate -> CRUpdate
stateUpdate u cr w = case u (updateMovement cr) w of
    (f, maybeCr) -> 
        ( Endo $ invSideEff cr . movementSideEff cr . deathEff . appEndo f
        , fmap (stepReloading . stepItemUseCooldown . doDamage . crAutoReload) $ crOrCorpse =<< maybeCr
        )
  where   
    crOrCorpse cr' 
        | cr' ^. crHP > 0 = Just cr'
        | otherwise      = Nothing
    deathEff 
        | cr ^.crHP > 0 = id
        | otherwise = stopSoundFrom (CrWeaponSound (_crID cr) 0) 
            . over decorations addCorpse 
            . dropByState cr
    addCorpse = IM.insertNewKey 
        $ uncurryV translate (_crOldPos cr) 
        $ rotate (_crDir cr)
        (_crCorpse cr)
-- | Drop items according to the creature state.
dropByState :: Creature -> World -> World
dropByState cr w = foldr (copyItemToFloor cr) w is
  where
    is :: [Int]
    is = case cr ^. crState . crDropsOnDeath of
        DropAll -> IM.keys $ _crInv cr
        DropSpecific xs -> xs
        DropAmount n -> take n $ evalState (shuffle $ IM.keys $ _crInv cr) (_randGen w)

setOldPos :: Creature -> Creature
setOldPos cr = cr
    & crOldPos .~ _crPos cr
    & crOldDir .~ _crDir cr
{- |
Given a creature and a velocity, applies friction to that creature and evaluates a 
velocity to carry across frames.
-}
crFriction :: Creature -> Point2 -> Point2
crFriction _ _ = V2 0 0

--meleeCooldown :: CRUpdate -> CRUpdate
--meleeCooldown u cr = u $ cr & crMeleeCooldown . _Just %~ (max 0 . (\x -> x - 1))

doDamage :: Creature -> Creature
doDamage cr = set (crState . crDamage) [] 
    . over (crState . crPastDamage) (advancePastDamages dams)
    $ snd $ _crApplyDamage cr dams cr
  where 
    dams = _crDamage $ _crState cr

advancePastDamages :: [DamageType] -> V.Vector [DamageType] -> V.Vector [DamageType]
advancePastDamages newDs v = f $ V.backpermute v bpVector
  where
    f = V.modify $ \v' -> MV.write v' 0 newDs

bpVector :: V.Vector Int
bpVector = V.fromList $ 0 : [0 .. 18]

movementSideEff :: Creature -> World -> World
movementSideEff cr w
    | hasJetPack = case cr ^? crStance . carriage of
        Just (Boosting v) 
            -> makeFlameletTimed 
                        (oldPos +.+ (_crRad cr + 3) *.* unitVectorAtAngle (_crDir cr + pi))
                        20
                        (momentum +.+ 1 *.* rotateV randDir (vInverse v)) 
                        Nothing
                        1
                        20
                $ set randGen g w
        _   -> w
    | otherwise = footstepSideEffect cr w
  where 
    hasJetPack = any (\it -> it ^? itIdentity == 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

invSideEff :: Creature -> World -> World
invSideEff cr w = weaponReloadSounds cr $ IM.foldrWithKey f w (_crInv cr)
  where 
    f i it w' = case it ^? itEffect . itInvEffect of
        Nothing -> w'
        Just g  -> g (_itEffect it) cr i w'

weaponReloadSounds :: Creature -> World -> World
weaponReloadSounds cr w = case _crInv cr IM.!? _crInvSel cr of
    Just Weapon{_wpReloadType = PassiveReload stype,_wpReloadTime=rt,_wpReloadState=rs} 
        | rt == rs -> soundContinue (CrReloadSound 0) (_crPos cr) stype Nothing w
        | otherwise -> w
    Just Weapon{_wpReloadState = 0} -> w
    Just Weapon{_wpReloadState = 1} -> stopSoundFrom (CrReloadSound cid) w
    Just Weapon{} -> soundContinue (CrReloadSound cid) (_crPos cr) reloadS (Just 1) w
    _ -> w
  where 
    cid = _crID cr

updateMovement :: Creature -> Creature
updateMovement cr 
    | isFrictionless cr = over crPos (+.+ momentum) $ setOldPos cr
    | otherwise = updateWalkCycle $ setOldPos 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

stepItemUseCooldown :: Creature -> Creature
stepItemUseCooldown cr = cr & crInv . ix iSel %~
    (itUseTime %~ decreaseToZero) . (wpCurWarmUp %~ decreaseToZero)
  where 
    iSel = _crInvSel cr 

stepReloading :: Creature -> Creature
stepReloading cr = over (crInv . ix iSel . wpReloadState) decreaseToZero cr
  where 
    iSel = _crInvSel cr 

decreaseToZero :: Int -> Int
decreaseToZero x = max 0 (x - 1) 

onDeath :: (Creature -> World -> World) -> CRUpdate -> CRUpdate
onDeath h u cr w
    | _crHP cr > 0 = u cr w
    | otherwise    = ( Endo $ h cr , Nothing )

updateBarrel 
    :: Creature 
    -> World
    -> (Endo World, Maybe Creature)
updateBarrel cr _
    | _crHP cr > 0 = (Endo id, newCr)
    | otherwise    = (Endo id, Nothing)
  where 
    newCr = Just $ doDamage cr

-- should generate the sparks externally using new random generators
updateExpBarrel 
    :: Creature 
    -> World
    -> (Endo World, Maybe Creature)
updateExpBarrel cr w 
    | _crHP cr > 0 = (Endo $ foldr (.) id pierceSparks . hiss, newCr)
    | otherwise    = (Endo $ makeExplosionAt (_crPos cr) . stopSounds , Nothing)
  where 
    g = _randGen w
    damages = _crDamage $ _crState cr
    pierceSparks :: [World -> World]
    pierceSparks 
      = zipWith4 (\p a -> createBarrelSpark (_crPos cr +.+ p) (a + argV p) (Just $ _crID cr))
                            poss as times colids
    as =  randomRs (-0.7,0.7) g
    colids = randomRs (0,11) g
    times = randomRs (2,5) g
    poss = _piercedPoints $ _crSpState $ _crState cr
    --newCr = Just $ doDamage $ applyFuseDamage cr -- $ foldr perforate cr damages
    newCr = Just $ applyFuseDamage $ set (crState . crDamage) [] $ damToExpBarrel damages cr
    --perforate :: DamageType -> Creature -> Creature
    --perforate (Piercing amount sp int ep) cr = over (crState . crSpState . piercedPoints)
    --                                                  ((:) $ int -.- _crPos cr) cr
    --perforate _ cr = cr
    applyFuseDamage cr' = cr' & crHP %~ 
        subtract (length . _piercedPoints . _crSpState $ _crState cr')
    hiss | null poss = id
         | otherwise  = soundMultiFrom [BarrelHiss 0,BarrelHiss 1] (_crPos cr) foamSprayLoopS (Just 1)
    stopSounds = stopSoundFrom (BarrelHiss 0) . stopSoundFrom (BarrelHiss 1)

damToExpBarrel :: [DamageType] -> Creature -> Creature
damToExpBarrel ds cr = foldr damToExpBarrel' (foldr damToExpBarrel' cr pierceDam) otherDam
  where 
    (pierceDam,otherDam) = partition isPierce ds
    isPierce Piercing{} = True
    isPierce _             = False

damToExpBarrel' :: DamageType -> Creature -> Creature
damToExpBarrel' (Piercing amount _ int _) cr 
    = over (crState . crSpState . piercedPoints) ((:) $ int -.- _crPos cr) 
        $ over crHP (\hp -> hp - div amount 200) cr
damToExpBarrel' PoisonDam {} cr = cr
damToExpBarrel' SparkDam {}  cr = cr
damToExpBarrel' PushDam{_dmPushBack = v} cr = cr Control.Lens.& crPos %~ (+.+) (1 / _crMass cr *.* v)
damToExpBarrel' dt             cr = cr Control.Lens.& crHP -~ _dmAmount dt
