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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameUserSettings.h"
#include "CharacterAppearanceTypes.h"
#include "SurvivalUserSettings.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSurvivalSettingsApplied);

/**
 * Bornes et pas des reglages numeriques.
 *
 * Ici et pas dans le widget : c'est l'objet de reglages qui borne ses propres
 * valeurs, et l'ecran doit proposer exactement les memes paliers. Deux jeux de
 * constantes finiraient par diverger et le menu afficherait des valeurs que le
 * setter refuse.
 */
namespace SurvivalSettingsRange
{
	// Deux granularites par reglage :
	//   Step      = le pas FIN, celui du glissement au curseur. C'est aussi
	//               celui sur lequel les setters arrondissent : le stocker plus
	//               grossier annulerait le reglage fin juste apres.
	//   ArrowStep = le bond des fleches, pour traverser la plage vite.
	constexpr float SensitivityMin = 5.f;
	constexpr float SensitivityMax = 100.f;
	constexpr float SensitivityStep = 1.f;
	constexpr float SensitivityArrowStep = 5.f;

	constexpr float FieldOfViewMin = 70.f;
	constexpr float FieldOfViewMax = 120.f;
	constexpr float FieldOfViewStep = 1.f;
	constexpr float FieldOfViewArrowStep = 5.f;

	constexpr float CameraShakeMin = 0.f;
	constexpr float CameraShakeMax = 100.f;
	constexpr float CameraShakeStep = 1.f;
	constexpr float CameraShakeArrowStep = 10.f;

	/**
	 * Luminosite en POURCENTAGE et non en gamma.
	 *
	 * Le gamma utile va de 0.8 a 1.4 ; affiche tel quel il tiendrait dans un
	 * intervalle de 0.6 que la ligne, qui n'affiche que des entiers, arrondirait
	 * a "1" partout. On stocke donc 80 a 140 et on divise a l'application.
	 */
	constexpr float BrightnessMin = 80.f;
	constexpr float BrightnessMax = 140.f;
	constexpr float BrightnessStep = 1.f;
	constexpr float BrightnessArrowStep = 10.f;

	/**
	 * Gamma d'affichage a 100 % de luminosite.
	 *
	 * C'est la valeur du moteur (BaseEngine.ini), PAS un neutre a 1.0 :
	 * GEngine->DisplayGamma est un exposant, pas un multiplicateur. Y ecrire 1.0
	 * assombrit tout l'ecran -- et comme GEngine est global, l'editeur avec.
	 */
	constexpr float BaseDisplayGamma = 2.2f;

	/** Echelle de resolution, en pourcentage de la resolution native. */
	constexpr float ResolutionScaleMin = 50.f;
	constexpr float ResolutionScaleMax = 100.f;
	constexpr float ResolutionScaleStep = 1.f;
	constexpr float ResolutionScaleArrowStep = 10.f;

	/**
	 * Multiplicateurs reels correspondant aux extremites de l'echelle affichee.
	 *
	 * Le joueur regle un nombre de 5 a 100 parce que "0.4" ne veut rien dire ;
	 * le pawn, lui, veut le multiplicateur. Le defaut de 35 retombe sur ~0.41,
	 * soit la valeur qui etait codee en dur dans AFpsPlayer.
	 */
	constexpr float LookMultiplierAtMin = 0.05f;
	constexpr float LookMultiplierAtMax = 1.2f;

	/** Les quatre volumes partagent la meme echelle : 0 a 100 %. */
	constexpr float VolumeMin = 0.f;
	constexpr float VolumeMax = 100.f;
	constexpr float VolumeStep = 1.f;
	constexpr float VolumeArrowStep = 10.f;

	/**
	 * Exposant applique au pourcentage avant d'en faire une amplitude.
	 *
	 * L'oreille repond au logarithme de l'amplitude, pas a l'amplitude : un
	 * curseur envoye tel quel donne un reglage inutilisable, ou tout se joue dans
	 * les vingt derniers pourcents et ou la moitie basse ne s'entend deja plus.
	 * Elever au carre est l'approximation habituelle -- 50 % rendent 0,25 en
	 * amplitude, soit environ la moitie du volume PERCU. 0 reste le silence exact.
	 */
	constexpr float VolumeCurveExponent = 2.f;
}

/**
 * Les reglages du joueur, et le seul endroit qui fasse autorite dessus.
 *
 * Sous-classe de UGameUserSettings et non un objet a nous : le moteur fournit
 * deja le singleton, sa persistance dans Saved/Config/Windows/GameUserSettings.ini
 * et tout le volet video (resolution, VSync, qualite). C'est l'equivalent d'un
 * PlayerPrefs + QualitySettings + Screen d'Unity reunis, sauf qu'il est deja
 * ecrit. On l'etend, on ne le remplace pas.
 *
 * Pour que le moteur instancie CETTE classe et pas la sienne, Config/DefaultEngine.ini
 * contient :
 *   [/Script/Engine.Engine]
 *   GameUserSettingsClassName=/Script/EmberWild.SurvivalUserSettings
 *
 * Les UPROPERTY(config) sont lues et ecrites automatiquement : aucun code de
 * serialisation a maintenir.
 */
