module Dodge.Item.Weapon.Launcher
    where
import Dodge.Data
import Dodge.Data.SoundOrigin
import Dodge.Default.Weapon
import Dodge.Default.Shell
import Dodge.SoundLogic.LoadSound
import Dodge.WorldEvent.Explosion
import Dodge.Item.Data
import Dodge.Item.Draw
import Dodge.Item.Weapon.Shell
import Dodge.Item.Weapon.TriggerType
import Dodge.Base
import Dodge.Zone
import Dodge.SoundLogic
import Dodge.WorldEvent.SpawnParticle
import Dodge.WorldEvent.Cloud
import Dodge.RandomHelp
import Geometry
import Geometry.Vector3D
import Picture
import qualified IntMapHelp as IM
import Padding
import ShapePicture
import Shape

import Data.Maybe
import Control.Lens
import Control.Applicative
import Control.Monad.State
import System.Random

launcher :: Item
launcher = defaultGun 
    { _itName = "ROCKO"
    , _itIdentity = Launcher
    , _wpMaxAmmo = 30
    , _wpLoadedAmmo = 30
    , _wpReloadTime = 80
    , _wpReloadState = 0
    , _itUseRate = 20
    , _itUseTime = 0
    , _itUse = aRocketWithItemParams
    , _itUseModifiers =
        [ ammoCheckI
        , useTimeCheckI
        , withSoundStart tap4S
        , useAmmo 1
        ]
    , _wpSpread = 0.02
    , _wpRange = 20
    , _itFloorPict = launcherPic
    , _itAimingSpeed = 0.2
    , _itAimingRange = 0.5
    , _itEquipPict = pictureWeaponOnAim launcherPic
    , _itHammer = NoHammer
    , _itEffect = NoItEffect
    , _wpAmmo = defaultShellAmmo 
        { _amPayload = makeExplosionAt
        , _amString = ""
        , _amPjParams = basicAmPjMoves
        , _amPjDraw = shellPic
        }
    , _itAimStance = TwoHandTwist
    }

launcherPic :: SPic
launcherPic = 
    ( colorSH cyan $ upperPrismPoly 7 $ rectNESW 5 10 (-5) (-10)
    , mempty
    )

basicAmPjMoves :: [PjParam]
basicAmPjMoves = 
    [spinDrag
    ,spinStart
    ,thrustParam
    ]
spinDrag :: PjParam
spinDrag = PjParam
    { _pjMoveParam = \i _ _ -> reduceSpinBy (1 - fromIntegral i*2 / 200)
    , _pjIntParam  = 1
    , _pjDisplayParam = pjPadText "SPIN SLOWDOWN" . show
    , _pjMaxParam = 5
    }
pjPadText :: String -> String -> String
pjPadText s = (rightPad 12 ' ' s ++ ) . (' ':)
spinStart :: PjParam
spinStart = PjParam
    { _pjMoveParam = \i _ cr -> pjEffAtTime 35 $ trySpinByCID (_crID cr) i
    , _pjIntParam  = 2
    , _pjDisplayParam = pjPadText "SPIN AMOUNT" . show
    , _pjMaxParam = 5
    }
thrustParam :: PjParam
thrustParam = PjParam
    { _pjMoveParam = \i _ _ -> pjThrust i
    , _pjIntParam = 1
    , _pjDisplayParam = pjPadText "THRUST DELAY" . show
    , _pjMaxParam = 5
    }

pjThrust :: Int -> Projectile -> World -> World
pjThrust i = pjEffTimeRange (st,et) doThrust
  where
    et | i == 0 = 36
       | otherwise = 40 - (i * 20)
    st = et - 100

doThrust :: Projectile -> World -> World
doThrust pj w = w
    & randGen .~ g
    & projectiles . ix i . pjVel    %~ (\v -> accel +.+ frict *.* v)
    & soundContinue (ShellSound i) newPos missileLaunchS (Just 1)
    & makeFlameletTimed (oldPos -.- vel) 0 (vel +.+ rotateV (pi+sparkD) accel) Nothing 3 10
    & smokeGen
  where
    accel = _pjAcc pj
    i = _pjID pj
    oldPos   = _pjPos pj
    vel      = _pjVel pj
    newPos   = oldPos +.+ vel
    (frict,g)  = randomR (0.6,0.9) $ _randGen w
    (sparkD,_) = randomR (-0.2,0.2) $ _randGen w
    r1 = randInCirc 10 & evalState $ _randGen w
    smokeGen = shellTrailCloud $ addZ 20 (oldPos +.+ r1 +.+ 30 *.* normalizeV (oldPos -.- newPos))

