module Player.Gun.All.GrenadeLauncher
    ( mkGrenadeLauncherGun
    ) where

import Control.Monad.IO.Class (MonadIO)

import Configs
import Configs.All.PlayerGun.GrenadeLauncher
import Constants
import FileCache
import Msg
import Player
import Player.BufferedInputState
import Player.Gun
import Player.Gun.All.GrenadeLauncher.Data
import Player.Gun.All.GrenadeLauncher.Grenade
import Player.Gun.All.GrenadeLauncher.Mine
import Player.Gun.FireDrawState
import Util
import Window.Graphics
import Window.InputState

grenadeLauncherSoundPath      = "event:/SFX Events/Player/Guns/grenade-launcher"       :: FilePath
grenadeLauncherUnderSoundPath = "event:/SFX Events/Player/Guns/grenade-launcher-under" :: FilePath
throwMineSoundPath            = "event:/SFX Events/Player/Guns/mine-toss"              :: FilePath

throwMineFrameTagName = FrameTagName "throwMine" :: FrameTagName

mkGrenadeLauncherGun :: (ConfigsRead m, FileCache m, GraphicsRead m, MonadIO m) => m (Some Gun)
mkGrenadeLauncherGun = do
    grenadeLauncherData <- mkGrenadeLauncherData

    return . Some $ (mkGun grenadeLauncherData GrenadeLauncherGun)
        { _fireDrawData = Just $ _shootFireDrawData grenadeLauncherData
        , _think        = thinkGrenadeLauncher
        , _update       = updateGrenadeLauncher
        }

thinkGrenadeLauncher :: InputRead m => GunThink GrenadeLauncherData m
thinkGrenadeLauncher gunStatus player grenadeLauncher = think <$> readInputState
    where
        think :: InputState -> [Msg ThinkPlayerMsgsPhase]
        think inputState
            | fireDelayTtl <= 0.0 && shootDownPressed && canShoot = if
                | not (canSpendMeter mineMeterCost) -> [mkMsg $ UiMsgInsufficientMeter mineMeterCost shootJustPressed]
                | otherwise                         ->
                    let
                        updateThrowMine = \g ->
                            let
                                gData         = _data g
                                fireDelaySecs = _mineFireDelaySecs $ _config (gData :: GrenadeLauncherData)
                            in g
                                { _data         = gData {_fireDelayTtl = fireDelaySecs}
                                , _fireDrawData = Just $ _throwMineFireDrawData gData
                                }
                    in
                        [ mkMsg $ PlayerMsgUpdateGun updateThrowMine
                        , mkMsg PlayerMsgFiredGun
                        ]

            | fireDelayTtl <= 0.0 && shootPressed && canShoot = if
                | not (canSpendMeter shotMeterCost) -> [mkMsg $ UiMsgInsufficientMeter shotMeterCost shootJustPressed]
                | otherwise                         ->
                    let
                        updateShoot = \g ->
                            let
                                gData         = _data g
                                fireDelaySecs = _shootFireDelaySecs $ _config (gData :: GrenadeLauncherData)
                            in g
                                { _data         = gData {_fireDelayTtl = fireDelaySecs}
                                , _fireDrawData = Just $ _shootFireDrawData gData
                                }
                    in
                        [ mkMsg PlayerMsgFiredGun
                        , mkMsg $ NewThinkProjectileMsgAddM (mkGrenade player)
                        , mkMsg $ AudioMsgPlaySound grenadeLauncherSoundPath pos
                        , mkMsg $ AudioMsgPlaySound grenadeLauncherUnderSoundPath pos
                        , mkMsg $ PlayerMsgUpdateGun updateShoot
                        , mkMsg $ PlayerMsgSpendMeter shotMeterCost
                        ]

            | otherwise = case _torso <$> playerGunFireDrawSprites player of
                Just torsoSpr
                    | throwMineFrameTagName `isSpriteFrameTag` torsoSpr && _frameChanged torsoSpr ->
                        [ mkMsg $ NewThinkProjectileMsgAddM (mkMine player)
                        , mkMsg $ AudioMsgPlaySound throwMineSoundPath pos
                        , mkMsg $ PlayerMsgSpendMeter mineMeterCost
                        ]

                _ -> []

            where
                canSpendMeter = \meterCost -> canSpendPlayerMeter meterCost player

                grenadeLauncherData = _data grenadeLauncher
                fireDelayTtl        = _fireDelayTtl grenadeLauncherData

                cfg           = _config (grenadeLauncherData :: GrenadeLauncherData)
                shotMeterCost = _shotMeterCost cfg
                mineMeterCost = _mineMeterCost cfg
                canShoot      = gunStatus == ActiveStatus Shootable
                pos           = _pos (player :: Player)

                shootJustPressed = ShootAlias `aliasPressed` inputState
                shootPressed     = shootJustPressed || ShootInput `inPlayerInputBuffer` player
                shootDownPressed =
                    (ShootAlias `aliasPressed` inputState && DownAlias `aliasHold` inputState) ||
                    ShootDownInput `inPlayerInputBuffer` player

