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


#include "PickupItem.h"

#include "Components/BoxComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "InventoryComponent.h"
#include "ItemDataAsset.h"
#include "Net/UnrealNetwork.h"

#define LOCTEXT_NAMESPACE "PickupItem"

APickupItem::APickupItem()
{
	PrimaryActorTick.bCanEverTick = false;

	// Acteur du monde : le serveur fait autorite. Un pickup ramasse doit
	// disparaitre chez les quatre joueurs, pas seulement chez celui qui l'a pris.
	bReplicates = true;

	// Un pickup ne bouge plus une fois pose. Couper la replication de mouvement
	// evite d'envoyer une transform a chaque joueur qui entre dans sa zone de
	// pertinence, alors que celle du spawn suffit.
	SetReplicateMovement(false);

	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	SetRootComponent(Mesh);
	// Purement decoratif : c'est la boite qui porte la collision.
	Mesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);

	// Le trace d'interaction passe par le canal Visibility. On ne bloque QUE
	// ce canal : le pickup reste traversable par le joueur et les projectiles.
	InteractionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("InteractionBox"));
	InteractionBox->SetupAttachment(Mesh);
	// Valeur de repli seulement : FitInteractionBox l'ecrase des qu'un mesh est
	// pose. Elle ne sert qu'aux pickups sans ItemData, et au cas ou
	// bAutoFitInteractionBox serait decoche sans qu'on regle la boite.
	InteractionBox->SetBoxExtent(FVector(25.f));
	InteractionBox->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
	InteractionBox->SetCollisionResponseToAllChannels(ECR_Ignore);
	InteractionBox->SetCollisionResponseToChannel(ECC_Visibility, ECR_Block);
}

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

	DOREPLIFETIME(APickupItem, ItemData);
	DOREPLIFETIME(APickupItem, Quantity);
	DOREPLIFETIME(APickupItem, RemainingUses);
}

void APickupItem::InitPickup(UItemDataAsset* InItemData, int32 InQuantity, int32 InRemainingUses)
{
	ItemData = InItemData;
	Quantity = FMath::Max(1, InQuantity);
	RemainingUses = InRemainingUses;
}

void APickupItem::ApplyItemVisuals()
{
	if (bUseMeshFromItemData && ItemData && ItemData->WorldMesh)
	{
		ItemData->ApplyWorldVisualsTo(Mesh);
	}

	// Hors du if, et apres lui : la boite doit suivre le mesh REELLEMENT pose,
	// que celui-ci vienne de l'ItemData ou qu'il ait ete impose a la main.
	if (bAutoFitInteractionBox)
	{
		FitInteractionBox();
	}
}

void APickupItem::FitInteractionBox()
{
	const UStaticMesh* CurrentMesh = Mesh->GetStaticMesh();
	if (!CurrentMesh)
	{
		// Pickup non configure, ou client dont l'ItemData n'est pas encore arrive :
		// on garde la boite du Blueprint plutot que de l'ecraser par un volume nul.
		// OnRep_ItemData repassera ici quand la donnee sera la.
		return;
	}

	// La bounding box de l'ASSET, jamais Mesh->Bounds : ces dernieres sont en
	// espace monde et deja multipliees par l'echelle de l'acteur. Or SetBoxExtent
	// recoit une extension LOCALE que le moteur remultiplie a son tour -- une
	// instance posee a l'echelle 2 aurait une boite quatre fois trop grande.
	const FBox LocalBounds = CurrentMesh->GetBoundingBox();

	// Le pivot d'un mesh d'asset pack tombe rarement au centre de sa geometrie :
	// il est presque toujours au sol, parfois franchement decale. Une boite
	// laissee a l'origine deborderait d'un cote et laisserait l'autre invisable.
	InteractionBox->SetRelativeLocation(LocalBounds.GetCenter());

	// ComponentMax et non un Max sur la norme : le plancher s'applique axe par
	// axe, sinon relever l'epaisseur d'une branche rallongerait aussi la boite
	// dans le sens ou elle etait deja bien assez grande.
	const FVector Fitted = LocalBounds.GetExtent() + FVector(InteractionBoxPadding);

	// bUpdateOverlaps a false : cette boite ne bloque que le canal Visibility et
	// ne genere aucun evenement d'overlap. Le defaut (true) declencherait une
	// requete physique par pickup a chaque spawn, pour rien.
	InteractionBox->SetBoxExtent(
		Fitted.ComponentMax(FVector(MinInteractionExtent)),
		/*bUpdateOverlaps=*/ false);
}

