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


#include "InventorySlotWidget.h"

#include "Components/Image.h"
#include "Components/ProgressBar.h"
#include "Components/TextBlock.h"
#include "Components/Widget.h"
#include "InventoryDragDropOperation.h"
#include "ItemDataAsset.h"

void UInventorySlotWidget::NativeConstruct()
{
	Super::NativeConstruct();

	// Force en C++ plutot que de dependre d'un reglage du Blueprint : une case
	// non testable a la souris ne recevrait jamais NativeOnMouseButtonDown, et
	// le glisser-deposer serait mysterieusement inerte.
	// NativeConstruct s'execute APRES ConfigureAsDragVisual() pour un fantome
	// (le widget n'est construit qu'une fois ajoute a la couche de drag),
	// d'ou le test : sans lui, on remettrait le fantome en Visible et il
	// intercepterait le depot destine a la case du dessous.
	SetVisibility(bIsDragVisual ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Visible);
}

void UInventorySlotWidget::SetSelected(bool bSelected)
{
	if (SelectionBorder)
	{
		SelectionBorder->SetVisibility(bSelected ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
	}
}

void UInventorySlotWidget::ConfigureAsDragVisual()
{
	bIsDragVisual = true;

	// Le cadre de selection appartient a la barre, pas a l'objet.
	if (SelectionBorder)
	{
		SelectionBorder->SetVisibility(ESlateVisibility::Collapsed);
	}

	// Le fond de case appartient a la grille, pas a l'objet. Le trainer sous
	// le curseur donne l'impression de deplacer la case elle-meme.
	if (SlotBackground)
	{
		SlotBackground->SetVisibility(ESlateVisibility::Collapsed);
	}

	SetVisibility(ESlateVisibility::HitTestInvisible);
}

void UInventorySlotWidget::InitSlot(UInventoryComponent* InInventory, int32 InSlotIndex)
{
	OwningInventory = InInventory;
	SlotIndex = InSlotIndex;
}

void UInventorySlotWidget::SetQuickTransferTarget(UInventoryComponent* InTarget)
{
	QuickTransferTarget = InTarget;
}

void UInventorySlotWidget::NativeOnMouseEnter(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	Super::NativeOnMouseEnter(InGeometry, InMouseEvent);

	OnHoverChanged.Broadcast(SlotIndex, true);
}

void UInventorySlotWidget::NativeOnMouseLeave(const FPointerEvent& InMouseEvent)
{
	Super::NativeOnMouseLeave(InMouseEvent);

	OnHoverChanged.Broadcast(SlotIndex, false);
}

void UInventorySlotWidget::SetSlot(const FInventorySlot& InSlot)
{
	CachedSlot = InSlot;

	// Filet de securite : toute mise a jour de contenu remet la case en pleine
	// opacite, meme si un glisser s'est termine par un chemin imprevu.
	if (!bIsDragVisual)
	{
		SetRenderOpacity(1.f);
	}

	const bool bEmpty = InSlot.IsEmpty();

	if (ItemIcon)
	{
		// Hidden et non Collapsed : on veut que la case garde sa taille dans
		// la grille meme vide, sinon la mise en page sauterait a chaque
		// ramassage.
		if (bEmpty || !InSlot.Item->Icon)
		{
			ItemIcon->SetVisibility(ESlateVisibility::Hidden);
		}
		else
		{
			ItemIcon->SetBrushFromTexture(InSlot.Item->Icon, /*bMatchSize=*/false);
			ItemIcon->SetVisibility(ESlateVisibility::HitTestInvisible);
		}
	}

	if (QuantityText)
	{
		// On n'affiche pas "x1" : ca alourdit la grille pour rien.
		if (bEmpty || InSlot.Quantity <= 1)
		{
			QuantityText->SetVisibility(ESlateVisibility::Hidden);
		}
		else
		{
			QuantityText->SetText(FText::AsNumber(InSlot.Quantity));
			QuantityText->SetVisibility(ESlateVisibility::HitTestInvisible);
		}
	}

	if (UsesBar)
	{
		// Collapsed et non Hidden : une barre d'usure sur un objet qui n'en a
		// pas ne doit meme pas reserver sa place au bas de la case.
		const int32 MaxUses = bEmpty ? 1 : FMath::Max(1, InSlot.Item->MaxUses);
		if (MaxUses <= 1)
		{
			UsesBar->SetVisibility(ESlateVisibility::Collapsed);
		}
		else
		{
			UsesBar->SetPercent(FMath::Clamp(static_cast<float>(InSlot.RemainingUses) / static_cast<float>(MaxUses), 0.f, 1.f));
			UsesBar->SetVisibility(ESlateVisibility::HitTestInvisible);
		}
	}
}

FReply UInventorySlotWidget::NativeOnMouseButtonDown(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent)
{
	// Clic droit : transfert rapide vers l'inventaire d'en face, quand il y en
	// a un. Traite avant le glisser pour que le bouton droit ne demarre jamais
	// de DetectDrag -- ce qui laisserait une operation orpheline en cours.
	if (!CachedSlot.IsEmpty() && InMouseEvent.GetEffectingButton() == EKeys::RightMouseButton)
	{
		if (QuickTransferTarget.IsValid() && OwningInventory.IsValid() && SlotIndex != INDEX_NONE)
		{
			UInventoryComponent::QuickTransferSlot(OwningInventory.Get(), SlotIndex, QuickTransferTarget.Get());
			return FReply::Handled();
		}
	}

	if (CachedSlot.IsEmpty() || !InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
	{
		return Super::NativeOnMouseButtonDown(InGeometry, InMouseEvent);
	}

	// On ne demarre pas le glisser tout de suite : DetectDrag attend que la
	// souris bouge reellement. Sans ca, un simple clic serait interprete comme
	// un debut de glisser et on ne pourrait jamais faire autre chose.
	return FReply::Handled().DetectDrag(TakeWidget(), EKeys::LeftMouseButton);
}

void UInventorySlotWidget::NativeOnDragDetected(const FGeometry& InGeometry, const FPointerEvent& InMouseEvent, UDragDropOperation*& OutOperation)
{
	Super::NativeOnDragDetected(InGeometry, InMouseEvent, OutOperation);

	if (CachedSlot.IsEmpty() || !OwningInventory.IsValid() || SlotIndex == INDEX_NONE)
	{
		return;
	}

	// Modificateurs au moment de la saisie : Maj pour la moitie, Ctrl pour un
	// seul. Conventions etablies du genre, le joueur les connait deja.
	int32 DraggedQuantity = CachedSlot.Quantity;
	if (InMouseEvent.IsControlDown())
	{
		DraggedQuantity = 1;
	}
	else if (InMouseEvent.IsShiftDown())
	{
		// Moitie arrondie au superieur : sur une pile de 7 on emporte 4.
		// C'est la convention de Minecraft et consorts.
		DraggedQuantity = (CachedSlot.Quantity + 1) / 2;
	}

	UInventoryDragDropOperation* Operation = NewObject<UInventoryDragDropOperation>();
	Operation->SourceSlotIndex = SlotIndex;
	Operation->Quantity = DraggedQuantity;
	Operation->SourceInventory = OwningInventory;
	Operation->Pivot = EDragPivot::CenterCenter;

	// Le fantome sous le curseur est une copie de cette meme case : aucun
	// widget supplementaire a maintenir, et l'apparence reste coherente.
	// Il affiche la quantite EMPORTEE, pas la pile complete.
	if (UInventorySlotWidget* Visual = CreateWidget<UInventorySlotWidget>(GetOwningPlayer(), GetClass()))
	{
		FInventorySlot VisualSlot = CachedSlot;
		VisualSlot.Quantity = DraggedQuantity;

		Visual->SetSlot(VisualSlot);
		Visual->ConfigureAsDragVisual();
		Visual->SetRenderOpacity(DragVisualOpacity);
		Operation->DefaultDragVisual = Visual;
	}

	// On n'estompe la case source que si la pile part en entier. Lors d'une
	// division une partie reste sur place, et la faire palir serait mentir
	// sur ce qu'elle contient encore.
	if (DraggedQuantity >= CachedSlot.Quantity)
	{
		SetRenderOpacity(DraggedSlotOpacity);
	}

	// Les deux issues possibles remettent l'opacite : depot reussi, ou
	// abandon hors de la grille.
	Operation->OnDrop.AddDynamic(this, &UInventorySlotWidget::HandleDragFinished);
	Operation->OnDragCancelled.AddDynamic(this, &UInventorySlotWidget::HandleDragFinished);

	OutOperation = Operation;
}

void UInventorySlotWidget::HandleDragFinished(UDragDropOperation* Operation)
{
	SetRenderOpacity(1.f);
}

bool UInventorySlotWidget::NativeOnDrop(const FGeometry& InGeometry, const FDragDropEvent& InDragDropEvent, UDragDropOperation* InOperation)
{
	const UInventoryDragDropOperation* Payload = Cast<UInventoryDragDropOperation>(InOperation);
	if (!Payload || SlotIndex == INDEX_NONE)
	{
		return Super::NativeOnDrop(InGeometry, InDragDropEvent, InOperation);
	}

	// TransferSlot diffuse OnInventoryChanged des DEUX cotes, donc les grilles
	// se rafraichissent seules. Elle gere indifferemment le meme inventaire et
	// deux inventaires distincts : glisser vers un coffre, vers la barre rapide
	// ou vers la case d'a cote emprunte exactement le meme chemin.
	return UInventoryComponent::TransferSlot(
		Payload->SourceInventory.Get(), Payload->SourceSlotIndex,
		OwningInventory.Get(), SlotIndex,
		Payload->Quantity);
}
