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


#include "ScreenFadeComponent.h"

#include "Blueprint/UserWidget.h"
#include "Camera/PlayerCameraManager.h"
#include "GameFramework/PlayerController.h"

namespace
{
	// Le voile doit passer devant absolument tout, y compris les widgets qu'on
	// ajouterait plus tard sans y penser.
	constexpr int32 ZOrderFade = 1000;
}

UScreenFadeComponent::UScreenFadeComponent()
{
	// Le composant ne tourne QUE pendant un fondu : on allume le tick dans
	// FadeIn/FadeOut et on l'eteint des la cible atteinte. Ticker en permanence
	// pour une valeur qui ne bouge qu'une seconde par mort serait du gaspillage.
	PrimaryComponentTick.bCanEverTick = true;
	PrimaryComponentTick.bStartWithTickEnabled = false;

	// Le fondu doit continuer d'avancer quand le monde est fige : c'est le seul
	// moyen de partir au noir depuis le menu de pause. Sans ce drapeau, l'ecran
	// resterait bloque a mi-chemin et le delegue ne se declencherait jamais.
	PrimaryComponentTick.bTickEvenWhenPaused = true;
}

void UScreenFadeComponent::BeginPlay()
{
	Super::BeginPlay();

	// Cree des maintenant pour que le voile puisse couvrir des la premiere
	// frame, avant meme que le reste du HUD existe.
	EnsureWidget();
}

APlayerController* UScreenFadeComponent::GetOwningController() const
{
	return Cast<APlayerController>(GetOwner());
}

void UScreenFadeComponent::EnsureWidget()
{
	if (FadeWidget)
	{
		return;
	}

	APlayerController* Controller = GetOwningController();
	if (!Controller)
	{
		UE_LOG(LogTemp, Error, TEXT("UScreenFadeComponent : doit etre pose sur un PlayerController, pas sur %s."),
			*GetNameSafe(GetOwner()));
		return;
	}

	// Un widget n'existe que sur la machine du joueur local : inutile et
	// incorrect d'en creer un sur un serveur dedie.
	// ATTENTION : chez un client, ce test est FAUX au BeginPlay du controller --
	// son ULocalPlayer ne lui est attache qu'ensuite, dans SetPlayer(). Un
	// SnapToBlack() fait trop tot noircit donc un voile inexistant, en silence.
	if (!Controller->IsLocalController())
	{
		return;
	}

	if (!FadeWidgetClass)
	{
		UE_LOG(LogTemp, Warning, TEXT("UScreenFadeComponent : FadeWidgetClass n'est pas assignee, aucun fondu ne s'affichera."));
		return;
	}

	FadeWidget = CreateWidget<UUserWidget>(Controller, FadeWidgetClass);
	if (FadeWidget)
	{
		FadeWidget->AddToViewport(ZOrderFade);
		SetCoverage(1.f - Alpha);
	}
}

void UScreenFadeComponent::SetCoverage(float Coverage)
{
	if (!FadeWidget)
	{
		return;
	}

	const float Clamped = FMath::Clamp(Coverage, 0.f, 1.f);
	FadeWidget->SetRenderOpacity(Clamped);

	// HitTestInvisible et jamais Visible : un voile qui capture la souris
	// bloquerait les boutons du menu meme completement transparent.
	FadeWidget->SetVisibility(FMath::IsNearlyZero(Clamped)
		? ESlateVisibility::Collapsed
		: ESlateVisibility::HitTestInvisible);
}

void UScreenFadeComponent::FadeIn(float Duration)
{
	EnsureWidget();

	if (APlayerController* Controller = GetOwningController())
	{
		if (Controller->PlayerCameraManager)
		{
			Controller->PlayerCameraManager->StartCameraFade(
				/*FromAlpha=*/1.f, /*ToAlpha=*/0.f,
				Duration, FadeColor,
				/*bShouldFadeAudio=*/true);
		}
	}

	CurrentDuration = FMath::Max(Duration, 0.01f);
	Alpha = 0.f;
	Target = 1.f;
	bFading = true;
	SetCoverage(1.f);
	SetComponentTickEnabled(true);
}

void UScreenFadeComponent::FadeOut(float Duration)
{
	EnsureWidget();

	if (APlayerController* Controller = GetOwningController())
	{
		if (Controller->PlayerCameraManager)
		{
			// bHoldWhenFinished : l'ecran reste noir au lieu de revenir tout seul.
			Controller->PlayerCameraManager->StartCameraFade(
				/*FromAlpha=*/0.f, /*ToAlpha=*/1.f,
				Duration, FadeColor,
				/*bShouldFadeAudio=*/true, /*bHoldWhenFinished=*/true);
		}
	}

	CurrentDuration = FMath::Max(Duration, 0.01f);
	Target = 0.f;
	bFading = true;
	SetComponentTickEnabled(true);
}

void UScreenFadeComponent::SnapToBlack()
{
	EnsureWidget();

	if (APlayerController* Controller = GetOwningController())
	{
		if (Controller->PlayerCameraManager)
		{
			Controller->PlayerCameraManager->SetManualCameraFade(1.f, FadeColor, /*bFadeAudio=*/true);
		}
	}

	Alpha = 0.f;
	Target = 0.f;
	bFading = false;
	SetCoverage(1.f);
	SetComponentTickEnabled(false);
}

void UScreenFadeComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (!bFading)
	{
		return;
	}

	const float Step = DeltaTime / CurrentDuration;
	Alpha = FMath::Clamp(Alpha + Step * FMath::Sign(Target - Alpha), 0.f, 1.f);

	// Alpha va de 0 (ecran couvert) a 1 (jeu visible) : le voile suit l'inverse.
	SetCoverage(1.f - Alpha);

	if (FMath::IsNearlyEqual(Alpha, Target))
	{
		bFading = false;

		// On eteint le tick AVANT de prevenir : un abonne qui relance un fondu
		// dans son handler doit pouvoir le rallumer.
		SetComponentTickEnabled(false);
		OnFadeFinished.Broadcast();
	}
}

void UScreenFadeComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
	if (FadeWidget)
	{
		FadeWidget->RemoveFromParent();
		FadeWidget = nullptr;
	}

	Super::EndPlay(EndPlayReason);
}
