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


#include "HeldItemComponent.h"

#include "Animation/AnimMontage.h"
#include "Components/SkeletalMeshComponent.h"
#include "Components/StaticMeshComponent.h"
#include "FpsPlayer.h"
#include "InventoryComponent.h"
#include "ItemDataAsset.h"
#include "Net/UnrealNetwork.h"

UHeldItemComponent::UHeldItemComponent()
{
	// Aucun Tick : le montage est evenementiel. Il ne se declenche qu'au
	// changement de case active ou de contenu d'inventaire, et les deux sont
	// deja des delegues.
	PrimaryComponentTick.bCanEverTick = false;

	// Obligatoire, et pas seulement pour repliquer HeldItem : sans lui le moteur
	// JETTE SILENCIEUSEMENT Server_SetHeldItem. Aucun message, aucune erreur --
	// l'objet reste simplement invisible chez les autres joueurs.
	SetIsReplicatedByDefault(true);
}

void UHeldItemComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	// COND_SkipOwner, exactement comme AFpsPlayer::bIsSprinting : le proprietaire
	// est celui qui a DECIDE de ce changement et l'a deja applique en local. Le
	// lui renvoyer poserait une valeur ayant un aller-retour de retard, ce qui
	// fait clignoter l'objet quand on change de case rapidement.
	DOREPLIFETIME_CONDITION(UHeldItemComponent, HeldItem, COND_SkipOwner);
}

void UHeldItemComponent::BeginPlay()
{
	Super::BeginPlay();

	AFpsPlayer* Player = GetOwningPlayer();
	if (!Player)
	{
		UE_LOG(LogTemp, Warning, TEXT("UHeldItemComponent : porteur %s n'est pas un AFpsPlayer, aucun objet ne sera montre en main."),
			*GetNameSafe(GetOwner()));
		return;
	}

	if (UInventoryComponent* Inventory = Player->GetInventoryComponent())
	{
		Inventory->OnSelectedHotbarSlotChanged.AddDynamic(this, &UHeldItemComponent::HandleSelectedHotbarSlotChanged);

		// Le second abonnement n'est pas une redondance : vider la case active
		// au glisser-deposer, ou consommer la derniere charge de ce qu'on tient,
		// ne change pas l'INDEX selectionne. Sans lui, la pomme resterait en main
		// apres avoir ete mangee.
		Inventory->OnInventoryChanged.AddDynamic(this, &UHeldItemComponent::HandleInventoryChanged);
	}

	// Chez l'hote et en solo, tout est deja en place a cet instant. Chez un
	// client le controller peut manquer encore : AFpsPlayer rappellera alors
	// depuis OnRep_Controller, et cette fonction est idempotente.
	RefreshFromInventory();
}

void UHeldItemComponent::OnUnregister()
{
	// Les composants engendres appartiennent a l'acteur et disparaissent avec
	// lui. On ne lache que nos references, pour ne pas retenir des objets que le
	// GC doit pouvoir reprendre.
	FirstPersonHeldMesh = nullptr;
	ThirdPersonHeldMesh = nullptr;

	Super::OnUnregister();
}

AFpsPlayer* UHeldItemComponent::GetOwningPlayer() const
{
	return Cast<AFpsPlayer>(GetOwner());
}

void UHeldItemComponent::HandleInventoryChanged()
{
	RefreshFromInventory();
}

void UHeldItemComponent::HandleSelectedHotbarSlotChanged(int32 /*NewIndex*/)
{
	RefreshFromInventory();
}

void UHeldItemComponent::RefreshFromInventory()
{
	const AFpsPlayer* Player = GetOwningPlayer();
	if (!Player)
	{
		return;
	}

	// Reposee AVANT la garde ci-dessous, et c'est tout l'interet : le passage de
	// « pas encore controle » a « controle localement » est justement ce qui doit
	// cacher l'objet en main, et AFpsPlayer rappelle cette fonction depuis
	// OnRep_Controller. La poser apres la garde ne la ferait jamais tourner chez
	// celui qui vient de prendre le controle.
	UpdateLocalVisibility();

	// LA garde de ce composant. Chez l'hote, le pawn de chaque ami porte aussi
	// un inventaire et un UHeldItemComponent : lire la case active d'un pawn
	// distant y trouverait toujours l'index 0, puisque SelectedHotbarIndex n'est
	// pas replique. Sans ce test, le serveur remplacerait la hache d'un client
	// par le contenu de sa premiere case, en boucle.
	if (!Player->IsLocallyControlled())
	{
		return;
	}

	const UInventoryComponent* Inventory = Player->GetInventoryComponent();
	if (!Inventory)
	{
		return;
	}

	const FInventorySlot Selected = Inventory->GetSelectedSlot();
	SetHeldItem(Selected.IsEmpty() ? nullptr : Selected.Item.Get());

	// Reposee meme quand SetHeldItem est sortie tot faute de changement : chez un
	// client, les premiers rafraichissements tombent avant que le controller soit
	// la, donc avant qu'on ait le droit de toucher aux bras. Sans ce rattrapage,
	// un joueur qui reapparait avec sa pioche deja selectionnee garderait les
	// cycles mains nues jusqu'a ce qu'il change de case.
	UpdateArmsAnimLayer();
}

