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


#include "SettingsMenuWidget.h"

#include "Components/Button.h"
#include "Components/WidgetSwitcher.h"
#include "ConfirmDialogWidget.h"
#include "EnhancedInputSubsystems.h"
#include "Engine/LocalPlayer.h"
#include "InputCoreTypes.h"
#include "KeyCaptureWidget.h"
#include "UserSettings/EnhancedInputUserSettings.h"
#include "SettingsTabWidget.h"
#include "SurvivalUserSettings.h"

#define LOCTEXT_NAMESPACE "Reglages"

namespace
{
	// Ordre des pages dans le WidgetSwitcher. Un bouton dont la page manque se
	// grise tout seul, ce qui reste vrai si un onglet vient a etre retire.
	constexpr int32 TabIndexGameplay = 0;
	constexpr int32 TabIndexDisplay = 1;
	constexpr int32 TabIndexGraphics = 2;
	constexpr int32 TabIndexControls = 3;
	constexpr int32 TabIndexAudio = 4;
}

USettingsMenuWidget::USettingsMenuWidget(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	// Sans ce drapeau le widget ne peut pas prendre le focus clavier, et
	// NativeOnKeyDown ne serait jamais appele. Il ne se change qu'a la
	// construction, d'ou sa presence ici et pas dans Open().
	SetIsFocusable(true);

	ResetDialogMessage = LOCTEXT("ResetConfirm", "Réinitialiser cet onglet ?");
}

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

	if (BackButton)
	{
		BackButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleBackClicked);
	}

	if (ResetButton)
	{
		ResetButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleResetClicked);
	}

	if (GameplayTabButton)
	{
		GameplayTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleGameplayTabClicked);
	}

	if (DisplayTabButton)
	{
		DisplayTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleDisplayTabClicked);
	}

	if (GraphicsTabButton)
	{
		GraphicsTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleGraphicsTabClicked);
	}

	if (ControlsTabButton)
	{
		ControlsTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleControlsTabClicked);
	}

	if (AudioTabButton)
	{
		AudioTabButton->OnClicked.AddDynamic(this, &USettingsMenuWidget::HandleAudioTabClicked);
	}

	// Chaque onglet recoit un pointeur vers nous : c'est par la qu'il demandera
	// une confirmation, sans avoir a fabriquer sa propre boite.
	if (TabSwitcher)
	{
		for (int32 Index = 0; Index < TabSwitcher->GetNumWidgets(); ++Index)
		{
			if (USettingsTabWidget* Tab = Cast<USettingsTabWidget>(TabSwitcher->GetWidgetAtIndex(Index)))
			{
				Tab->SetOwningMenu(this);
			}
		}
	}

	UpdateTabButtonStates();
}

void USettingsMenuWidget::NativeDestruct()
{
	if (BackButton)
	{
		BackButton->OnClicked.RemoveDynamic(this, &USettingsMenuWidget::HandleBackClicked);
	}

	if (ResetButton)
	{
		ResetButton->OnClicked.RemoveDynamic(this, &USettingsMenuWidget::HandleResetClicked);
	}

	// La boite et le voile vivent au viewport, pas dans notre arbre : ils ne
	// disparaitraient pas avec nous.
	if (ResetDialog)
	{
		ResetDialog->RemoveFromParent();
		ResetDialog = nullptr;
	}

	if (KeyCapture)
	{
		KeyCapture->RemoveFromParent();
		KeyCapture = nullptr;
	}

	Super::NativeDestruct();
}

void USettingsMenuWidget::Open()
{
	SetVisibility(ESlateVisibility::Visible);

	// L'ecran a pu etre referme sur un autre onglet que celui d'ouverture.
	UpdateTabButtonStates();

	// L'ecran a pu etre ouvert, referme, puis rouvert apres un changement fait
	// ailleurs : on relit toujours plutot que de faire confiance a l'affichage.
	if (USettingsTabWidget* ActiveTab = GetActiveTab())
	{
		ActiveTab->RefreshFromSettings();
	}

	// Le focus doit etre repris a CHAQUE ouverture, sinon Echap reste sans effet.
	SetKeyboardFocus();
}

