module Dodge.WorldEvent.Shockwave
    ( makeShockwaveAt
    , inverseShockwaveAt
    )
  where
import Dodge.Data
import Dodge.Data.DamageType
import Dodge.WorldEvent.DamageBlock
import Dodge.Creature.State.Data
import Dodge.Base
import Dodge.Zone
import Dodge.Picture.Layer
import Geometry
import Picture

import qualified Data.IntMap.Strict as IM
import Control.Lens

makeShockwaveAt 
    :: [Int]    -- ^ IDs of invulnerable creatures.
    -> Point2   -- ^ Center of shockwave.
    -> Float    -- ^ Maximal radius.
    -> Int      -- ^ Damage caused per frame.
    -> Float    -- ^ Amount of pushback per frame.
    -> Color    -- ^ Color of shockwave.
    -> World    -- ^ Start world.
    -> World
makeShockwaveAt is p rad dam push col = over particles (theShockwave :)
  where 
    theShockwave = shockwaveAt is p rad dam push col 10

shockwaveAt 
    :: [Int]    -- ^ IDs of invulnerable creatures.
    ->  Point2 -> Float -> Int -> Float -> Color -> Int -> Particle
shockwaveAt is p rad dam push col maxtime
    = Shockwave'
        { _ptDraw       = drawShockwave
        , _ptUpdate'    = mvShockwave is
        , _btColor'     = col
        , _btPos'       = p
        , _btRad'       = rad
        , _btDam'       = dam
        , _btPush'      = push
        , _btMaxTime'   = maxtime
        , _btTimer'     = maxtime
        }

{-
Shockwave picture.
-}
drawShockwave :: Particle -> Picture
drawShockwave pt = pic 
  where
    pic = setDepth 20 . setLayer 1 . uncurryV translate p 
                          $ color (_btColor' pt) $ thickCircle rad thickness
    p    = _btPos' pt
    r    = _btRad' pt
    thickness = tFraction**2 * r 
    rad = r - (3/4) * r * tFraction
    tFraction = fromIntegral t / fromIntegral (_btMaxTime' pt)
    t    = _btTimer' pt

mvShockwave
    :: [Int]    -- ^ IDs of invulnerable creatures.
    -> World -> Particle -> (World, Maybe Particle)
mvShockwave is w pt 
    | _btTimer' pt <= 0 = (w, Nothing)
    | otherwise
        = (dams w , Just $ set btTimer' (t - 1) pt) --  $ set ptDraw (const pic) pt)
  where 
    r    = _btRad' pt
    p    = _btPos' pt
    push = _btPush' pt
    dam  = _btDam' pt
    t    = _btTimer' pt
    tFraction = fromIntegral t / fromIntegral (_btMaxTime' pt)
    rad = r - (3/4) * r * tFraction
    dams = over creatures (IM.map damCr) . flip (IM.foldr (damageBlocksBy 1)) hitBlocks
    hitBlocks = wallsOnCirc p rad $ wallsNearPoint p w 
    damCr cr | _crID cr `elem` is = cr
             | dist (_crPos cr) p < rad + _crRad cr 
                          = over (crState . crDamage) 
                                 ((:) $ PushDam dam (25 * push *.* safeNormalizeV (_crPos cr -.- p)))
                                 cr
             | otherwise  = cr

-------------------------------------------------

{-
Create a shockwave going from an outside circle into a center point.
 -}
inverseShockwaveAt 
    :: Point2   -- Center position
    -> Float    -- Radius
    -> Int      -- Damage
    -> Float    -- Push amount
    -> Float    -- Push amount parameter
    -> World 
    -> World
inverseShockwaveAt p rad _ push pushexp = over particles (theShockwave :)
  where 
    theShockwave = Particle
        { _ptDraw = const blank
        , _ptUpdate' = moveInverseShockWave 10 p rad push pushexp
        }
moveInverseShockWave 
    :: Int      -- ^ Timer
    -> Point2   -- ^ Center position
    -> Float    -- ^ Radius
    -> Float    -- ^ Push amount
    -> Float    -- ^ Push amount parameter
    -> World
    -> Particle 
    -> (World, Maybe Particle)
moveInverseShockWave 0 _ _ _    _       w _  = (w, Nothing)
moveInverseShockWave t p r push pushexp w pt 
    = (dams w, Just $ newupdate $ newpic pt )
  where 
    newupdate = set ptUpdate' $ moveInverseShockWave (t-1) p r push pushexp
    newpic = set ptDraw (const $ onLayer PtLayer $ uncurryV translate p 
                          $ color cyan $ thickCircle rad thickness)
    rad = r - (4/40) * r * fromIntegral (10 - t)
    thickness = fromIntegral (10 - t) **2 * rad / 40
    dams = over creatures (IM.map damCr) . flip (foldr (damageBlocksBy 1)) hitBlocks
    hitBlocks = wallsOnCirc p rad $ wallsNearPoint p w 
    damCr cr | dist (_crPos cr) p < rad + _crRad cr 
                          = over (crState . crDamage) 
                                 ((:) $ PushDam 1 (25 *.* safeNormalizeV (p -.- _crPos cr)))
                                 cr
             | otherwise  = cr
