// Icon Studio - Mathew Simon

#pragma once

#include "CoreMinimal.h"
#include "EditorSubsystem.h"
#include "IconStudioRenderer.h"
#include "IconStudioTypes.h"
#include "IconStudioSubsystem.generated.h"

class UIconStudioPreset;
class UIconStudioSession;
class UMaterialInterface;
class UStaticMesh;
class UTexture2D;

// Delegues C++ et non dynamiques : la fenetre est un widget Slate, qui n'est pas
// un UObject et ne peut donc pas s'abonner a un delegue dynamique. Les fonctions
// de l'outil restent BlueprintCallable pour rester pilotables depuis un script.
DECLARE_MULTICAST_DELEGATE_OneParam(FOnIconStudioPreviewUpdated, UTexture2D* /*PreviewTexture*/);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnIconStudioStatus, const FString& /*Message*/);

/**
 * L'API de l'outil, cote Blueprint comme cote C++.
 *
 * Un sous-systeme d'editeur plutot qu'un objet possede par le widget : la scene
 * de rendu coute cher a construire, et un Editor Utility Widget est detruit et
 * recree a chaque fois qu'on ferme son onglet. Poser l'etat ici, c'est retrouver
 * son preset et sa liste de lot en rouvrant la fenetre, et ne payer la mise en
 * place du monde de preview qu'une fois par session d'editeur.
 *
 * Le widget ne fait donc que trois choses : afficher la texture renvoyee par
 * RefreshPreview, montrer la session dans un Details View, et appeler les deux
 * boutons de generation.
 */
UCLASS()
class ICONSTUDIO_API UIconStudioSubsystem : public UEditorSubsystem
{
	GENERATED_BODY()

public:
	//~ USubsystem
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;
	virtual void Deinitialize() override;
	//~ End USubsystem

	/** L'objet a poser dans le Details View de la fenetre. */
	UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = "Icon Studio")
	UIconStudioSession* GetSession();

	/**
	 * Garantit qu'un preset est selectionne : reprend celui du projet s'il en
	 * existe un, en cree un sinon.
	 *
	 * Sans lui, l'outil s'ouvre sur une fenetre qui ne fait rien tant qu'on n'a
	 * pas devine qu'il fallait aller creer un Data Asset dans le Content Browser.
	 * Un outil qui demande une manip prealable pour donner signe de vie est un
	 * outil qu'on croit casse.
	 */
	UFUNCTION(BlueprintCallable, Category = "Icon Studio")
	UIconStudioPreset* EnsurePreset();

	/**
	 * Relance le rendu et renvoie la texture a afficher.
	 * Renvoie null s'il manque le preset ou le sujet -- pas une erreur, juste
	 * une fenetre pas encore renseignee.
	 */
	UFUNCTION(BlueprintCallable, Category = "Icon Studio")
	UTexture2D* RefreshPreview();

	/** Genere l'icone d'un seul objet : PNG, texture, et affectation du champ. */
	UFUNCTION(BlueprintCallable, Category = "Icon Studio")
	bool GenerateForSubject(UObject* Subject);

	/** Genere celle du sujet actuellement previsualise. */
	UFUNCTION(BlueprintCallable, Category = "Icon Studio")
	bool GenerateCurrent();

	/** Traite toute la liste de lot. Renvoie le nombre d'icones ecrites. */
	UFUNCTION(BlueprintCallable, Category = "Icon Studio")
	int32 GenerateBatch();

	/** Ajoute la selection du Content Browser au lot, sans doublon. */
	UFUNCTION(BlueprintCallable, Category = "Icon Studio")
	int32 AddContentBrowserSelectionToBatch();

	/** Prend le premier asset selectionne dans le Content Browser comme sujet. */
	UFUNCTION(BlueprintCallable, Category = "Icon Studio")
	bool SetSubjectFromContentBrowser();

	UFUNCTION(BlueprintCallable, Category = "Icon Studio")
	void ClearBatch();

	/**
	 * Emis a chaque tentative de rendu, reussie ou non -- avec un pointeur nul
	 * dans le second cas. La fenetre s'en sert aussi pour raccrocher son panneau
	 * de preset, qui doit suivre meme quand il n'y a rien a afficher.
	 */
	FOnIconStudioPreviewUpdated OnPreviewUpdated;

	/** Ligne de statut lisible, affichee sous les boutons. */
	FOnIconStudioStatus OnStatus;

private:
	/** Retrouve le mesh a rendre, que le sujet soit un mesh ou un DataAsset. */
	UStaticMesh* ResolveMesh(UObject* Subject, UMaterialInterface*& OutMaterialOverride) const;

	/** Nom de l'asset produit, prefixe compris. */
	FString BuildAssetName(UObject* Subject, const UIconStudioPreset* Preset) const;

	UTexture2D* CreateOrUpdateTexture(const FIconStudioImage& Image, const FString& PackagePath, const FString& AssetName, TArray<UPackage*>& OutTouchedPackages);
	bool AssignIcon(UObject* Subject, UTexture2D* Texture, TArray<UPackage*>& OutTouchedPackages) const;
	bool WritePng(const FIconStudioImage& Image, const FString& AssetName) const;

	bool GenerateInternal(UObject* Subject, TArray<UPackage*>& OutTouchedPackages);

	/** Ecrit un package sur le disque, sans condition. */
	static bool SavePackageToDisk(UPackage* Package);

	/** Ecrit les packages touches par une generation, si le reglage l'autorise. */
	void SavePackages(const TArray<UPackage*>& Packages) const;

	void HandleSessionChanged();
	void HandleObjectPropertyChanged(UObject* Object, struct FPropertyChangedEvent& Event);
	void Status(const FString& Message, bool bIsError = false);

	UPROPERTY(Transient)
	TObjectPtr<UIconStudioSession> Session;

	TUniquePtr<FIconStudioRenderer> Renderer;

	FDelegateHandle PropertyChangedHandle;
};
