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


#include "CraftingWidget.h"

#include "Components/Button.h"
#include "Components/Image.h"
#include "Components/TextBlock.h"
#include "Components/UniformGridPanel.h"
#include "CraftingComponent.h"
#include "CraftingIngredientRowWidget.h"
#include "CraftingRecipeSlotWidget.h"
#include "FpsPlayer.h"
#include "GameFramework/PlayerController.h"
#include "InventoryComponent.h"
#include "ItemDataAsset.h"

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

	if (CraftButton)
	{
		CraftButton->OnClicked.AddDynamic(this, &UCraftingWidget::HandleCraftClicked);
	}

	BindToOwningPawn();
}

void UCraftingWidget::NativeDestruct()
{
	UnbindDelegates();

	Super::NativeDestruct();
}

UCraftingComponent* UCraftingWidget::ResolveCrafting() const
{
	const APlayerController* OwningController = GetOwningPlayer();
	AFpsPlayer* PlayerPawn = OwningController ? Cast<AFpsPlayer>(OwningController->GetPawn()) : nullptr;
	return PlayerPawn ? PlayerPawn->GetCraftingComponent() : nullptr;
}

void UCraftingWidget::UnbindDelegates()
{
	if (BoundInventory.IsValid())
	{
		BoundInventory->OnInventoryChanged.RemoveDynamic(this, &UCraftingWidget::HandleInventoryChanged);
	}
	BoundInventory.Reset();

	if (BoundCrafting.IsValid())
	{
		BoundCrafting->OnAvailableStationsChanged.RemoveDynamic(this, &UCraftingWidget::HandleStationsChanged);
	}
	BoundCrafting.Reset();
}

void UCraftingWidget::BindToOwningPawn()
{
	UCraftingComponent* NewCrafting = ResolveCrafting();
	if (BoundCrafting.Get() == NewCrafting)
	{
		return;
	}

	UnbindDelegates();

	BoundCrafting = NewCrafting;

	if (BoundCrafting.IsValid())
	{
		BoundCrafting->OnAvailableStationsChanged.AddDynamic(this, &UCraftingWidget::HandleStationsChanged);

		// L'inventaire est la vraie source des couleurs et de l'etat du bouton :
		// c'est lui qui change quand le joueur ramasse ou consomme.
		if (const AActor* Owner = BoundCrafting->GetOwner())
		{
			BoundInventory = Owner->FindComponentByClass<UInventoryComponent>();
			if (BoundInventory.IsValid())
			{
				BoundInventory->OnInventoryChanged.AddDynamic(this, &UCraftingWidget::HandleInventoryChanged);
			}
		}
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget : aucun UCraftingComponent trouve sur le pawn possede."));
	}

	// Les cases gardent l'ancienne recette et l'ancien parent en memoire : on
	// force leur recreation en vidant la grille.
	for (UCraftingRecipeSlotWidget* SlotWidget : RecipeSlots)
	{
		if (SlotWidget)
		{
			SlotWidget->RemoveFromParent();
		}
	}
	RecipeSlots.Reset();

	// Les lignes de cout aussi : elles affichent l'inventaire du pawn precedent.
	for (UCraftingIngredientRowWidget* Row : IngredientRows)
	{
		if (Row)
		{
			Row->RemoveFromParent();
		}
	}
	IngredientRows.Reset();

	SelectedRecipe = nullptr;

	Refresh();
}

void UCraftingWidget::HandleInventoryChanged()
{
	// Seules les couleurs et l'etat du bouton bougent : la liste des recettes,
	// elle, ne depend que des postes accessibles.
	RefreshStates();
}

void UCraftingWidget::HandleStationsChanged()
{
	Refresh();
}

void UCraftingWidget::SetStationFilter(ECraftingStation InFilter)
{
	if (StationFilter == InFilter)
	{
		return;
	}

	StationFilter = InFilter;
	Refresh();
}