updateGrenadeLauncher :: Monad m => GunUpdate GrenadeLauncherData m
updateGrenadeLauncher grenadeLauncher = return $ grenadeLauncher {_data = grenadeLauncherData'}
    where
        grenadeLauncherData  = _data grenadeLauncher
        fireDelayTtl         = max 0.0 (_fireDelayTtl grenadeLauncherData - timeStep)
        grenadeLauncherData' = grenadeLauncherData {_fireDelayTtl = fireDelayTtl}

module Player.Gun.All.GrenadeLauncher
    ( mkGrenadeLauncherGun
    ) where

import Control.Monad.IO.Class (MonadIO)

import Configs
import Configs.All.PlayerGun.GrenadeLauncher
import Constants
import FileCache
import Msg
import Player
import Player.BufferedInputState
import Player.Gun
import Player.Gun.All.GrenadeLauncher.Data
import Player.Gun.All.GrenadeLauncher.Grenade
import Player.Gun.All.GrenadeLauncher.Mine
import Player.Gun.FireDrawState
import Util
import Window.Graphics
import Window.InputState

grenadeLauncherSoundPath      = "event:/SFX Events/Player/Guns/grenade-launcher"       :: FilePath
grenadeLauncherUnderSoundPath = "event:/SFX Events/Player/Guns/grenade-launcher-under" :: FilePath
throwMineSoundPath            = "event:/SFX Events/Player/Guns/mine-toss"              :: FilePath

throwMineFrameTagName = FrameTagName "throwMine" :: FrameTagName

mkGrenadeLauncherGun :: (ConfigsRead m, FileCache m, GraphicsRead m, MonadIO m) => m (Some Gun)
mkGrenadeLauncherGun = do
    grenadeLauncherData <- mkGrenadeLauncherData

    return . Some $ (mkGun grenadeLauncherData GrenadeLauncherGun)
        { _fireDrawData = Just $ _shootFireDrawData grenadeLauncherData
        , _think        = thinkGrenadeLauncher
        , _update       = updateGrenadeLauncher
        }

thinkGrenadeLauncher :: InputRead m => GunThink GrenadeLauncherData m
thinkGrenadeLauncher gunStatus player grenadeLauncher = think <$> readInputState
    where
        think :: InputState -> [Msg ThinkPlayerMsgsPhase]
        think inputState
            | fireDelayTtl <= 0.0 && shootDownPressed && canShoot = if
                | not (canSpendMeter mineMeterCost) -> [mkMsg $ UiMsgInsufficientMeter mineMeterCost shootJustPressed]
                | otherwise                         ->
                    let
                        updateThrowMine = \g ->
                            let
                                gData         = _data g
                                fireDelaySecs = _mineFireDelaySecs $ _config (gData :: GrenadeLauncherData)
                            in g
                                { _data         = gData {_fireDelayTtl = fireDelaySecs}
                                , _fireDrawData = Just $ _throwMineFireDrawData gData
                                }
                    in
                        [ mkMsg $ PlayerMsgUpdateGun updateThrowMine
                        , mkMsg PlayerMsgFiredGun
                        ]

            | fireDelayTtl <= 0.0 && shootPressed && canShoot = if
                | not (canSpendMeter shotMeterCost) -> [mkMsg $ UiMsgInsufficientMeter shotMeterCost shootJustPressed]
                | otherwise                         ->
                    let
                        updateShoot = \g ->
                            let
                                gData         = _data g
                                fireDelaySecs = _shootFireDelaySecs $ _config (gData :: GrenadeLauncherData)
                            in g
                                { _data         = gData {_fireDelayTtl = fireDelaySecs}
                                , _fireDrawData = Just $ _shootFireDrawData gData
                                }
                    in
                        [ mkMsg PlayerMsgFiredGun
                        , mkMsg $ NewThinkProjectileMsgAddM (mkGrenade player)
                        , mkMsg $ AudioMsgPlaySound grenadeLauncherSoundPath pos
                        , mkMsg $ AudioMsgPlaySound grenadeLauncherUnderSoundPath pos
                        , mkMsg $ PlayerMsgUpdateGun updateShoot
                        , mkMsg $ PlayerMsgSpendMeter shotMeterCost
                        ]

            | otherwise = case _torso <$> playerGunFireDrawSprites player of
                Just torsoSpr
                    | throwMineFrameTagName `isSpriteFrameTag` torsoSpr && _frameChanged torsoSpr ->
                        [ mkMsg $ NewThinkProjectileMsgAddM (mkMine player)
                        , mkMsg $ AudioMsgPlaySound throwMineSoundPath pos
                        , mkMsg $ PlayerMsgSpendMeter mineMeterCost
                        ]

                _ -> []

            where
                canSpendMeter = \meterCost -> canSpendPlayerMeter meterCost player

                grenadeLauncherData = _data grenadeLauncher
                fireDelayTtl        = _fireDelayTtl grenadeLauncherData

                cfg           = _config (grenadeLauncherData :: GrenadeLauncherData)
                shotMeterCost = _shotMeterCost cfg
                mineMeterCost = _mineMeterCost cfg
                canShoot      = gunStatus == ActiveStatus Shootable
                pos           = _pos (player :: Player)

                shootJustPressed = ShootAlias `aliasPressed` inputState
                shootPressed     = shootJustPressed || ShootInput `inPlayerInputBuffer` player
                shootDownPressed =
                    (ShootAlias `aliasPressed` inputState && DownAlias `aliasHold` inputState) ||
                    ShootDownInput `inPlayerInputBuffer` player

updateGrenadeLauncher :: Monad m => GunUpdate GrenadeLauncherData m
updateGrenadeLauncher grenadeLauncher = return $ grenadeLauncher {_data = grenadeLauncherData'}
    where
        grenadeLauncherData  = _data grenadeLauncher
        fireDelayTtl         = max 0.0 (_fireDelayTtl grenadeLauncherData - timeStep)
        grenadeLauncherData' = grenadeLauncherData {_fireDelayTtl = fireDelayTtl}
