// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "SurvivalStatsComponent.generated.h"

/** Diffuse a chaque changement de vie, faim ou soif. L'UI s'y abonne. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSurvivalStatsChanged);

/** Diffuse une seule fois, quand la vie atteint zero. */
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSurvivalDeath);

/**
 * Vie, faim et soif du personnage.
 *
 * Les valeurs descendent sur un timer a basse frequence et non dans Tick :
 * une jauge qui perd trois points par minute n'a aucune raison d'etre
 * recalculee 120 fois par seconde.
 *
 * Les vitesses sont exprimees PAR MINUTE, parce que c'est l'unite dans
 * laquelle on raisonne quand on equilibre un jeu de survie -- "cent points
 * de faim en trente minutes" se lit, "0.055 par seconde" non.
 */
UCLASS(ClassGroup = (Survival), meta = (BlueprintSpawnableComponent))
class EMBERWILD_API USurvivalStatsComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	USurvivalStatsComponent();

	virtual void BeginPlay() override;
	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	/** Rend de la vie, de la faim et de la soif. Les valeurs sont bornees au max. */
	UFUNCTION(BlueprintCallable, Category = "Survie")
	void ApplyRestore(float HealthAmount, float HungerAmount, float ThirstAmount);

	UFUNCTION(BlueprintCallable, Category = "Survie")
	void ApplyDamage(float Amount);

	UFUNCTION(BlueprintPure, Category = "Survie")
	float GetHealth() const { return Health; }

	UFUNCTION(BlueprintPure, Category = "Survie")
	float GetHunger() const { return Hunger; }

	UFUNCTION(BlueprintPure, Category = "Survie")
	float GetThirst() const { return Thirst; }

	/** Ratios 0-1, directement utilisables par une ProgressBar. */
	UFUNCTION(BlueprintPure, Category = "Survie")
	float GetHealthPercent() const { return MaxHealth > 0.f ? Health / MaxHealth : 0.f; }

	UFUNCTION(BlueprintPure, Category = "Survie")
	float GetHungerPercent() const { return MaxHunger > 0.f ? Hunger / MaxHunger : 0.f; }

	UFUNCTION(BlueprintPure, Category = "Survie")
	float GetThirstPercent() const { return MaxThirst > 0.f ? Thirst / MaxThirst : 0.f; }

	UFUNCTION(BlueprintPure, Category = "Survie")
	bool IsDead() const { return bDead; }

	UPROPERTY(BlueprintAssignable, Category = "Survie")
	FOnSurvivalStatsChanged OnStatsChanged;

	UPROPERTY(BlueprintAssignable, Category = "Survie")
	FOnSurvivalDeath OnDied;

protected:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Survie|Vie", meta = (ClampMin = "1"))
	float MaxHealth = 100.f;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Survie|Faim", meta = (ClampMin = "1"))
	float MaxHunger = 100.f;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Survie|Soif", meta = (ClampMin = "1"))
	float MaxThirst = 100.f;

	/** Points de faim perdus par minute. 3.3 vide une jauge pleine en ~30 min. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie|Faim", meta = (ClampMin = "0"))
	float HungerDecayPerMinute = 3.3f;

	/** La soif descend plus vite que la faim : c'est le besoin le plus pressant. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie|Soif", meta = (ClampMin = "0"))
	float ThirstDecayPerMinute = 5.f;

	/** Vie perdue par minute quand la faim est a zero. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie|Vie", meta = (ClampMin = "0"))
	float StarvationDamagePerMinute = 6.f;

	/** Vie perdue par minute quand la soif est a zero. Cumulable avec la faim. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Survie|Vie", meta = (ClampMin = "0"))
	float DehydrationDamagePerMinute = 9.f;

	/**
	 * Secondes entre deux mises a jour. Une seconde suffit largement et reste
	 * assez fin pour que les barres bougent de facon fluide a l'oeil.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Survie", meta = (ClampMin = "0.05"))
	float UpdateInterval = 1.f;

private:
	/** Appelee par le timer : fait descendre les jauges et applique les degats. */
	void UpdateStats();

	/**
	 * Les jauges viennent d'arriver du serveur : on rediffuse le delegue
	 * habituel, et WBP_SurvivalStats se met a jour sans une ligne de changement.
	 */
	UFUNCTION()
	void OnRep_Stats();

	FTimerHandle UpdateTimerHandle;

	// La simulation tourne UNIQUEMENT sur le serveur : quatre clients qui
	// feraient descendre leur faim chacun de leur cote divergeraient en
	// quelques minutes, et le premier a mourir ne serait pas le meme partout.
	// Les clients ne font que recevoir.

	UPROPERTY(ReplicatedUsing = OnRep_Stats)
	float Health = 0.f;

	UPROPERTY(ReplicatedUsing = OnRep_Stats)
	float Hunger = 0.f;

	UPROPERTY(ReplicatedUsing = OnRep_Stats)
	float Thirst = 0.f;

	/**
	 * ReplicatedUsing et non simplement Replicated : OnDied n'est diffuse que
	 * sur le serveur, or c'est ce delegue qui declenche l'ecran de mort et le
	 * respawn cote joueur. Sans OnRep, un client mourrait sans jamais le savoir
	 * et continuerait a jouer un personnage que le serveur considere mort.
	 */
	UPROPERTY(ReplicatedUsing = OnRep_Dead)
	bool bDead = false;

	UFUNCTION()
	void OnRep_Dead();
};