void UHeldItemComponent::SetHeldItem(UItemDataAsset* NewItem)
{
	if (HeldItem == NewItem)
	{
		return;
	}

	HeldItem = NewItem;
	ApplyHeldVisuals();

	// La couche AVANT le montage : le geste de sortie doit se superposer aux
	// cycles du nouvel outil, pas a ceux qu'on vient de quitter. L'inverse se
	// voit sur la premiere image, la main partant de la mauvaise pose.
	UpdateArmsAnimLayer();
	PlayEquipMontage();

	// En local d'abord, pour que l'objet apparaisse sous les doigts sans attendre
	// un aller-retour ; le serveur suit. Le sens inverse donnerait une main qui
	// se remplit avec un temps de retard a chaque coup de molette.
	if (const AActor* Owner = GetOwner(); Owner && !Owner->HasAuthority())
	{
		Server_SetHeldItem(NewItem);
	}
}

void UHeldItemComponent::UpdateArmsAnimLayer()
{
	AFpsPlayer* Player = GetOwningPlayer();
	if (!Player || !Player->IsLocallyControlled())
	{
		return;
	}

	USkeletalMeshComponent* Arms = Player->GetFirstPersonArms();
	if (!Arms)
	{
		return;
	}

	// Le mesh REELLEMENT monte fait foi, pas le seul HeldItem : un objet dont on
	// a oublie de cocher bShowInHand ferait sinon jouer les cycles de la pioche
	// sur une main vide.
	const TSubclassOf<UAnimInstance> DesiredLayer =
		(bItemVisibleInHand && HeldItem) ? HeldItem->ArmsAnimLayer : nullptr;

	if (DesiredLayer == LinkedArmsLayer)
	{
		return;
	}

	// Delier AVANT de lier, toujours. LinkAnimClassLayers ajoute une couche sans
	// retirer la precedente : enchainer hache puis pioche laisserait les deux en
	// place, et la hache l'emporterait sur toute fonction que la pioche
	// n'implemente pas. Aucun message ne le signale.
	if (LinkedArmsLayer)
	{
		Arms->UnlinkAnimClassLayers(LinkedArmsLayer);
	}

	if (DesiredLayer)
	{
		Arms->LinkAnimClassLayers(DesiredLayer);
	}

	LinkedArmsLayer = DesiredLayer;
}

void UHeldItemComponent::PlayEquipMontage()
{
	// Ranger un objet ne joue rien : le pack ne livre que des gestes de sortie
	// d'outil, et un desequipement sans animation ne se remarque pas -- l'objet
	// suivant arrive dans la foulee avec le sien.
	if (!HeldItem)
	{
		return;
	}

	PlayLocalArmsMontage(HeldItem->EquipMontage, /*bSkipIfAlreadyPlaying=*/false);
}

