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


#include "SettingsControlsWidget.h"

#include "EnhancedInputSubsystems.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/PlayerController.h"
#include "SettingKeyRowWidget.h"
#include "SettingsMenuWidget.h"
#include "SurvivalGameInstance.h"
#include "UserSettings/EnhancedInputUserSettings.h"

#define LOCTEXT_NAMESPACE "Reglages"

USettingsControlsWidget::USettingsControlsWidget(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// Dans le CONSTRUCTEUR et non dans NativeConstruct : ce dernier tourne a
	// chaque ouverture et ecraserait le texte que tu aurais regle dans les Class
	// Defaults de WBP_SettingsControls, rendant la UPROPERTY inutile.
	//
	// Trois informations, pas deux : la touche, qui la possede aujourd'hui, et a
	// qui on la donne. Sans la troisieme, un joueur qui a ouvert le voile puis
	// hesite ne sait plus quelle ligne il etait en train de modifier.
	ConflictMessage = LOCTEXT("ConflitTouche",
		"« {0} » est déjà utilisée par « {1} ».\nL'assigner à « {2} » ?\n\n« {1} » n'aura plus de touche.");
}

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

	// Les identifiants doivent correspondre EXACTEMENT au champ "Name" des
	// Player Mappable Key Settings de IMC_Default. Une faute de frappe donne une
	// ligne perpetuellement "Non assignée" -- c'est le seul point de contact
	// entre le C++ et l'editeur, et il n'est verifiable qu'au runtime.
	SetupRow(MoveForwardRow, TEXT("MoveForward"), LOCTEXT("Avancer", "Avancer"));
	SetupRow(MoveBackwardRow, TEXT("MoveBackward"), LOCTEXT("Reculer", "Reculer"));
	SetupRow(MoveLeftRow, TEXT("MoveLeft"), LOCTEXT("AllerGauche", "Aller à gauche"));
	SetupRow(MoveRightRow, TEXT("MoveRight"), LOCTEXT("AllerDroite", "Aller à droite"));
	SetupRow(JumpRow, TEXT("Jump"), LOCTEXT("Sauter", "Sauter"));
	SetupRow(SprintRow, TEXT("Sprint"), LOCTEXT("Sprinter", "Sprinter"));
	SetupRow(CrouchRow, TEXT("Crouch"), LOCTEXT("SAccroupir", "S'accroupir"));
	SetupRow(InteractRow, TEXT("Interact"), LOCTEXT("Interagir", "Interagir"));
	SetupRow(UseItemRow, TEXT("UseItem"), LOCTEXT("UtiliserObjet", "Utiliser l'objet"));
	SetupRow(ToggleInventoryRow, TEXT("ToggleInventory"), LOCTEXT("Inventaire", "Inventaire"));
	SetupRow(ToggleCraftingRow, TEXT("ToggleCrafting"), LOCTEXT("Fabrication", "Fabrication"));
	SetupRow(DropItemRow, TEXT("DropItem"), LOCTEXT("Jeter", "Jeter"));

	RefreshFromSettings();
}

void USettingsControlsWidget::SetupRow(USettingKeyRowWidget* Row, FName MappingName, const FText& Label)
{
	if (!Row)
	{
		return;
	}

	MappingLabels.Add(MappingName, Label);

	// Touche invalide au depart : RefreshFromSettings ira chercher la vraie juste
	// apres. Poser une valeur en dur ici mentirait le temps d'une frame.
	Row->Setup(MappingName, Label, FKey());

	// Un seul handler pour les onze lignes : chacune renvoie son propre nom de
	// mapping, il n'y a rien a distinguer ici.
	Row->OnRebindRequested.AddDynamic(this, &USettingsControlsWidget::HandleRebindRequested);
}

UEnhancedInputUserSettings* USettingsControlsWidget::GetInputSettings() const
{
	const ULocalPlayer* LocalPlayer = GetOwningLocalPlayer();
	if (!LocalPlayer)
	{
		return nullptr;
	}

	UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>();
	return Subsystem ? Subsystem->GetUserSettings() : nullptr;
}

TArray<USettingKeyRowWidget*> USettingsControlsWidget::GetAllRows() const
{
	// L'ordre est celui de l'affichage : il sert aussi a la reinitialisation.
	TArray<USettingKeyRowWidget*> Rows;
	Rows.Reserve(12);

	for (USettingKeyRowWidget* Row : { MoveForwardRow.Get(), MoveBackwardRow.Get(), MoveLeftRow.Get(),
		MoveRightRow.Get(), JumpRow.Get(), SprintRow.Get(), CrouchRow.Get(), InteractRow.Get(),
		UseItemRow.Get(), ToggleInventoryRow.Get(), ToggleCraftingRow.Get(), DropItemRow.Get() })
	{
		if (Row)
		{
			Rows.Add(Row);
		}
	}

	return Rows;
}

