{- |
Weapon effects when pulling the trigger.  -}
module Dodge.Item.Weapon.TriggerType
    ( useAmmo
    , withMuzFlareI
    , withOldDir
    , trigDoAlso
    , withTempLight
    --, withVelWthHiteff
    , ammoUseCheckI
    , rateIncABI
    , torqueBefore
    , torqueBeforeAtLeast
    , torqueAfterI
    , withSoundStart
    , withSoundContinue
    , withSoundForI
    , withSmoke
    , withThickSmokeI
    , withThinSmokeI
    , withRandomOffsetI
    , withRandomDirI
    , withRecoilI
    , afterRecoil
    , withSidePushAfterI
    , withSidePushI
    , withWarmUpI
    , spreadNumI
    , numI
    , randWalkAngle -- ^ should be made into a modifier, perhaps
    , hammerCheckI
    , hammerCheckL
    , shootL
    , useTimeCheckI
    , ammoCheckI
    , randSpreadDir
    , modClock
    )
    where
import Dodge.Data
import Dodge.Data.SoundOrigin
import Dodge.SoundLogic
import Dodge.Creature.Action (startReloadingWeapon)
import Dodge.WorldEvent
--import Dodge.WorldEvent.Cloud
import Dodge.RandomHelp
import Dodge.Item.Attachment.Data
import Dodge.Item.Data
import Dodge.Default
import Sound.Data
import Geometry
import Geometry.Vector3D

import System.Random
import Control.Lens
import Control.Monad.State
import Data.Maybe
import qualified Data.IntMap.Strict as IM
import Data.Foldable

type ChainEffect = 
    (Item -> Creature -> World -> World)
    -> Item
    -> Creature
    -> World 
    -> World

-- Note that this uses the "base" creature and item values
trigDoAlso 
    :: (Item -> Creature -> World -> World)
    -> ChainEffect
trigDoAlso afterEff eff item cr = afterEff item cr . eff item cr

withSmoke :: Int -> Point4 -> Float -> Int -> Float -> ChainEffect
withSmoke num col rad t alt eff item cr w 
    = eff item cr 
    $ foldl' (flip $ smokeCloudAt col rad t alt . (+.+.+ pos) . (*8)) w ps
  where
    dir = _crDir cr
    pos = addZ 0 $ _crPos cr +.+ (_crRad cr + 15) *.* unitVectorAtAngle dir
    ps = replicateM num randOnUnitSphere & evalState $ _randGen w

withThinSmokeI :: ChainEffect
withThinSmokeI eff item cr w = eff item cr $ foldl' (flip $ makeThinSmokeAt . (+.+.+ pos) . (* 8)) w ps
  where
    dir = _crDir cr
    pos = addZ 0 $ _crPos cr +.+ (_crRad cr +0.5) *.* unitVectorAtAngle dir
    ps = replicateM 5 randOnUnitSphere & evalState $ _randGen w

withThickSmokeI :: ChainEffect
withThickSmokeI eff item cr w = eff item cr $ foldl' (flip $ makeThickSmokeAt . (+.+.+ pos) . (*8)) w ps
  where
    dir = _crDir cr
    pos = addZ 0 $ _crPos cr +.+ (_crRad cr + 15) *.* unitVectorAtAngle dir
    ps = replicateM 20 randOnUnitSphere & evalState $ _randGen w
-- TODO  create a trigger that does different things on first and continued
-- fire.
ammoCheckI :: ChainEffect
ammoCheckI eff item cr w
    | _wpLoadedAmmo item <= 0 
                = fromMaybe w (startReloadingWeapon cr w)
    | _wpReloadState item > 0 = w 
    | otherwise = eff item cr w
{- |
Fires at an increasing rate.
Has different effect after first fire.
Applies ammo check and use cooldown check.  -}
rateIncABI
    :: Int  -- ^ Start rate
    -> Int  -- ^ End rate
    -> ChainEffect  -- ^ Extra effect on first fire
    -> ChainEffect  -- ^ Extra effect on continued fire
    -> ChainEffect
rateIncABI startRate fastRate exeffFirst exeffCont eff item cr w
    | repeatFire = w
        & pointItem %~ ( (itUseRate .~ max fastRate (currentRate - 1))
            . (wpLoadedAmmo -~ 1)
            . (itUseTime .~ currentRate) )
        & exeffCont eff item cr
    | firstFire = w
        & pointItem %~ ( (itUseRate .~ startRate - 1)
            . (wpLoadedAmmo -~ 1)
            . (itUseTime .~ startRate) )
        & exeffFirst eff item cr
    | otherwise = w
  where 
    cid = _crID cr
    itRef = _crInvSel cr
    pointItem = creatures . ix cid . crInv . ix itRef
    currentRate = _itUseRate item
    repeatFire = _wpReloadState item == 0 
        && _itUseTime item == 1 
    firstFire = _wpReloadState item == 0 
        && _itUseTime item == 0 
{- | Apply effect after a warm up.  -}
withWarmUpI
    :: SoundID -- ^ warm up sound id
    -> ChainEffect
