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

#pragma once

#include "CoreMinimal.h"
#include "Engine/DeveloperSettings.h"
// Sound/SoundClass.h en entier, et pas une simple declaration anticipee :
// TStrongObjectPtr exige un type COMPLET, la ou TObjectPtr s'en passait.
#include "Sound/SoundClass.h"
#include "UObject/StrongObjectPtr.h"
#include "SurvivalAudioSettings.generated.h"

/**
 * Ou le C++ trouve les SoundClass du jeu. Reglable dans Project Settings, section
 * "Survival Audio".
 *
 * UDeveloperSettings et pas des chemins codes en dur dans le .cpp : renommer ou
 * deplacer SC_Music casserait silencieusement le curseur Musique, sans la moindre
 * erreur de compilation. Ici la reference est un asset comme un autre, suivie par
 * le redirecteur de l'editeur et par le cooker.
 *
 * Ces references sont SOFT : un UDeveloperSettings est construit tres tot, bien
 * avant que le gestionnaire d'assets ne soit pret. Une reference dure forcerait le
 * chargement des SoundClass au demarrage du moteur, ce qui est exactement le genre
 * de dependance qui fait echouer un cook.
 *
 * A NOTER : le SoundMix, lui, n'est PAS ici. Il est deja declare dans
 * Project Settings > Audio > Default Base Sound Mix, d'ou le moteur le reactive a
 * chaque chargement de map -- c'est precisement ce qu'on veut, et le redeclarer ici
 * ouvrirait la porte a deux references qui divergent.
 */
UCLASS(config = Game, defaultconfig, meta = (DisplayName = "Survival Audio"))
class EMBERWILD_API USurvivalAudioSettings : public UDeveloperSettings
{
	GENERATED_BODY()

public:
	/** Range la page sous "Game" et non sous "Plugins". */
	virtual FName GetCategoryName() const override;

	/**
	 * Le CDO, seul exemplaire qui existe.
	 *
	 * Non const alors que rien n'est modifie du point de vue du joueur : les
	 * accesseurs ci-dessous memorisent l'asset resolu dans un champ Transient, comme
	 * le fait UAudioSettings pour sa propre SoundClass par defaut.
	 */
	static USurvivalAudioSettings* Get();

	/**
	 * Le sommet de la hierarchie : toutes les autres classes en descendent, donc son
	 * volume les multiplie toutes.
	 */
	UPROPERTY(config, EditAnywhere, Category = "Sound Classes")
	TSoftObjectPtr<USoundClass> MasterSoundClass;

	/**
	 * Imposee au lecteur de musique par UMusicSubsystem, donc les morceaux eux-memes
	 * n'ont rien a declarer -- ajouter une piste a une playlist suffit.
	 */
	UPROPERTY(config, EditAnywhere, Category = "Sound Classes")
	TSoftObjectPtr<USoundClass> MusicSoundClass;

	/**
	 * A declarer aussi comme Default Sound Class dans Project Settings > Audio :
	 * tout son qui n'annonce pas sa classe atterrit alors ici, ce qui evite qu'un
	 * bruitage oublie echappe au curseur Effets.
	 */
	UPROPERTY(config, EditAnywhere, Category = "Sound Classes")
	TSoftObjectPtr<USoundClass> EffectsSoundClass;

	/** A poser a la main sur les sons d'interface (survol, validation). */
	UPROPERTY(config, EditAnywhere, Category = "Sound Classes")
	TSoftObjectPtr<USoundClass> UiSoundClass;

	USoundClass* GetMasterSoundClass();
	USoundClass* GetMusicSoundClass();
	USoundClass* GetEffectsSoundClass();
	USoundClass* GetUiSoundClass();

private:
	/**
	 * Charge au premier appel, puis rend le pointeur memorise.
	 *
	 * Le cache n'est pas qu'une optimisation : il fait aussi office d'ancre pour le
	 * ramasse-miettes. Une SoundClass chargee par un LoadSynchronous et referencee
	 * nulle part serait collectee, et le curseur cesserait d'agir en pleine partie.
	 */
	USoundClass* Resolve(const TSoftObjectPtr<USoundClass>& Asset, TStrongObjectPtr<USoundClass>& Cache);

	/**
	 * TStrongObjectPtr et surtout PAS UPROPERTY(Transient) : c'est ce qui plantait
	 * le jeu package au demarrage, alors que tout allait bien dans l'editeur.
	 *
	 * Le CDO d'un UDeveloperSettings naît avant CloseDisregardForGC, donc il vit dans
	 * le pool des objets PERMANENTS. Le moteur ne les scanne jamais -- ils sont
	 * immortels par construction -- et exige en echange qu'ils ne referencent que des
	 * objets du root set, sans quoi ils garderaient un pointeur sur un objet collecte.
	 * VerifyGCAssumptions le controle et c'est une erreur FATALE :
	 *
	 *   Disregard for GC object Default__SurvivalAudioSettings referencing SoundClass
	 *   SC_Master which is not part of root set. Property: CachedMasterSoundClass
	 *   Fatal error: Encountered 3 object(s) breaking Disregard for GC assumptions.
	 *
	 * Invisible dans l'editeur, ou les SoundClass sont deja chargees quand le pool se
	 * ferme, donc permanentes elles aussi. En jeu package elles arrivent apres, et le
	 * jeu meurt au premier ramassage -- pendant le chargement de la premiere map.
	 *
	 * TStrongObjectPtr ancre l'asset exactement comme le faisait la UPROPERTY, mais
	 * range sa reference dans GGCObjectReferencer, le seul objet que VerifyGCAssumptions
	 * saute explicitement. Le CDO ne reference donc plus rien, et l'asset reste vivant.
	 */
	TStrongObjectPtr<USoundClass> CachedMasterSoundClass;

	TStrongObjectPtr<USoundClass> CachedMusicSoundClass;

	TStrongObjectPtr<USoundClass> CachedEffectsSoundClass;

	TStrongObjectPtr<USoundClass> CachedUiSoundClass;
};