USettingKeyRowWidget* USettingsControlsWidget::FindRow(FName MappingName) const
{
	for (USettingKeyRowWidget* Row : GetAllRows())
	{
		if (Row->GetMappingName() == MappingName)
		{
			return Row;
		}
	}

	return nullptr;
}

FText USettingsControlsWidget::GetMappingDisplayName(FName MappingName) const
{
	if (const FText* Label = MappingLabels.Find(MappingName))
	{
		return *Label;
	}

	// Repli sur l'identifiant technique : moins joli, mais toujours plus utile
	// qu'un libelle vide dans un message de conflit.
	return FText::FromName(MappingName);
}

void USettingsControlsWidget::RefreshFromSettings()
{
	// On declare les contextes JUSTE AVANT de lire : c'est le seul moment ou
	// l'on est certain que le joueur local a son PlayerController, donc que le
	// sous-systeme a cree ses UserSettings. Tout enregistrement plus precoce
	// tombe dans le vide -- ULocalPlayer previent ses abonnes avant ses
	// sous-systemes. L'appel est idempotent, le repeter ne coute rien.
	if (USurvivalGameInstance* GameInstance = GetGameInstance<USurvivalGameInstance>())
	{
		GameInstance->RegisterMappableContexts();
	}

	const UEnhancedInputUserSettings* Settings = GetInputSettings();
	const UEnhancedPlayerMappableKeyProfile* Profile = Settings ? Settings->GetActiveKeyProfile() : nullptr;

	if (!Profile)
	{
		UE_LOG(LogTemp, Warning, TEXT("USettingsControlsWidget : aucun profil de touches. Verifie 'Enable User Settings' dans Project Settings > Enhanced Input."));
		return;
	}

	// On parcourt les lignes DECLAREES et on va chercher leur mapping, plutot que
	// l'inverse : le profil contient aussi des mappings qu'on n'expose pas, et
	// l'ordre d'affichage est le notre, pas le sien.
	for (const TPair<FName, FKeyMappingRow>& Pair : Profile->GetPlayerMappingRows())
	{
		USettingKeyRowWidget* Row = FindRow(Pair.Key);
		if (!Row)
		{
			continue;
		}

		FKey CurrentKey;
		for (const FPlayerKeyMapping& Mapping : Pair.Value.Mappings)
		{
			CurrentKey = Mapping.GetCurrentKey();
			break;
		}

		Row->SetKey(CurrentKey);
	}
}

void USettingsControlsWidget::ResetToDefaults()
{
	UEnhancedInputUserSettings* Settings = GetInputSettings();
	if (!Settings)
	{
		return;
	}

	for (const USettingKeyRowWidget* Row : GetAllRows())
	{
		FMapPlayerKeyArgs Args;
		Args.MappingName = Row->GetMappingName();

		FGameplayTagContainer FailureReason;
		Settings->ResetAllPlayerKeysInRow(Args, FailureReason);
	}

	Settings->ApplySettings();

	// Reinitialiser rend les touches de l'IMC, donc du QWERTY : sur un clavier
	// AZERTY le joueur se retrouverait avec un WASD qu'il ne peut pas atteindre.
	// On repose donc la disposition juste apres. "Par defaut" veut dire "par defaut
	// POUR TON CLAVIER", pas "par defaut pour le clavier du developpeur".
	if (USurvivalGameInstance* GameInstance = GetGameInstance<USurvivalGameInstance>())
	{
		GameInstance->ApplyKeyboardLayoutToDefaultKeys();
	}

	RefreshFromSettings();
}

void USettingsControlsWidget::HandleRebindRequested(FName MappingName)
{
	if (!OwningMenu.IsValid())
	{
		return;
	}

	PendingMappingName = MappingName;
	PendingKey = FKey();
	ConflictingMappingName = NAME_None;

	FOnSettingsKeyCaptureResult Callback;
	Callback.BindDynamic(this, &USettingsControlsWidget::HandleKeyCaptured);

	OwningMenu->RequestKeyCapture(GetMappingDisplayName(MappingName), Callback);
}