withWarmUpI soundID f item cr w
    | _wpReloadState item /= 0 = w
    | curWarmUp < maxWarmUp = w
        & pointerToItem . wpCurWarmUp +~ 2
        & soundContinue (CrWeaponSound cid 0) (_crPos cr) soundID (Just 2)
    | otherwise = w
        & pointerToItem . wpCurWarmUp .~ maxWarmUp
        & f item cr
  where 
    cid = _crID cr
    itRef = _crInvSel cr
    pointerToItem = creatures . ix cid . crInv . ix itRef
    curWarmUp = _wpCurWarmUp item
    maxWarmUp = _wpMaxWarmUp item
{- | Adds a sound to a creature based world effect. 
The sound is emitted from the creature's position. -}
withSoundStart
    :: SoundID -- ^ Sound id
    -> ChainEffect
withSoundStart soundid f item cr 
    = soundMultiFrom [CrWeaponSound cid 0,CrWeaponSound cid 1,CrWeaponSound cid 2] (_crPos cr) soundid Nothing
--    = soundStart (CrWeaponSound cid 0) (_crPos cr) soundid Nothing
    . f item cr
  where
    cid = _crID cr
{- | Adds a sound to a creature based world effect. 
The sound is emitted from the creature's position. -}
withSoundContinue
    :: SoundID -- ^ Sound id
    -> ChainEffect
withSoundContinue soundid f item cr 
    = soundContinue (CrWeaponSound cid 0) (_crPos cr) soundid Nothing
    . f item cr
  where
    cid = _crID cr

{- | Adds a sound to a creature based world effect. 
The sound is emitted from the creature's position. -}
withSoundForI
    :: SoundID -- ^ Sound id
    -> Int -- ^ Frames to play
    -> ChainEffect
withSoundForI soundid playTime f item cr 
    = soundContinue (CrWeaponSound (_crID cr) 0) (_crPos cr) soundid (Just playTime)
    . f item cr

afterRecoil
    :: Float    -- ^ Recoil amount
    -> ChainEffect
afterRecoil recoilAmount eff item cr = eff item (pushback cr) . over (creatures . ix cid) pushback
  where 
    cid = _crID cr
    pushback = over crPos (+.+ rotateV (_crDir cr) (V2 ((-recoilAmount) / _crMass cr ) 0))
withRecoilI
    :: Float    -- ^ Recoil amount
    -> ChainEffect
withRecoilI recoilAmount eff item cr = eff item cr . over (creatures . ix cid) pushback
  where 
    cid = _crID cr
    pushback = over crPos (+.+ rotateV (_crDir cr) (V2 ((-recoilAmount) / _crMass cr ) 0))
{- | Pushes a creature sideways by a random amount.
Applied before the underlying effect.  -}
withSidePushI
    :: Float    -- ^ Maximal possible side push amount
    -> ChainEffect
withSidePushI maxSide eff item cr w = eff item (push cr) $
    w & creatures . ix cid %~ push
      & randGen .~ g
  where 
    cid = _crID cr
    push = over crPos (+.+ rotateV (_crDir cr) (V2 0 (pushAmount / _crMass cr)))
    (pushAmount, g) = randomR (-maxSide,maxSide) $ _randGen w
-- consider unifying the pushes using a direction vector
{- | Pushes a creature sideways by a random amount.
Applied after the underlying effect.  -}
withSidePushAfterI
    :: Float    -- ^ Maximal possible side push amount
    -> ChainEffect
withSidePushAfterI maxSide eff item cr w = over (creatures . ix cid) push . eff item cr $ 
    w & randGen .~ g
  where 
    cid = _crID cr
    push = over crPos (+.+ rotateV (_crDir cr) (V2 0 (pushAmount / _crMass cr)))
    (pushAmount, g) = randomR (-maxSide,maxSide) $ _randGen w
useAmmo
    :: Int  -- ^ amount of ammo to use
    -> ChainEffect
useAmmo amAmount eff item cr = eff item cr .
    (creatures . ix (_crID cr) . crInv . ix (_crInvSel cr) . wpLoadedAmmo -~ amAmount)
{- | 
Applies a world effect after an item use cooldown check. -}
useTimeCheckI :: ChainEffect
useTimeCheckI f item cr w = case item ^? itUseTime of
    Just 0 -> f item cr $ setUseTime w
    _             -> w
  where 
    cid = _crID cr
    setUseTime = creatures . ix cid . crInv . ix (_crInvSel cr) . itUseTime +~ useRate
    useRate = fromMaybe 0 $ item ^? itUseRate
{- | Applies a world effect after a hammer position check. -}
hammerCheckI :: ChainEffect
hammerCheckI f it cr w = case it ^? itHammer of
    Just HammerUp -> f it cr $ setHammerDown w
    _             -> setHammerDown w
  where 
    cid = _crID cr
    setHammerDown = creatures . ix cid . crInv . ix (_crInvSel cr) . itHammer .~ HammerDown
{- | Applies a world effect after an ammo check. -}
ammoUseCheckI  :: ChainEffect
ammoUseCheckI f item cr w 
    | fireCondition = f item cr w & pointerToItem %~
        ( (wpLoadedAmmo -~ 1) . (itUseTime .~ _itUseRate item) )
    | reloadCondition = fromMaybe w $ startReloadingWeapon cr w
    | otherwise = w
  where 
    cid = _crID cr
    itRef = _crInvSel cr
    pointerToItem = creatures . ix cid . crInv . ix itRef
    fireCondition = _wpReloadState item == 0
        && _itUseTime item == 0
        && _wpLoadedAmmo item > 0
    reloadCondition = _wpLoadedAmmo item == 0
{- | Applies a world effect after a hammer position check.
Arbitrary inventory position. -}
hammerCheckL
    :: (Creature -> Int -> World -> World)  -- ^ Underlying effect
    -> Creature 
    -> Int
    -> World
    -> World
hammerCheckL f cr invid w = case (_crInv cr IM.! invid) ^? itHammer of
    Just HammerUp -> f cr invid $ setHammerDown w
    _             -> setHammerDown w
  where 
    cid = _crID cr
    setHammerDown = creatures . ix cid . crInv . ix invid . itHammer .~ HammerDown
{- | Applies a world effect after an ammo check. 
Arbitrary inventory position. -}
shootL 
    :: (Creature -> Int -> World -> World) 
            -- ^ Underlying effect
    -> Creature
    -> Int -- ^ Inventory position
    -> World
    -> World
shootL f cr invid w 
    | fireCondition = f cr invid w & pointerToItem %~
        ( (wpLoadedAmmo -~ 1) . (itUseTime .~ _itUseRate item) )
    | reloadCondition = fromMaybe w $ startReloadingWeapon cr w
    | otherwise = w
  where 
    cid = _crID cr
    item = _crInv cr IM.! invid
    pointerToItem = creatures . ix cid . crInv . ix invid
    fireCondition = _wpReloadState item == 0
        && _itUseTime item == 0
        && _wpLoadedAmmo item > 0
    reloadCondition = _wpLoadedAmmo item == 0
withTempLight :: Int -> Float -> V3 Float -> ChainEffect
withTempLight time rad col eff item cr = eff item cr 
    . over tempLightSources (theTLS :)
  where
    theTLS = defaultTLS
        { _tlsPos = V3 x y 10
        , _tlsRad=rad
        , _tlsIntensity = col
        , _tlsTime = time
        }
    V2 x y = _crPos cr +.+ 15 *.* unitVectorAtAngle (_crDir cr)

modClock :: Int -> ChainEffect -> ChainEffect
modClock n chainEff eff it cr w
    | _frameClock w `mod` n == 0 = chainEff eff it cr w
    | otherwise = eff it cr w

withMuzFlareI :: ChainEffect
withMuzFlareI f it cr w = 
    makeTLight 3 100 (V3 1 1 0.5) pos2 -- . muzzleFlashAt pos2 
    $ f it cr w
  where 
    --pos = _crPos cr +.+ _crRad cr *.* unitVectorAtAngle (_crDir cr)
    pos2 = addZ 35 $ _crPos cr +.+ (2.5 * _crRad cr) *.* unitVectorAtAngle (_crDir cr)
{- | Applies the effect to a randomly rotated creature,
 - rotation amount given by wpSpread  -}
randSpreadDir :: ChainEffect
randSpreadDir f it cr w = f it (cr & crDir +~ a) $ set randGen g w
  where 
    acc = _wpSpread it
    (a, g) = randomR (-acc,acc) $ _randGen w

{- | Applies the effect to a randomly rotated creature.  -}
withRandomDirI
    :: Float    -- ^ Max possible rotation
    -> ChainEffect
withRandomDirI acc f it cr w = f it (cr & crDir +~ a) $ set randGen g w
  where 
    (a, g) = randomR (-acc,acc) $ _randGen w
withOldDir
    :: Float    -- ^ The fraction of the old direction
    -> ChainEffect
withOldDir aFrac eff item cr
    = eff item (cr & crDir %~ tweenAngles aFrac (_crOldDir cr))
{- | Apply the effect to a translated creature. -}
withRandomOffsetI
    :: Float    -- ^ Max possible translate
    -> ChainEffect
withRandomOffsetI offsetAmount f item cr w = f item (cr & crPos %~ (+.+ offV)) $ set randGen g w
  where 
    (offsetVal , g) = randomR (-offsetAmount,offsetAmount) $ _randGen w
    offV = rotateV (_crDir cr) (V2 0 offsetVal)
-- | Rotates a creature 
torqueBefore
    :: Float    -- ^ Max possible rotation
    -> ChainEffect
torqueBefore torque feff item cr w 
    | cid == 0  = feff item (cr & crDir +~ rot) $ w
        & randGen                    .~ g 
        & creatures . ix cid . crDir +~ rot
        & cameraRot                  +~ rot
    | otherwise = feff item cr $ set randGen g $ over (creatures . ix cid . crDir) (+rot) w
  where 
    cid = _crID cr
    (rot, g) = randomR (-torque,torque) $ _randGen w
-- | Rotates a creature with minimum rotation
-- Rotates the player creature before applying the effect, other creatures after.
torqueBeforeAtLeast
    :: Float    -- ^ Minimal possible rotation
    -> Float    -- ^ Extra possible rotation
    -> ChainEffect
torqueBeforeAtLeast minTorque exTorque feff item cr w 
    | cid == 0  = feff item (cr & crDir +~ rot') $ w
        & randGen                    .~ g 
        & creatures . ix cid . crDir +~ rot'
        & cameraRot                  +~ rot'
    | otherwise = feff item cr $ set randGen g $ over (creatures . ix cid . crDir) (+rot') w
  where 
    cid = _crID cr
    (rot, g) = randomR (-exTorque,exTorque) $ _randGen w
    rot' | rot < 0   = rot - minTorque
         | otherwise = rot + minTorque
-- | Rotate a randomly creature after applying an effect.
torqueAfterI
    :: Float                    -- ^ Max possible rotation
    -> ChainEffect
torqueAfterI torque feff item cr w 
    | cid == 0  = rotateScope $ set randGen g $ over cameraRot (+rot) $ feff item cr w
    | otherwise = set randGen g $ over (creatures . ix cid . crDir) (+rot) $ feff item cr w
  where 
    cid = _crID cr
    (rot, g) = randomR (-torque,torque) $ _randGen w
    rotateScope = creatures . ix 0 . crInv . ix (_crInvSel (_creatures w IM.! 0))
                          . itAttachment . scopePos %~ rotateV rot 
spreadNumI :: ChainEffect
spreadNumI eff item cr w = foldr f w dirs
  where
    dirs = zipWith (+) 
        [-spread,-spread+(2*spread/fromIntegral numBul)..spread]
        (randomRs (0,spread/fromIntegral numBul) (_randGen w))
    f dir = eff item (cr & crDir +~ dir)
    spread = _wpSpread item
    numBul = _wpNumBarrels item
numI :: ChainEffect
numI eff item cr w = foldr f w poss
  where
    cp :: Float
    cp = -0.5 * fromIntegral numBul
    poss :: [V2 Float]
    poss = map (rotateV (_crDir cr) . V2 0 . (*5) . (+ cp) . fromIntegral) [0 .. numBul - 1]
    f pos = eff item (cr & crPos %~ (+.+ pos))
    numBul = _wpNumBarrels item
{- | Uses '_wpSpread' as a parameter for the current offset angle. -}
randWalkAngle 
    :: Float    -- ^ Max offset angle
    -> Float    -- ^ Walk speed
    -> (Creature -> World -> World)  -- ^ Underlying effect
    -> Creature      -- ^ Creature id
    -> World 
    -> World
randWalkAngle ma aspeed f cr w = w
    & f (cr & crDir +~ a)
    & creatures . ix cid . crInv . ix i . wpSpread .~ newa
  where
    cid = _crID cr
    a = _wpSpread $ _crInv (_creatures w IM.! cid) IM.! i
    (walka, _) = randomR (-aspeed,aspeed) (_randGen w)
    newa = min ma $ max (negate ma) (a + walka)
    i = _crInvSel $ _creatures w IM.! cid