EUseMontageResult UHeldItemComponent::PlayUseMontage()
{
	// Le test porte sur l'OBJET et pas sur le mesh monte : une pomme n'apparait
	// pas dans la main (bShowInHand faux) mais elle occupe bien la case active,
	// et la manger ne doit pas declencher un coup de poing.
	UAnimMontage* const Montage = HeldItem ? HeldItem->UseMontage.Get() : UnarmedUseMontage.Get();

	if (!Montage)
	{
		return EUseMontageResult::NoMontage;
	}

	// Le geste deja en cours est distingue AVANT l'appel, parce que
	// PlayLocalArmsMontage rend false dans les deux cas et que l'appelant, lui,
	// doit les separer : l'un enverra son marqueur d'impact, l'autre jamais.
	const AFpsPlayer* Player = GetOwningPlayer();
	const USkeletalMeshComponent* Arms = Player ? Player->GetFirstPersonArms() : nullptr;
	const UAnimInstance* ArmsAnim = Arms ? Arms->GetAnimInstance() : nullptr;
	if (ArmsAnim && ArmsAnim->Montage_IsPlaying(Montage))
	{
		return EUseMontageResult::AlreadyPlaying;
	}

	// Chez un pawn distant les bras ne jouent rien (voir PlayLocalArmsMontage) :
	// aucun marqueur n'en sortira, d'ou NoMontage plutot qu'un etat a part. La
	// garde IsLocallyControlled() du pawn reste seule juge de ce qui part sur
	// le reseau.
	return PlayLocalArmsMontage(Montage, /*bSkipIfAlreadyPlaying=*/true)
		? EUseMontageResult::Started
		: EUseMontageResult::NoMontage;
}

bool UHeldItemComponent::PlayCrouchMontage(bool bEntering)
{
	UAnimMontage* const Held = bEntering ? HeldEnterCrouchMontage.Get() : HeldExitCrouchMontage.Get();
	UAnimMontage* const Unarmed = bEntering ? UnarmedEnterCrouchMontage.Get() : UnarmedExitCrouchMontage.Get();

	// Le repli sur la version mains vides est ce qui permet de n'en fournir
	// qu'une au depart : les gestes « avec objet » arriveront plus tard sans
	// qu'on ait a repasser dans le code.
	//
	// Le test porte sur le MESH monte et pas sur HeldItem, contrairement au clic
	// gauche : ce qui change la posture des genoux, c'est un objet qu'on voit
	// dans la main, pas une pomme rangee dans la case active.
	UAnimMontage* const Montage = (bItemVisibleInHand && Held) ? Held : Unarmed;

	// Pas de bSkipIfAlreadyPlaying : marteler la touche d'accroupissement doit
	// relancer le geste, puisque la capsule, elle, change bel et bien de taille
	// a chaque fois.
	return PlayLocalArmsMontage(Montage, /*bSkipIfAlreadyPlaying=*/false);
}

bool UHeldItemComponent::PlayLocalArmsMontage(UAnimMontage* Montage, bool bSkipIfAlreadyPlaying)
{
	if (!Montage)
	{
		return false;
	}

	AFpsPlayer* Player = GetOwningPlayer();

	// Les bras sont en OnlyOwnerSee : chez l'hote, le pawn de chaque ami porte
	// AUSSI un squelette de bras que personne ne rend. Y jouer un montage ferait
	// tourner quatre machines a etats pour rien.
	if (!Player || !Player->IsLocallyControlled())
	{
		return false;
	}

	if (bSkipIfAlreadyPlaying)
	{
		const USkeletalMeshComponent* Arms = Player->GetFirstPersonArms();
		const UAnimInstance* ArmsAnim = Arms ? Arms->GetAnimInstance() : nullptr;
		if (ArmsAnim && ArmsAnim->Montage_IsPlaying(Montage))
		{
			return false;
		}
	}

	return Player->PlayArmsMontage(Montage) > 0.f;
}

void UHeldItemComponent::Server_SetHeldItem_Implementation(UItemDataAsset* NewItem)
{
	if (HeldItem == NewItem)
	{
		return;
	}

	HeldItem = NewItem;

	// Le serveur rend lui aussi les pawns distants -- l'hote voit ses amis. Sans
	// cet appel, la hache n'existerait que chez les clients.
	ApplyHeldVisuals();
}

void UHeldItemComponent::OnRep_HeldItem()
{
	ApplyHeldVisuals();
}