void USettingsMenuWidget::Close()
{
	// Fermer l'ecran avec une question en suspens vaut un refus : sinon le
	// demandeur -- typiquement un changement de resolution en attente de
	// confirmation -- resterait dans un etat non tranche.
	if (bResetDialogOpen)
	{
		FinishConfirmation(false);
	}

	if (bKeyCaptureOpen)
	{
		CloseKeyCapture();
	}

	SetVisibility(ESlateVisibility::Collapsed);

	// La sauvegarde a lieu ici et pas a chaque clic de fleche : ecrire le .ini
	// vingt fois pendant qu'on cherche la bonne sensibilite ne sert a rien.
	if (USurvivalUserSettings* Settings = USurvivalUserSettings::Get())
	{
		Settings->SaveSettings();
	}

	// Les touches remappees vivent ailleurs : Enhanced Input les serialise pour
	// son compte, dans son propre fichier. Deux systemes de persistance, mais un
	// seul moment ou l'on ecrit.
	if (const ULocalPlayer* LocalPlayer = GetOwningLocalPlayer())
	{
		if (UEnhancedInputLocalPlayerSubsystem* Subsystem = LocalPlayer->GetSubsystem<UEnhancedInputLocalPlayerSubsystem>())
		{
			if (UEnhancedInputUserSettings* InputSettings = Subsystem->GetUserSettings())
			{
				InputSettings->SaveSettings();
			}
		}
	}
}

FReply USettingsMenuWidget::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent)
{
	const FKey Key = InKeyEvent.GetKey();

	if (Key == EKeys::Escape)
	{
		// La boite et le voile gerent Echap eux-memes quand ils ont le focus.
		// S'ils l'ont perdu, on annule ici plutot que de fermer tout l'ecran en
		// laissant la question ou le voile affiche.
		if (bKeyCaptureOpen)
		{
			CloseKeyCapture();
			return FReply::Handled();
		}

		if (bResetDialogOpen)
		{
			FinishConfirmation(false);
			return FReply::Handled();
		}

		OnCloseRequested.Broadcast();

		// Handled : sans ca la touche continue de remonter et le menu de pause
		// la consommerait une seconde fois.
		return FReply::Handled();
	}

	return Super::NativeOnKeyDown(InGeometry, InKeyEvent);
}

USettingsTabWidget* USettingsMenuWidget::GetActiveTab() const
{
	return TabSwitcher ? Cast<USettingsTabWidget>(TabSwitcher->GetActiveWidget()) : nullptr;
}

void USettingsMenuWidget::ShowTab(int32 Index)
{
	if (!TabSwitcher || Index < 0 || Index >= TabSwitcher->GetNumWidgets())
	{
		return;
	}

	TabSwitcher->SetActiveWidgetIndex(Index);

	// L'onglet a change, donc le bouton a griser aussi.
	UpdateTabButtonStates();

	if (USettingsTabWidget* ActiveTab = GetActiveTab())
	{
		ActiveTab->RefreshFromSettings();
	}
}

void USettingsMenuWidget::UpdateTabButtonStates()
{
	const int32 PageCount = TabSwitcher ? TabSwitcher->GetNumWidgets() : 0;
	const int32 ActiveIndex = TabSwitcher ? TabSwitcher->GetActiveWidgetIndex() : INDEX_NONE;

	// Un bouton est cliquable si sa page existe ET qu'on n'y est pas deja.
	// Griser l'onglet courant lui sert d'indicateur de position : sans ca, rien
	// ne distingue visuellement l'onglet affiche des autres.
	auto ApplyState = [PageCount, ActiveIndex](UButton* Button, int32 TabIndex)
	{
		if (Button)
		{
			Button->SetIsEnabled(PageCount > TabIndex && ActiveIndex != TabIndex);
		}
	};

	ApplyState(GameplayTabButton, TabIndexGameplay);
	ApplyState(DisplayTabButton, TabIndexDisplay);
	ApplyState(GraphicsTabButton, TabIndexGraphics);
	ApplyState(ControlsTabButton, TabIndexControls);
	ApplyState(AudioTabButton, TabIndexAudio);
}

void USettingsMenuWidget::HandleBackClicked()
{
	OnCloseRequested.Broadcast();
}

