// Project Sword & Gun Copyright © 2021 Kevin Poretti

#pragma once

#include "CoreMinimal.h"

#include "SwordNGun/SNGTypes.h"
#include "Weapons/SNGWeaponBase.h"
#include "SNGRangedWeaponBase.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWeaponFiredSignature, int32, CurrAmmoCount);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnWeaponHitSignature);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnWeaponReloadedSignature, int32, CurrAmmoCount);

/**
 *	Base class for ranged weapons.
 *
 *	Handles logic for determining if a weapon can be fired and performing the periodic firing of a weapon based on the
 *	configurable rate. This class also handles logic for ammo consumption and playing appropriate effects.
 *
 *	This class has virtual functions meant to be overriden to create the actual firing logic in subclasses, whether that
 *	be a line trace or spawning some kind of projectile.
 */
UCLASS()
class SWORDNGUN_API ASNGRangedWeaponBase : public ASNGWeaponBase
{
	GENERATED_BODY()

public:
	/** Sets default values for this actor's properties. */
	ASNGRangedWeaponBase();

	/**
	 *	Function that sets up the weapon for firing. This sets up a timer which periodically calls the actual weapon
	 *	firing functionality.
	 */
	UFUNCTION(BlueprintCallable)
	void StartFire();

	/**
	 *	Function that sets up the weapon for firing. This clears the timer previously set up by the StartFire function.
	 */
	UFUNCTION(BlueprintCallable)
	void StopFire();

	/**
	 *	Checks to see if this weapon has enough ammo in its "clip" to actually fire.
	 *
	 *	@returns true if the weapon can be fired and false otherwise
	 */
	UFUNCTION(BlueprintCallable)
	bool CanFire() const;

	/**
	 *	Reloads this weapon.
	 *
	 *	NOTE(kevin): This description is supposed to be how this function behaves, however the concept of an ammo
	 *	inventory does not exists yet. All weapons are treated as having infinite ammo in their reserves, so this always
	 *	sets CurrentAmmoCount to MaxAmmoCount.
	 *
	 *	This sets the CurrentAmmoCount to either MaxAmmoCount or the remaining ammo count in the weapons owner's ammo
	 *	inventory, whichever is smaller. This also subtracts the amount of ammo loaded into this weapon's clip from whatever
	 *	was in the weapon owner's ammo inventory.
	 */
	UFUNCTION(BlueprintCallable)
	void Reload();

	/**
	 *	Checks to see if the weapon "clip" is not full and can actually be reloaded.
	 *
	 *	@returns true if the weapon can be reloaded and false otherwise 
	 */
	UFUNCTION(BlueprintCallable)
	bool CanReload() const;

	/**
	 *	Gets the current ammo count in this weapon's "clip"
	 */
	UFUNCTION(BlueprintGetter)
	int32 GetCurrentAmmoCount() const;
	
protected:
	/** Triggered when this weapon is fired. */
	UPROPERTY(BlueprintAssignable, Category="Events")
	FOnWeaponFiredSignature OnFired;

	/** Triggered when this weapon successfully hits a target. */
	UPROPERTY(BlueprintAssignable, Category="Events")
	FOnWeaponHitSignature OnHit;

	/** Triggered when this weapon is reloaded. */
	UPROPERTY(BlueprintAssignable, Category="Events")
	FOnWeaponReloadedSignature OnReloaded;

	/** Name of the muzzle socket on this weapon's skeletal mesh. */
	UPROPERTY(VisibleDefaultsOnly, Category="Effects")
	FName MuzzleSocketName;

	/** Name of the vector parameter that determine's where the tracer effect ends. */
	UPROPERTY(VisibleDefaultsOnly, Category="Effects")
	FName TracerTargetName;

	/** Effect to play at this weapon's muzzle location. */
	UPROPERTY(EditDefaultsOnly, Category="Effects")
	UParticleSystem* MuzzleEffect;

	/** Effect that shows a trail from this weapon's muzzle to the hit location. */
	UPROPERTY(EditDefaultsOnly, Category="Effects")
	UParticleSystem* TracerEffect;

	/** Sound to play when this weapon is fired. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Sounds")
	USoundBase* FireSound;

	/** Sound to play when this weapon successfully hits a target. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Sounds")
	USoundBase* HitMarkerSound;
	
	/** How fast this weapon fires in rounds per minute. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Properties", meta = (ClampMin = 1.0f))
	float RateOfFire;

	/** How much ammo can this weapon store in its "clip". */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Properties", meta = (ClampMin = 0))
	int32 MaxAmmoCount;

	/** Value to multiply this weapons base damage by when this weapon hits a vulnerable flesh physical material. */
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category="Properties", meta = (ClampMin = 0.0f))
	float VulnerableDamageMultiplier;

	/** Current amount of ammo in this weapon's "clip". */
	UPROPERTY(BlueprintReadOnly)
	int32 CurrentAmmoCount;

	/** Damage parameters to be passed to a damage receiver for when this weapon successfully hits a target. */
	UPROPERTY(EditDefaultsOnly, Category="Properties")
	FSNGHitData HitData;

	/** Timer handle for the timer that calls Fire periodically based on the rate of fire of this weapon. */
	FTimerHandle TimerHandle_TimeBetweenShots;

	/** Last time this weapon was fired in seconds. */
	float LastFireTime;

	/**
	 *	Time between shots in seconds.
	 *
	 *	This is the inverse of the RateOfFire parameter and converted from minutes to seconds.
	 */
	float TimeBetweenShots;

	/**
	 *	Plays impacts when this weapon successfully hits something.
	 *
	 *	This function is overriden from the SNGWeaponBase class to modify the ImpactTransform's rotation so that the
	 *	impact effect is spawned with a rotation determined by the direction the weapon was fired from rather than the
	 *	impact point's normal.
	 *
	 *	@param SurfaceType the type of the surface that was hit by this weapon
	 *	@param ImpactTransform the location and rotation of the impact (rotation is usually the impacted normal direction)
	 */
	virtual void PlayImpactEffects(EPhysicalSurface SurfaceType, FTransform ImpactTransform) override;

	/**
	 *	Plays the weapon firing effects like the muzzle flash.
	 */
	virtual void PlayFireEffect();

	/**
	 *	Calls actual firing logic if this weapon is able to be fired and handles ammo subtraction.
	 *
	 *	This function is called via a timer periodically. The rate at which this function is called is determined by
	 *	whatever RateOfFire variable is set to.
	 *
	 *	This function is not meant to be modified on a per class basis. If you want to modify the actual firing logic
	 *	you should either override the DoFire_Implementation function in C++ or implement the DoFire event in Blueprints.
	 */
	void Fire();

	/**
	 *	Function that does the actual firing logic, whether that be performing a line trace for instant weapons or
	 *	spawning a projectile for projectile based weapons. This is called in Fire if this weapon is actually able to
	 *	be fired.
	 *
	 *	If you want to modify the actual firing logic in Blueprints you can implement this event.
	 */
	UFUNCTION(BlueprintNativeEvent)
	void DoFire();

	/**
	 *	C++ implementation of the DoFire function, which is called in Fire if this weapon is actually able to be fired.
	 *
	 *	By default, this just plays this weapon's firing sound. You must override this function to do specific firing
	 *	logic (line cast or spawning a projectile). 
	 */
	virtual void DoFire_Implementation();

	/** Called when the game starts or when spawned */
	virtual void BeginPlay() override;
};