void UCraftingWidget::Refresh()
{
	if (!RecipeGrid)
	{
		UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget::Refresh : le widget nomme 'RecipeGrid' manque dans le Blueprint."));
		return;
	}

	TArray<UCraftingRecipeDataAsset*> Recipes;
	if (BoundCrafting.IsValid())
	{
		BoundCrafting->GatherRecipes(Recipes, StationFilter);
	}

	RebuildGrid(Recipes);

	if (EmptyGridMessage)
	{
		EmptyGridMessage->SetVisibility(Recipes.Num() > 0 ? ESlateVisibility::Collapsed : ESlateVisibility::Visible);
	}

	// La recette choisie a pu disparaitre de la liste : on s'est eloigne de
	// l'etabli, ou le filtre a change.
	if (SelectedRecipe && !Recipes.Contains(SelectedRecipe))
	{
		SelectedRecipe = nullptr;
	}

	RefreshStates();
}

void UCraftingWidget::RebuildGrid(const TArray<UCraftingRecipeDataAsset*>& NewRecipes)
{
	// La liste ne change qu'en s'approchant d'un poste ou en changeant de
	// filtre. Recreer les cases a chaque ramassage serait du gaspillage pur,
	// d'ou cette comparaison prealable.
	bool bSameList = RecipeSlots.Num() == NewRecipes.Num();
	if (bSameList)
	{
		for (int32 Index = 0; Index < RecipeSlots.Num(); ++Index)
		{
			if (!RecipeSlots[Index] || RecipeSlots[Index]->GetRecipe() != NewRecipes[Index])
			{
				bSameList = false;
				break;
			}
		}
	}

	if (bSameList)
	{
		return;
	}

	for (UCraftingRecipeSlotWidget* SlotWidget : RecipeSlots)
	{
		if (SlotWidget)
		{
			SlotWidget->RemoveFromParent();
		}
	}
	RecipeSlots.Reset();

	if (NewRecipes.Num() == 0)
	{
		return;
	}

	if (!RecipeSlotClass)
	{
		UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget : RecipeSlotClass n'est pas assignee, la grille restera vide."));
		return;
	}

	const int32 Columns = FMath::Max(1, ColumnCount);
	RecipeSlots.Reserve(NewRecipes.Num());

	for (int32 Index = 0; Index < NewRecipes.Num(); ++Index)
	{
		UCraftingRecipeSlotWidget* NewSlot = CreateWidget<UCraftingRecipeSlotWidget>(this, RecipeSlotClass);
		if (!NewSlot)
		{
			// Arrive typiquement quand le Widget Blueprint de case ne compile
			// plus, apres un changement de BindWidget cote C++ par exemple.
			UE_LOG(LogTemp, Error, TEXT("UCraftingWidget : impossible de creer une case a partir de %s. Recompile ce Widget Blueprint."),
				*GetNameSafe(RecipeSlotClass));
			break;
		}

		NewSlot->InitSlot(NewRecipes[Index], this);
		RecipeGrid->AddChildToUniformGrid(NewSlot, Index / Columns, Index % Columns);
		RecipeSlots.Add(NewSlot);
	}
}

void UCraftingWidget::RefreshStates()
{
	UCraftingComponent* Crafting = BoundCrafting.Get();

	for (UCraftingRecipeSlotWidget* SlotWidget : RecipeSlots)
	{
		if (!SlotWidget)
		{
			continue;
		}

		ECraftFailureReason Reason = ECraftFailureReason::None;
		const bool bCraftable = Crafting && Crafting->CanCraft(SlotWidget->GetRecipe(), Reason);

		SlotWidget->SetCraftable(bCraftable);
		SlotWidget->SetSelected(SlotWidget->GetRecipe() == SelectedRecipe);
	}

	RefreshDetails();
}