void UHeldItemComponent::ApplyHeldVisuals()
{
	EnsureHeldMeshComponents();

	const UItemDataAsset* Item = HeldItem;

	// Un objet peut tres bien n'avoir aucune representation en main : c'est le
	// cas par defaut de toutes les ressources du jeu. On demonte alors, sans que
	// ce soit une anomalie.
	const bool bShow = Item != nullptr && Item->bShowInHand && Item->GetHeldMesh() != nullptr;
	bItemVisibleInHand = bShow;

	if (FirstPersonHeldMesh)
	{
		if (bShow)
		{
			Item->ApplyHeldVisualsTo(FirstPersonHeldMesh);
			FirstPersonHeldMesh->SetRelativeTransform(Item->FirstPersonHeldOffset);
		}
		else
		{
			FirstPersonHeldMesh->SetStaticMesh(nullptr);
		}

		FirstPersonHeldMesh->SetVisibility(bShow);
	}

	if (ThirdPersonHeldMesh)
	{
		if (bShow)
		{
			Item->ApplyHeldVisualsTo(ThirdPersonHeldMesh);
			ThirdPersonHeldMesh->SetRelativeTransform(Item->ThirdPersonHeldOffset);
		}
		else
		{
			ThirdPersonHeldMesh->SetStaticMesh(nullptr);
		}

		// Et surtout pas SetVisibility(bShow) : chez le joueur qui tient l'objet,
		// ce mesh doit rester invisible tout en projetant son ombre.
		UpdateLocalVisibility();
	}

	OnHeldItemChanged.Broadcast(HeldItem);
}

void UHeldItemComponent::UpdateLocalVisibility()
{
	if (!ThirdPersonHeldMesh)
	{
		return;
	}

	const AFpsPlayer* Player = GetOwningPlayer();

	// Le pawn qu'on controle est le seul dont on ne doit pas voir le corps -- donc
	// le seul dont l'objet en main se cache. Chez les trois autres joueurs le meme
	// pawn n'est pas controle localement, et l'outil s'y affiche normalement.
	//
	// Relu a CHAQUE passage plutot que pose une fois : chez un client le pawn
	// arrive par replication AVANT son controller, et IsLocallyControlled() y est
	// donc faux pendant les premieres images. Sans cette relecture, le joueur
	// verrait sa propre pioche flotter devant lui pour le reste de la partie.
	const bool bHideForLocalPlayer = Player != nullptr && Player->IsLocallyControlled();
	const bool bHasMesh = ThirdPersonHeldMesh->GetStaticMesh() != nullptr;

	ThirdPersonHeldMesh->SetVisibility(bHasMesh && !bHideForLocalPlayer);
}

void UHeldItemComponent::EnsureHeldMeshComponents()
{
	AFpsPlayer* Player = GetOwningPlayer();
	if (!Player)
	{
		return;
	}

	if (!FirstPersonHeldMesh)
	{
		FirstPersonHeldMesh = CreateHeldMeshComponent(
			TEXT("FirstPersonHeldMesh"), Player->GetFirstPersonArms(), FirstPersonSocket,
			/*bCastWorldShadow=*/false);
	}

	if (!ThirdPersonHeldMesh)
	{
		ThirdPersonHeldMesh = CreateHeldMeshComponent(
			TEXT("ThirdPersonHeldMesh"), Player->GetMesh(), ThirdPersonSocket,
			/*bCastWorldShadow=*/true);
	}
}

