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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "CharacterAppearanceTypes.h"
#include "CharacterPreviewActor.generated.h"

class UCameraComponent;
class UCharacterAppearanceComponent;
class USkeletalMeshComponent;

/**
 * Les deux cadrages de l'ecran de customisation.
 *
 * Deux et pas un par categorie : ce qu'on veut, c'est voir le visage quand on
 * choisit une barbe et la silhouette entiere quand on choisit un corps. Un
 * cadrage par categorie donnerait onze reglages a poser a la main pour onze
 * fois le meme resultat.
 */
UENUM(BlueprintType)
enum class ECharacterPreviewFraming : uint8
{
	FullBody,
	Face,

	Count UMETA(Hidden)
};

/**
 * Le mannequin de l'ecran de customisation : un personnage pose dans la map de
 * menu, qu'on fait tourner a la souris et que la camera cadre selon ce que le
 * joueur est en train de regler.
 *
 * Il porte le MEME UCharacterAppearanceComponent que le pawn de jeu, et c'est
 * tout l'interet : le personnage qu'on regarde ici est monte par exactement le
 * code qui montera celui que les autres joueurs verront. Deux chemins de
 * montage finiraient par diverger, et le joueur s'en apercevrait au pire
 * moment -- une fois en partie.
 */
UCLASS()
class EMBERWILD_API ACharacterPreviewActor : public AActor
{
	GENERATED_BODY()

public:
	ACharacterPreviewActor();

	virtual void Tick(float DeltaSeconds) override;

	/** Monte l'apparence sur le mannequin. */
	UFUNCTION(BlueprintCallable, Category = "Preview")
	void ApplyAppearance(const FCharacterAppearance& InAppearance);

	/** La camera glisse vers le cadrage demande. Sans effet s'il y est deja. */
	UFUNCTION(BlueprintCallable, Category = "Preview")
	void SetFraming(ECharacterPreviewFraming NewFraming);

	/** Fait pivoter le personnage. Appelee pendant un clic-glisser. */
	UFUNCTION(BlueprintCallable, Category = "Preview")
	void AddPreviewYaw(float DeltaDegrees);

	/** Remet le personnage face a la camera. */
	UFUNCTION(BlueprintCallable, Category = "Preview")
	void ResetPreviewYaw();

	UFUNCTION(BlueprintPure, Category = "Preview")
	UCameraComponent* GetPreviewCamera() const { return PreviewCamera; }

	UFUNCTION(BlueprintPure, Category = "Preview")
	UCharacterAppearanceComponent* GetAppearanceComponent() const { return AppearanceComponent; }

	/** Le reglage vit ici et pas sur le widget : c'est une sensibilite de camera. */
	UFUNCTION(BlueprintPure, Category = "Preview")
	float GetYawPerPixel() const { return YawPerPixel; }

protected:
	virtual void BeginPlay() override;

	/** Le porteur d'animation et de pose ; les pieces s'y accrochent. */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Preview")
	TObjectPtr<USkeletalMeshComponent> BodyMesh;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Preview")
	TObjectPtr<UCharacterAppearanceComponent> AppearanceComponent;

	/**
	 * Pivot de rotation, entre la racine et le mesh.
	 *
	 * Le personnage tourne en faisant pivoter CE composant et jamais l'acteur :
	 * la camera est fille de l'acteur, la faire tourner avec lui donnerait un
	 * decor qui defile pendant qu'on regarde un personnage immobile.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Preview")
	TObjectPtr<USceneComponent> TurntablePivot;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Preview")
	TObjectPtr<UCameraComponent> PreviewCamera;

	/**
	 * Les deux positions de camera, a placer A LA SOURIS dans le viewport du
	 * Blueprint plutot qu'a taper en coordonnees.
	 *
	 * Ce sont des reperes vides : la camera n'y est pas accrochee, elle glisse
	 * vers eux. Sans quoi changer de categorie la ferait sauter d'un cadrage a
	 * l'autre, ce qui se lit comme une coupure de montage.
	 */
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Preview|Framing")
	TObjectPtr<USceneComponent> FullBodyViewSpot;

	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Preview|Framing")
	TObjectPtr<USceneComponent> FaceViewSpot;

	/**
	 * Vitesse de glissement de la camera entre deux cadrages.
	 *
	 * Une vitesse d'interpolation et pas une duree : le mouvement freine en
	 * arrivant au lieu de s'arreter net, et deux changements de categorie
	 * coup sur coup s'enchainent sans a-coup -- la camera repart d'ou elle est.
	 */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Preview|Framing", meta = (ClampMin = "0.5"))
	float FramingInterpSpeed = 6.f;

	/** Degres de rotation par pixel de deplacement horizontal de la souris. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Preview|Turntable")
	float YawPerPixel = 0.35f;

private:
	/** Le repere vise par la camera, selon le cadrage courant. */
	USceneComponent* GetSpotForFraming(ECharacterPreviewFraming InFraming) const;

	/**
	 * Coupe le Tick quand la camera est arrivee.
	 *
	 * Meme principe que le couvercle d'AStorageContainer : cet acteur ne tick
	 * que pendant qu'il bouge. Un mannequin immobile dans un menu n'a aucune
	 * raison de couter une frame.
	 */
	void UpdateFramingInterpolation(float DeltaSeconds);

	ECharacterPreviewFraming CurrentFraming = ECharacterPreviewFraming::FullBody;

	/** Rotation accumulee par le clic-glisser, en degres. */
	float TurntableYaw = 0.f;
};