void APickupItem::OnRep_ItemData()
{
	ApplyItemVisuals();
}

void APickupItem::OnConstruction(const FTransform& Transform)
{
	Super::OnConstruction(Transform);

	// Confort d'edition : le mesh suit l'objet choisi, tu vois immediatement
	// dans le viewport ce que tu es en train de poser. Vaut aussi a l'execution
	// pour les objets jetes, puisque OnConstruction s'execute a FinishSpawning.
	//
	// Chez un client, ItemData est encore nul a cet instant : c'est OnRep_ItemData
	// qui reposera le visuel quand la propriete arrivera.
	ApplyItemVisuals();
}

FText APickupItem::GetInteractionPrompt_Implementation(AActor* Interactor) const
{
	// Interactor ne sert pas ici : ramasser dit la meme chose a tout le monde.
	if (!ItemData)
	{
		return LOCTEXT("PickupUnconfigured", "Unconfigured item");
	}

	if (Quantity > 1)
	{
		return FText::Format(LOCTEXT("PickupPromptStack", "Pick up {0} x{1}"),
			ItemData->DisplayName, FText::AsNumber(Quantity));
	}

	return FText::Format(LOCTEXT("PickupPrompt", "Pick up {0}"), ItemData->DisplayName);
}

bool APickupItem::CanInteract_Implementation(AActor* Interactor) const
{
	// Un pickup sans donnee est une erreur de configuration : on le rend
	// inerte plutot que de laisser le joueur ramasser du vide.
	return ItemData != nullptr;
}

void APickupItem::Interact_Implementation(AActor* Interactor)
{
	// Filet de securite : UInteractionComponent ne nous appelle deja que cote
	// autorite, mais un Blueprint ou un futur appelant pourrait l'oublier, et
	// un ramassage joue en local donnerait un objet qui reapparait a la
	// prochaine replication -- le pire des symptomes, parce qu'il est
	// intermittent.
	if (!HasAuthority())
	{
		return;
	}

	UInventoryComponent* Inventory = Interactor ? Interactor->FindComponentByClass<UInventoryComponent>() : nullptr;
	if (!Inventory)
	{
		UE_LOG(LogTemp, Warning, TEXT("APickupItem : %s n'a pas d'UInventoryComponent."), *GetNameSafe(Interactor));
		return;
	}

	const int32 Leftover = Inventory->AddItem(ItemData, Quantity, RemainingUses);

	// Inventaire plein : on ne detruit surtout pas le tas, on retire seulement
	// ce qui est rentre. Le joueur peut revenir chercher le reste.
	if (Leftover >= Quantity)
	{
		return;
	}

	// Les anciens AddOnScreenDebugMessage ont disparu : en reseau ils
	// s'affichaient sur l'ecran de l'HOTE, puisque c'est lui qui execute ce
	// code, y compris quand c'est un client qui ramasse. L'UI d'inventaire
	// existe desormais et dit la meme chose au bon joueur.

	if (Leftover > 0)
	{
		Quantity = Leftover;
		return;
	}

	Destroy();
}

void APickupItem::OnBeginFocus_Implementation()
{
	if (bHighlightOnFocus)
	{
		Mesh->SetRenderCustomDepth(true);
	}
}

void APickupItem::OnEndFocus_Implementation()
{
	if (bHighlightOnFocus)
	{
		Mesh->SetRenderCustomDepth(false);
	}
}

#undef LOCTEXT_NAMESPACE