UStaticMeshComponent* UHeldItemComponent::CreateHeldMeshComponent(FName ComponentName, USkeletalMeshComponent* Parent,
	FName SocketName, bool bCastWorldShadow)
{
	AActor* Owner = GetOwner();
	if (!Owner || !Parent)
	{
		return nullptr;
	}

	// Un os introuvable n'est pas une erreur pour le moteur : il attache a la
	// RACINE du composant, en silence, et l'objet apparait plante au milieu du
	// torse. C'est ce log qui distingue « mal place » de « mal nomme », et la
	// difference vaut une heure de recherche dans le mauvais fichier.
	//
	// Le test du mesh n'est pas superflu : UCharacterAppearanceComponent
	// REMPLACE le mesh du corps a son BeginPlay, et l'ordre des BeginPlay entre
	// composants n'est garanti par rien. Sans lui, un avertissement partirait un
	// coup sur deux pour un os parfaitement valide -- soit exactement le faux
	// indice que ce log est cense eviter. L'attachement, lui, se moque du
	// moment : le nom est resolu a chaque lecture de la transform du socket.
	if (!SocketName.IsNone() && Parent->GetSkeletalMeshAsset() && !Parent->DoesSocketExist(SocketName) && !bLoggedMissingSocket)
	{
		UE_LOG(LogTemp, Warning, TEXT("UHeldItemComponent : le socket ou l'os '%s' n'existe pas sur %s. L'objet en main sera attache a la racine du mesh."),
			*SocketName.ToString(), *GetNameSafe(Parent->GetSkeletalMeshAsset()));
		bLoggedMissingSocket = true;
	}

	UStaticMeshComponent* Held = NewObject<UStaticMeshComponent>(Owner, ComponentName);
	if (!Held)
	{
		return nullptr;
	}

	Held->SetupAttachment(Parent, SocketName);

	// AVANT l'enregistrement, et c'est le piege le plus couteux de ce fichier :
	// un UStaticMeshComponent cree par NewObject nait en Mobility STATIC, alors
	// qu'un sous-objet declare dans un Blueprint herite du Movable de son
	// parent. Le mesh suit pourtant la main sans broncher, ce qui donne
	// l'illusion que tout va bien -- mais sous Virtual Shadow Maps une primitive
	// Static part dans le cache d'ombres statiques, qui n'est pas reinvalide
	// quand elle bouge. Symptome : l'objet est la, son ombre non. Et il n'y a
	// aucun message pour le dire.
	Held->SetMobility(EComponentMobility::Movable);

	// Un objet tenu en main n'est pas une surface de jeu. Sans ca, la hache
	// bloquerait le trace d'interaction -- qui part exactement d'entre les bras.
	Held->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	Held->RegisterComponent();

	// Apres l'enregistrement, et pas avant : les setters d'etat de rendu
	// marquent le proxy « sale », ce qui ne veut rien dire sur un composant qui
	// n'est pas encore dans le monde. Les poser des deux cotes de
	// RegisterComponent est le genre de subtilite qui fait qu'un drapeau prend
	// et l'autre non -- on les pose donc tous ici.

	// « Visible pour moi seul » se recopie du porteur : c'est ce qui tient les
	// bras, et un mesh d'arme n'a pas a savoir qu'il les habille.
	Held->SetOnlyOwnerSee(Parent->bOnlyOwnerSee);

	// bOwnerNoSee du porteur, en revanche, ne se recopie SURTOUT PAS, alors que
	// c'est par la que le Mesh du Character se cache lui-meme. Mesure sur ce
	// projet : cases « Cast Shadow » et « Hidden Shadow » cochees et actives sur
	// le composant, aucune ombre tant que bOwnerNoSee valait vrai, ombre correcte
	// des la seconde ou on cache le mesh par « Visible » a la place.
	//
	// Autrement dit bCastHiddenShadow n'honore, sur un UStaticMeshComponent, que
	// la dissimulation par la VISIBILITE -- pas celle par le proprietaire. Le
	// corps y echappe parce qu'il est un mesh SQUELETTIQUE, qui n'emprunte pas le
	// meme chemin de relevance dans le renderer. D'ou le partage : le porteur se
	// cache comme il veut, l'objet en main se cache par UpdateLocalVisibility().
	//
	// Ce n'est pas une perte de fonctionnalite : sur une machine donnee, le seul
	// pawn dont on est proprietaire est celui qu'on controle, donc les deux
	// mecanismes cachent exactement les memes objets aux memes joueurs.

	// L'OMBRE ne se recopie pas non plus -- les deux meshes n'ont pas du tout le
	// meme role. Celui du corps doit projeter son ombre MEME invisible pour son
	// proprietaire (bCastHiddenShadow), sinon on voit son propre corps au sol
	// sans l'outil qu'on tient. Celui des bras ne doit rien projeter du tout :
	// colle a l'objectif, il barrerait le decor d'une ombre geante.
	Held->SetCastShadow(bCastWorldShadow);
	Held->SetCastHiddenShadow(bCastWorldShadow);

	// Rien a montrer tant qu'aucun objet n'est monte.
	Held->SetVisibility(false);

	// Sans ca le composant existe, se rend et s'anime, mais reste INTROUVABLE
	// dans le Details panel du pawn en PIE : le panneau ne liste que les
	// sous-objets du template et les composants declares comme composants
	// d'instance. C'est du confort de debogage, mais c'est ce confort qui
	// manquait pour diagnostiquer cette ombre.
	Owner->AddInstanceComponent(Held);

	UE_LOG(LogTemp, Log, TEXT("UHeldItemComponent : '%s' monte sur '%s' (socket '%s' %s, ombre %s)."),
		*ComponentName.ToString(), *Parent->GetName(), *SocketName.ToString(),
		Parent->DoesSocketExist(SocketName) ? TEXT("trouve") : TEXT("INTROUVABLE"),
		bCastWorldShadow ? TEXT("activee") : TEXT("desactivee"));

	return Held;
}