void USettingsControlsWidget::HandleKeyCaptured(FKey CapturedKey)
{
	UEnhancedInputUserSettings* Settings = GetInputSettings();

	// Touche invalide = le joueur a annule avec Echap.
	if (!CapturedKey.IsValid() || PendingMappingName.IsNone() || !Settings)
	{
		PendingMappingName = NAME_None;
		return;
	}

	PendingKey = CapturedKey;

	const UEnhancedPlayerMappableKeyProfile* Profile = Settings->GetActiveKeyProfile();
	if (!Profile)
	{
		return;
	}

	// Le moteur sait deja repondre a "qui utilise cette touche ?" : pas de
	// comparaison a la main sur toutes les lignes.
	TArray<FName> Owners;
	Profile->GetMappingNamesForKey(CapturedKey, Owners);

	ConflictingMappingName = NAME_None;
	for (const FName& Owner : Owners)
	{
		// Se voler sa propre touche n'a rien d'un conflit.
		if (Owner != PendingMappingName && FindRow(Owner))
		{
			ConflictingMappingName = Owner;
			break;
		}
	}

	if (ConflictingMappingName.IsNone())
	{
		// Rien a arbitrer : on applique et on referme le voile.
		ApplyPendingRebind();

		if (OwningMenu.IsValid())
		{
			OwningMenu->CloseKeyCapture();
		}

		return;
	}

	if (!OwningMenu.IsValid())
	{
		return;
	}

	FOnSettingsConfirmationResult Callback;
	Callback.BindDynamic(this, &USettingsControlsWidget::HandleConflictAnswered);

	const FText Message = FText::Format(ConflictMessage,
		PendingKey.GetDisplayName(),
		GetMappingDisplayName(ConflictingMappingName),
		GetMappingDisplayName(PendingMappingName));

	// Le voile reste ouvert et change simplement d'etat : pour le joueur c'est
	// le meme geste qui continue, pas un second ecran qui apparait.
	OwningMenu->ShowKeyCaptureConflict(Message, Callback);
}

void USettingsControlsWidget::HandleConflictAnswered(bool bConfirmed)
{
	// Le voile s'est deja referme de lui-meme en repondant : il n'y a plus qu'a
	// appliquer, ou a oublier ce qu'on avait mis de cote.
	if (!bConfirmed)
	{
		PendingMappingName = NAME_None;
		ConflictingMappingName = NAME_None;
		PendingKey = FKey();
		return;
	}

	ApplyPendingRebind();
}

void USettingsControlsWidget::ApplyPendingRebind()
{
	UEnhancedInputUserSettings* Settings = GetInputSettings();
	if (!Settings || PendingMappingName.IsNone() || !PendingKey.IsValid())
	{
		return;
	}

	FGameplayTagContainer FailureReason;

	// L'ancien proprietaire perd sa touche AVANT qu'on assigne la notre : deux
	// actions sur la meme touche laisseraient un doublon actif, et le joueur
	// declencherait les deux d'un seul appui.
	//
	// On lui assigne une touche VIDE au lieu d'appeler UnMapPlayerKey : ce
	// dernier fait un ResetToDefault(), donc il rendrait a l'action sa touche
	// d'origine -- exactement celle qu'on est en train de lui prendre. Le nom
	// trompe, il annule la surcharge du joueur, il ne libere pas la touche.
	if (!ConflictingMappingName.IsNone())
	{
		FMapPlayerKeyArgs ClearArgs;
		ClearArgs.MappingName = ConflictingMappingName;
		ClearArgs.Slot = EPlayerMappableKeySlot::First;
		ClearArgs.NewKey = FKey();

		Settings->MapPlayerKey(ClearArgs, FailureReason);
	}

	FMapPlayerKeyArgs MapArgs;
	MapArgs.MappingName = PendingMappingName;
	MapArgs.Slot = EPlayerMappableKeySlot::First;
	MapArgs.NewKey = PendingKey;

	Settings->MapPlayerKey(MapArgs, FailureReason);

	if (!FailureReason.IsEmpty())
	{
		UE_LOG(LogTemp, Warning, TEXT("USettingsControlsWidget : remappage de '%s' refuse (%s)."),
			*PendingMappingName.ToString(), *FailureReason.ToString());
	}

	// ApplySettings pousse les surcharges dans les mapping contexts deja
	// enregistres : le pawn n'a rien a rebrancher, la touche est active tout de
	// suite. La sauvegarde, elle, attend la fermeture de l'ecran.
	Settings->ApplySettings();

	PendingMappingName = NAME_None;
	ConflictingMappingName = NAME_None;
	PendingKey = FKey();

	// Les deux lignes concernees ont bouge : on relit tout plutot que de tenir
	// une comptabilite qui finirait par diverger.
	RefreshFromSettings();
}

#undef LOCTEXT_NAMESPACE