flameLauncher :: Item
flameLauncher = launcher & wpAmmo . amPayload .~ makeFlameExplosionAt
poisonLauncher :: Item
poisonLauncher = launcher & wpAmmo . amPayload .~ makePoisonExplosionAt
teslaLauncher :: Item
teslaLauncher = launcher & wpAmmo . amPayload .~ makeTeslaExplosionAt

aRocketWithItemParams
    :: Item   -- ^ Firing item
    -> Creature
    -> World
    -> World
aRocketWithItemParams it cr w = over projectiles (IM.insert i theShell) w
  where 
    am = _wpAmmo it
    i = IM.newKey $ _projectiles w
    pos = _crPos cr +.+ ((_crRad cr +1) *.* unitVectorAtAngle dir)
    dir = _crDir cr
    ammoMvs pj w' = foldr (\pjP -> _pjMoveParam pjP (_pjIntParam pjP) it cr pj) w' (_amPjParams am)
    theShell = defaultShell
        { _pjPos = pos
        , _pjStartPos = pos
        , _pjVel = rotateV dir (V2 1 0)
        , _pjAcc = rotateV dir (V2 3 0)
        , _pjDraw = _amPjDraw am
        , _pjID = i
        , _pjUpdate = \pj -> ammoMvs pj . decTimMvVel pj . moveShell pj
        , _pjPayload = _amPayload am
        , _pjTimer = 50
        }

moveShell 
    :: Projectile   -- ^ Projectile id
    -> World 
    -> World
moveShell pj w 
    | time > 40 = if circOnSomeWall oldPos 4 w
        then doExplode
        else w
    | isJust thingHit = doExplode
    | time > -300 = w
    | otherwise = doExplode
  where 
    time = _pjTimer pj
    doExplode = w
        & projectileExplosion oldPos 
        & stopSoundFrom (ShellSound i)
        & projectiles %~ IM.delete i
    i = _pjID pj
    oldPos   = _pjPos pj
    vel      = _pjVel pj
    projectileExplosion = _pjPayload pj
    newPos   = oldPos +.+ vel
    hitCr    = fst <$> collideCircCrsPoint oldPos newPos 4 w
    hitWl    = fst <$> collideCircWalls'' oldPos newPos 2 (wallsNearPoint newPos w)
    thingHit = hitCr <|> hitWl

reduceSpinBy :: Float -> Projectile -> World -> World
reduceSpinBy x pj = projectiles . ix (_pjID pj) .  pjSpin *~ x

shellPic :: Projectile -> Picture
shellPic pj = setDepth 20 . uncurryV translate pos $ rotate (argV accel) basePic
  where
    basePic = color black $ polygon $ map toV2 [(-6,4),(-6,-4),(6,-4),(8,0),(6,4)]
    accel = _pjAcc pj
    pos = _pjPos pj

pjEffTimeRange
    :: (Int,Int)
    -> (Projectile -> World -> World)
    -> Projectile
    -> World 
    -> World
pjEffTimeRange (st,et) f pj
    | t <= et && t >= st = f pj
    | otherwise = id
  where
    t = _pjTimer pj

pjEffAtTime 
    :: Int
    -> (Projectile -> World -> World)
    -> Projectile
    -> World
    -> World
pjEffAtTime t f pj
    | _pjTimer pj == t = f pj
    | otherwise = id

trySpinByCID
    :: Int -- ^ creature id
    -> Int -- ^ Spin amount
    -> Projectile
    -> World 
    -> World
trySpinByCID cid i pj w = w & projectiles . ix pjid . pjSpin .~ newSpin
  where
    pjid = _pjID pj
    dir      = argV $ _pjVel pj
    newSpin   = case w ^? creatures . ix cid of
        Just cr -> negate $ min 0.2 $ max (-0.2) $ normalizeAnglePi (dir - _crDir cr) / spinFactor
        _ -> 0
    spinFactor = 5 * (6 - fromIntegral i)