void UCraftingWidget::RefreshDetails()
{
	UCraftingComponent* Crafting = BoundCrafting.Get();

	if (DetailsPanel)
	{
		DetailsPanel->SetVisibility(SelectedRecipe ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
	}

	if (!SelectedRecipe)
	{
		if (CraftButton)
		{
			CraftButton->SetIsEnabled(false);
		}

		// Le panneau est masque, mais on vide quand meme : le contenu de
		// l'ancienne recette reapparaitrait un instant a la selection suivante.
		RefreshIngredients();
		return;
	}

	if (ResultIcon)
	{
		if (UTexture2D* Icon = SelectedRecipe->GetIcon())
		{
			ResultIcon->SetBrushFromTexture(Icon);
			ResultIcon->SetVisibility(ESlateVisibility::HitTestInvisible);
		}
		else
		{
			ResultIcon->SetVisibility(ESlateVisibility::Hidden);
		}
	}

	if (ResultNameText)
	{
		ResultNameText->SetText(SelectedRecipe->GetDisplayName());
	}

	if (ResultQuantityText)
	{
		const bool bMultiple = SelectedRecipe->ResultQuantity > 1;
		ResultQuantityText->SetVisibility(bMultiple ? ESlateVisibility::HitTestInvisible : ESlateVisibility::Collapsed);
		if (bMultiple)
		{
			ResultQuantityText->SetText(FText::AsNumber(SelectedRecipe->ResultQuantity));
		}
	}

	if (ResultDescriptionText)
	{
		ResultDescriptionText->SetText(SelectedRecipe->ResultItem ? SelectedRecipe->ResultItem->Description : FText::GetEmpty());
	}

	RefreshIngredients();

	if (CraftButton)
	{
		ECraftFailureReason Reason = ECraftFailureReason::None;
		CraftButton->SetIsEnabled(Crafting && Crafting->CanCraft(SelectedRecipe, Reason));
	}
}

void UCraftingWidget::RefreshIngredients()
{
	if (!IngredientList)
	{
		return;
	}

	TArray<FCraftIngredientStatus> Statuses;
	if (SelectedRecipe && BoundCrafting.IsValid())
	{
		BoundCrafting->GatherIngredientStatus(SelectedRecipe, Statuses);
	}

	// Trop de lignes : on retire le surplus par la fin.
	while (IngredientRows.Num() > Statuses.Num())
	{
		const int32 Last = IngredientRows.Num() - 1;
		if (IngredientRows[Last])
		{
			IngredientRows[Last]->RemoveFromParent();
		}
		IngredientRows.RemoveAt(Last);
	}

	if (IngredientRows.Num() < Statuses.Num() && !IngredientRowClass)
	{
		UE_LOG(LogTemp, Warning, TEXT("UCraftingWidget : IngredientRowClass n'est pas assignee, la liste de cout restera vide."));
	}

	// Pas assez : on en cree.
	while (IngredientRows.Num() < Statuses.Num() && IngredientRowClass)
	{
		UCraftingIngredientRowWidget* NewRow = CreateWidget<UCraftingIngredientRowWidget>(this, IngredientRowClass);
		if (!NewRow)
		{
			UE_LOG(LogTemp, Error, TEXT("UCraftingWidget : impossible de creer une ligne a partir de %s. Recompile ce Widget Blueprint."),
				*GetNameSafe(IngredientRowClass));
			break;
		}

		IngredientList->AddChild(NewRow);
		IngredientRows.Add(NewRow);
	}

	for (int32 Index = 0; Index < IngredientRows.Num(); ++Index)
	{
		if (IngredientRows[Index] && Statuses.IsValidIndex(Index))
		{
			IngredientRows[Index]->SetIngredient(Statuses[Index]);
		}
	}
}

void UCraftingWidget::NotifyRecipeClicked(UCraftingRecipeDataAsset* InRecipe)
{
	if (SelectedRecipe == InRecipe)
	{
		return;
	}

	SelectedRecipe = InRecipe;
	RefreshStates();
}

void UCraftingWidget::HandleCraftClicked()
{
	if (!SelectedRecipe || !BoundCrafting.IsValid())
	{
		return;
	}

	// Pas de re-verification ici : Craft() refait CanCraft de toute facon, et
	// dupliquer la condition finirait par la faire diverger.
	BoundCrafting->Craft(SelectedRecipe);

	// L'inventaire diffuse son changement, donc RefreshStates est deja passe.
	// Rien a rappeler.
}