UCLASS()
class EMBERWILD_API USurvivalUserSettings : public UGameUserSettings
{
	GENERATED_BODY()

public:
	USurvivalUserSettings();

	/**
	 * Le raccourci typé vers le singleton. GEngine->GetGameUserSettings()
	 * renvoie la classe de base ; ce helper evite un cast a chaque appel.
	 */
	UFUNCTION(BlueprintPure, Category = "Reglages", meta = (DisplayName = "Get Survival User Settings"))
	static USurvivalUserSettings* Get();

	/**
	 * Previent tout le monde que les reglages de jeu ont change.
	 *
	 * Indispensable a cause du respawn : le pawn est un NOUVEL acteur a chaque
	 * mort, et une valeur poussee une fois dans l'ancien serait perdue. Chaque
	 * pawn lit les reglages a son BeginPlay et s'abonne ici pour la suite.
	 */
	UPROPERTY(BlueprintAssignable, Category = "Reglages")
	FOnSurvivalSettingsApplied OnSurvivalSettingsApplied;

	/**
	 * Diffuse les reglages de jeu, sans toucher au volet video.
	 *
	 * Distinct d'ApplyNonResolutionSettings, qui repousse aussi la qualite et la
	 * limite de FPS : l'appeler a chaque clic de fleche sur la sensibilite
	 * provoquerait un a-coup pour rien.
	 */
	UFUNCTION(BlueprintCallable, Category = "Reglages")
	void ApplySurvivalSettings();

	/**
	 * Pousse les volumes sur le peripherique audio.
	 *
	 * Volontairement SEPAREE d'ApplySurvivalSettings : cinq commandes vers le
	 * thread audio a chaque clic de fleche sur le champ de vision seraient du
	 * bruit pur. Les deux sont appelees ensemble par ApplyNonResolutionSettings,
	 * donc le chemin de demarrage n'en oublie aucune.
	 */
	UFUNCTION(BlueprintCallable, Category = "Reglages|Audio")
	void ApplyAudioSettings();

	/** Remet les seuls reglages de jeu a leur valeur d'usine. */
	UFUNCTION(BlueprintCallable, Category = "Reglages")
	void SetSurvivalGameplayToDefaults();

	/** Remet a l'usine les seuls reglages de l'onglet Audio. */
	UFUNCTION(BlueprintCallable, Category = "Reglages|Audio")
	void SetSurvivalAudioToDefaults();

	virtual void SetToDefaults() override;
	virtual void ApplyNonResolutionSettings() override;

	// ------------------------------------------------------------------
	// Valeurs pretes a l'emploi
	//
	// Le pawn ne doit jamais lire les champs bruts : ce sont des valeurs
	// d'affichage (5 a 100), pas des multiplicateurs.
	// ------------------------------------------------------------------

	UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
	float GetLookMultiplierX() const;

	UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
	float GetLookMultiplierY() const;

	/** 0 = camera parfaitement fixe, 1 = amplitude d'origine. */
	UFUNCTION(BlueprintPure, Category = "Reglages|Jeu")
	float GetCameraShakeMultiplier() const;

	// ------------------------------------------------------------------
	// Accesseurs bruts, pour l'ecran de reglages
	// ------------------------------------------------------------------

	UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
	float GetLookSensitivity() const { return LookSensitivity; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
	void SetLookSensitivity(float Value);

	UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
	bool GetSeparateVerticalSensitivity() const { return bSeparateVerticalSensitivity; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
	void SetSeparateVerticalSensitivity(bool bValue);

	UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
	float GetLookSensitivityVertical() const { return LookSensitivityVertical; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
	void SetLookSensitivityVertical(float Value);

	UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
	bool GetInvertLookY() const { return bInvertLookY; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
	void SetInvertLookY(bool bValue);

	UFUNCTION(BlueprintPure, Category = "Reglages|Jeu")
	float GetFieldOfView() const { return FieldOfView; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Jeu")
	void SetFieldOfView(float Value);

	UFUNCTION(BlueprintPure, Category = "Reglages|Jeu")
	float GetCameraShakeScale() const { return CameraShakeScale; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Jeu")
	void SetCameraShakeScale(float Value);

	UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
	bool GetSprintToggle() const { return bSprintToggle; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
	void SetSprintToggle(bool bValue);

	UFUNCTION(BlueprintPure, Category = "Reglages|Controles")
	bool GetCrouchToggle() const { return bCrouchToggle; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Controles")
	void SetCrouchToggle(bool bValue);

	UFUNCTION(BlueprintPure, Category = "Reglages|Affichage")
	float GetBrightness() const { return Brightness; }

	/** Ne fait qu'enregistrer : c'est ApplySurvivalSettings qui pousse le gamma. */
	UFUNCTION(BlueprintCallable, Category = "Reglages|Affichage")
	void SetBrightness(float Value);

	/** Remet a l'usine les seuls reglages de l'onglet Affichage qui nous appartiennent. */
	UFUNCTION(BlueprintCallable, Category = "Reglages|Affichage")
	void SetSurvivalDisplayToDefaults();

	// ------------------------------------------------------------------
	// Audio
	//
	// Les quatre volumes sont des POURCENTAGES, comme le bouge de camera : c'est
	// ce que la ligne de reglage affiche, et la conversion en amplitude (courbe
	// comprise) n'appartient qu'a ApplyAudioSettings.
	// ------------------------------------------------------------------

	UFUNCTION(BlueprintPure, Category = "Reglages|Audio")
	float GetMasterVolume() const { return MasterVolume; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Audio")
	void SetMasterVolume(float Value);

	UFUNCTION(BlueprintPure, Category = "Reglages|Audio")
	float GetMusicVolume() const { return MusicVolume; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Audio")
	void SetMusicVolume(float Value);

	UFUNCTION(BlueprintPure, Category = "Reglages|Audio")
	float GetEffectsVolume() const { return EffectsVolume; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Audio")
	void SetEffectsVolume(float Value);

	UFUNCTION(BlueprintPure, Category = "Reglages|Audio")
	float GetUiVolume() const { return UiVolume; }

	UFUNCTION(BlueprintCallable, Category = "Reglages|Audio")
	void SetUiVolume(float Value);

	UFUNCTION(BlueprintPure, Category = "Reglages|Audio")
	bool GetPlayInBackground() const { return bPlayInBackground; }

	/** Vrai = le jeu continue de s'entendre quand la fenetre n'a plus le focus. */
	UFUNCTION(BlueprintCallable, Category = "Reglages|Audio")
	void SetPlayInBackground(bool bValue);

	// ------------------------------------------------------------------
	// Apparence du personnage
	//
	// Ici et pas dans une sauvegarde de partie : l'apparence appartient au
	// JOUEUR et pas a une partie donnee, elle doit le suivre d'une session a
	// l'autre. C'est aussi ce qui donne un visage a qui arrive par invitation
	// Steam sans passer par l'ecran de customisation.
	// ------------------------------------------------------------------

	UFUNCTION(BlueprintPure, Category = "Reglages|Apparence")
	const FCharacterAppearance& GetCharacterAppearance() const { return CharacterAppearance; }

	/**
	 * N'ecrit rien sur le disque : la sauvegarde a lieu a la fermeture de
	 * l'ecran, comme pour tous les autres reglages. Ne diffuse pas non plus
	 * OnSurvivalSettingsApplied -- le pawn ne relit pas son apparence dans les
	 * reglages, il la tient du PlayerState.
	 */
	UFUNCTION(BlueprintCallable, Category = "Reglages|Apparence")
	void SetCharacterAppearance(const FCharacterAppearance& InAppearance);

private:
	/**
	 * Serialisee telle quelle dans le .ini, sous la forme
	 * `CharacterAppearance=(BodyType=BodyA,HeadIndex=0,...)`.
	 *
	 * C'est le dividende de la struct d'index : une apparence faite de
	 * TSoftObjectPtr aurait donne une douzaine de chemins d'assets a ecrire, et
	 * une seule renommee d'asset aurait suffi a la casser silencieusement.
	 */
	UPROPERTY(config)
	FCharacterAppearance CharacterAppearance;

	/** Echelle lisible (5 a 100), convertie a la lecture. Jamais brute. */
	UPROPERTY(config)
	float LookSensitivity;

	UPROPERTY(config)
	bool bSeparateVerticalSensitivity;

	UPROPERTY(config)
	float LookSensitivityVertical;

	UPROPERTY(config)
	bool bInvertLookY;

	UPROPERTY(config)
	float FieldOfView;

	/** Pourcentage. A 0, plus aucun impact camera : pour le mal des transports. */
	UPROPERTY(config)
	float CameraShakeScale;

	UPROPERTY(config)
	bool bSprintToggle;

	UPROPERTY(config)
	bool bCrouchToggle;

	/** Pourcentage, converti en gamma a l'application. */
	UPROPERTY(config)
	float Brightness;

	/** Pourcentages. Le maitre multiplie les trois autres via la hierarchie des SoundClass. */
	UPROPERTY(config)
	float MasterVolume;

	UPROPERTY(config)
	float MusicVolume;

	UPROPERTY(config)
	float EffectsVolume;

	UPROPERTY(config)
	float UiVolume;

	/**
	 * Le moteur coupe le son des qu'il perd le focus, et c'est son defaut d'usine.
	 * En coop c'est justement le mauvais defaut pour qui bascule sur Discord.
	 */
	UPROPERTY(config)
	bool bPlayInBackground;
};