UConfirmDialogWidget* USettingsMenuWidget::EnsureDialog()
{
	if (ResetDialog)
	{
		return ResetDialog;
	}

	if (!ResetDialogClass)
	{
		return nullptr;
	}

	ResetDialog = CreateWidget<UConfirmDialogWidget>(GetOwningPlayer(), ResetDialogClass);
	if (!ResetDialog)
	{
		return nullptr;
	}

	ResetDialog->OnConfirmed.AddDynamic(this, &USettingsMenuWidget::HandleDialogConfirmed);
	ResetDialog->OnCancelled.AddDynamic(this, &USettingsMenuWidget::HandleDialogCancelled);
	ResetDialog->AddToViewport(DialogZOrder);

	return ResetDialog;
}

bool USettingsMenuWidget::RequestConfirmation(const FText& MessageOrFormat, float CountdownSeconds, const FOnSettingsConfirmationResult& OnResult)
{
	UConfirmDialogWidget* Dialog = EnsureDialog();
	if (!Dialog)
	{
		UE_LOG(LogTemp, Warning, TEXT("USettingsMenuWidget : ResetDialogClass n'est pas assignee, la question ne peut pas etre posee."));
		return false;
	}

	// Une seule boite pour tout l'ecran : si une question etait deja posee, la
	// nouvelle l'ecrase. On repond "non" a l'ancienne pour que son demandeur ne
	// reste pas suspendu a une reponse qui n'arrivera plus.
	if (PendingConfirmation.IsBound())
	{
		const FOnSettingsConfirmationResult Superseded = PendingConfirmation;
		PendingConfirmation.Unbind();
		Superseded.ExecuteIfBound(false);
	}

	PendingConfirmation = OnResult;
	bResetDialogOpen = true;

	if (CountdownSeconds > 0.f)
	{
		Dialog->OpenWithCountdown(MessageOrFormat, CountdownSeconds);
	}
	else
	{
		Dialog->Open(MessageOrFormat);
	}

	return true;
}

void USettingsMenuWidget::FinishConfirmation(bool bConfirmed)
{
	bResetDialogOpen = false;

	if (ResetDialog)
	{
		ResetDialog->Close();
	}

	// Detache AVANT d'appeler : le demandeur a parfaitement le droit de reposer
	// une question dans son propre handler.
	const FOnSettingsConfirmationResult Answered = PendingConfirmation;
	PendingConfirmation.Unbind();

	SetKeyboardFocus();

	Answered.ExecuteIfBound(bConfirmed);
}

void USettingsMenuWidget::HandleDialogConfirmed()
{
	FinishConfirmation(true);
}

void USettingsMenuWidget::HandleDialogCancelled()
{
	FinishConfirmation(false);
}

void USettingsMenuWidget::HandleResetClicked()
{
	FOnSettingsConfirmationResult Callback;
	Callback.BindDynamic(this, &USettingsMenuWidget::HandleResetAnswered);

	// Sans boite configuree on ne bloque pas le joueur : on reinitialise.
	if (!RequestConfirmation(ResetDialogMessage, 0.f, Callback))
	{
		HandleResetAnswered(true);
	}
}

void USettingsMenuWidget::HandleResetAnswered(bool bConfirmed)
{
	if (!bConfirmed)
	{
		return;
	}

	// Uniquement l'onglet visible : un "Reinitialiser" qui remettrait aussi la
	// resolution et le volume serait une mauvaise surprise.
	if (USettingsTabWidget* ActiveTab = GetActiveTab())
	{
		ActiveTab->ResetToDefaults();
	}
}

void USettingsMenuWidget::HandleGameplayTabClicked()
{
	ShowTab(TabIndexGameplay);
}

void USettingsMenuWidget::HandleDisplayTabClicked()
{
	ShowTab(TabIndexDisplay);
}

void USettingsMenuWidget::HandleGraphicsTabClicked()
{
	ShowTab(TabIndexGraphics);
}

void USettingsMenuWidget::HandleControlsTabClicked()
{
	ShowTab(TabIndexControls);
}

void USettingsMenuWidget::HandleAudioTabClicked()
{
	ShowTab(TabIndexAudio);
}

