module Dodge.WorldEvent.Shockwave
    ( makeShockwaveAt
    , inverseShockwaveAt
    )
  where
import Dodge.Data
import Dodge.Wall.Damage
import Dodge.Base
import Dodge.Zone
import Dodge.Picture.Layer
import Geometry
import Picture

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

-- currently very effective against walls
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 = Shockwave'
        { _ptDraw       = drawShockwave
        , _ptUpdate     = mvShockwave is
        , _btColor'     = col
        , _btPos'       = p
        , _btRad'       = rad
        , _btDam'       = dam
        , _btPush'      = push
        , _btMaxTime'   = 10
        , _btTimer'     = 10
        }
{- Shockwave picture.  -}
drawShockwave :: Particle -> Picture
drawShockwave pt = setDepth 20 
    . setLayer 1 
    . uncurryV translate (_btPos' pt) 
    . color (_btColor' pt) 
    $ thickCircle rad thickness
  where
    r    = _btRad' pt
    thickness = tFraction**2 * r 
    rad = r - (3/4) * r * tFraction
    tFraction = fromIntegral (_btTimer' pt) / fromIntegral (_btMaxTime' pt)

mvShockwave
    :: [Int]    -- ^ IDs of invulnerable creatures.
    -> World -> Particle -> (World, Maybe Particle)
mvShockwave is w pt 
    | _btTimer' pt <= 0 = (       w , Nothing)
    | otherwise         = (doDams w , Just $ pt & btTimer' -~ 1)
  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
    doDams = over creatures (IM.map damCr) . flip (IM.foldl' (flip $ damageWall (Explosive 10000 p))) 
        hitBlocks
    hitBlocks = wallsOnCirc p rad $ wallsNearPoint p w 
    damCr cr 
        | _crID cr `elem` is || dist (_crPos cr) p >= rad + _crRad cr  = cr
        | otherwise = cr & crState . crDamage %~
            (PushDam dam (25 * push *.* safeNormalizeV (_crPos cr -.- p)) : )
{- Create a shockwave going from an outside circle into a center point.  -}
inverseShockwaveAt 
    :: Point2   -- Center position
    -> Float    -- Radius
    -> Int      -- Damage
    -> Float    -- Push amount
    -> World 
    -> World
inverseShockwaveAt p rad dam push = over particles (theShockwave :)
  where 
    theShockwave = Shockwave'
        { _ptDraw = drawInverseShockwave
        , _ptUpdate = moveInverseShockwave
        , _btColor'     = cyan
        , _btPos'       = p
        , _btRad'       = rad
        , _btDam'       = dam
        , _btPush'      = push
        , _btMaxTime'   = 10
        , _btTimer'     = 10
        }
moveInverseShockwave 
    :: World
    -> Particle 
    -> (World, Maybe Particle)
moveInverseShockwave w pt 
    | t <= 0    = (     w, Nothing)
    | otherwise = (dams w, Just $ btTimer' -~ 1 $ pt )
  where 
    p = _btPos' pt
    r = _btRad' pt
    t = _btTimer' pt
    rad = r - 0.1 * r * fromIntegral (10 - t)
    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 = cr
        | otherwise = cr & crState . crDamage %~
            (PushDam 1 (25 *.* safeNormalizeV (p -.- _crPos cr)) :)

drawInverseShockwave :: Particle -> Picture
drawInverseShockwave  pt 
    = setLayer 1 $ onLayer PtLayer $ uncurryV translate p 
                          $ color cyan $ thickCircle rad thickness
  where
    p = _btPos' pt
    r = _btRad' pt
    t = _btTimer' pt
    rad = r - 0.1 * r * fromIntegral (10 - t)
    thickness = fromIntegral (10 - t) **2 * rad / 40