bool USettingsMenuWidget::RequestKeyCapture(const FText& ActionLabel, const FOnSettingsKeyCaptureResult& OnResult)
{
	if (!KeyCaptureClass)
	{
		UE_LOG(LogTemp, Warning, TEXT("USettingsMenuWidget : KeyCaptureClass n'est pas assignee, aucun remappage possible."));
		return false;
	}

	// Cree au premier remappage seulement, puis reutilise -- comme la boite.
	if (!KeyCapture)
	{
		KeyCapture = CreateWidget<UKeyCaptureWidget>(GetOwningPlayer(), KeyCaptureClass);
		if (!KeyCapture)
		{
			return false;
		}

		KeyCapture->OnKeyCaptured.AddDynamic(this, &USettingsMenuWidget::HandleKeyCaptured);
		KeyCapture->OnCaptureCancelled.AddDynamic(this, &USettingsMenuWidget::HandleKeyCaptureCancelled);
		KeyCapture->OnConflictAnswered.AddDynamic(this, &USettingsMenuWidget::HandleKeyConflictAnswered);

		// Au-dessus de la boite : une confirmation de conflit s'ouvre APRES la
		// capture, mais le voile doit couvrir l'ecran pendant qu'il attend.
		KeyCapture->AddToViewport(DialogZOrder + 1);
	}

	// Une capture deja en cours est abandonnee : son demandeur recoit une touche
	// invalide plutot que de rester suspendu.
	if (PendingKeyCapture.IsBound())
	{
		const FOnSettingsKeyCaptureResult Superseded = PendingKeyCapture;
		PendingKeyCapture.Unbind();
		Superseded.ExecuteIfBound(FKey());
	}

	PendingKeyCapture = OnResult;
	bKeyCaptureOpen = true;
	KeyCapture->BeginCapture(ActionLabel);

	return true;
}

void USettingsMenuWidget::ShowKeyCaptureConflict(const FText& Message, const FOnSettingsConfirmationResult& OnAnswer)
{
	if (!KeyCapture || !bKeyCaptureOpen)
	{
		// Le voile a ete referme entre-temps : on repond "non" plutot que de
		// laisser le demandeur suspendu.
		OnAnswer.ExecuteIfBound(false);
		return;
	}

	PendingConflictAnswer = OnAnswer;
	KeyCapture->ShowConflict(Message);
}

void USettingsMenuWidget::CloseKeyCapture()
{
	bKeyCaptureOpen = false;

	if (KeyCapture)
	{
		KeyCapture->EndCapture();
	}

	// On detache tout : si quelqu'un attendait encore une reponse, il la recoit
	// negative plutot que jamais.
	const FOnSettingsKeyCaptureResult AbandonedCapture = PendingKeyCapture;
	const FOnSettingsConfirmationResult AbandonedConflict = PendingConflictAnswer;
	PendingKeyCapture.Unbind();
	PendingConflictAnswer.Unbind();

	SetKeyboardFocus();

	AbandonedCapture.ExecuteIfBound(FKey());
	AbandonedConflict.ExecuteIfBound(false);
}

void USettingsMenuWidget::HandleKeyCaptured(FKey CapturedKey)
{
	// Le voile RESTE ouvert : c'est l'onglet qui decide s'il y a conflit, et
	// donc s'il faut le basculer sur la question ou le refermer.
	const FOnSettingsKeyCaptureResult Answered = PendingKeyCapture;
	PendingKeyCapture.Unbind();

	Answered.ExecuteIfBound(CapturedKey);
}

void USettingsMenuWidget::HandleKeyCaptureCancelled()
{
	// Echap pendant la capture : rien a arbitrer, on referme.
	CloseKeyCapture();
}

void USettingsMenuWidget::HandleKeyConflictAnswered(bool bConfirmed)
{
	// Detache AVANT d'appeler : le demandeur applique le remappage dans son
	// handler et pourrait vouloir rouvrir une capture derriere.
	const FOnSettingsConfirmationResult Answered = PendingConflictAnswer;
	PendingConflictAnswer.Unbind();

	bKeyCaptureOpen = false;

	if (KeyCapture)
	{
		KeyCapture->EndCapture();
	}

	SetKeyboardFocus();

	Answered.ExecuteIfBound(bConfirmed);
}

#undef LOCTEXT_NAMESPACE
