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


#include "TimeofDayManager.h"
#include "DayNightGameStateBase.h"

#include "Engine/DirectionalLight.h"
#include "Engine/SkyLight.h"
#include "Components/SkyLightComponent.h"
#include "EngineUtils.h"
#include "Engine.h"
#include "GameFramework/WorldSettings.h"
#include "Kismet/GameplayStatics.h"

ATimeofDayManager::ATimeofDayManager()
{
	PrimaryActorTick.bCanEverTick = true;

	m_RequiredCaptureDelta = 0.01f;

	SetReplicates(true);
}

void ATimeofDayManager::BeginPlay()
{
	m_GS = Cast<ADayNightGameStateBase>(GetWorld()->GetGameState());

	GatherConfig();

	Super::BeginPlay();

	if (m_PrimarySunLight)
	{
		m_OriginalSunBrightness = m_PrimarySunLight->GetBrightness();
		m_TargetSunBrightness = m_OriginalSunBrightness;
	}
}

void ATimeofDayManager::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (m_PrimarySunLight)
	{
		if (m_LastTimeOfDay == m_GS->GetElapsedGameMinutes())
		{
			m_TimeSinceLastIncrement += DeltaTime;
		}
		else
		{
			m_TimeSinceLastIncrement = 0;
		}

		/** Predict the movement of the sun to smooth out the rotations between replication updates of the actual time of day */
		const float PredictedIncrement = m_GS->GetTimeOfDayIncrement() * m_TimeSinceLastIncrement;

		/** TimeOfDay is expressed in minutes, we need to convert this into a pitch rotation */
		const float MinutesInDay = 24 * 60;
		const float PitchOffset = 90; /* The offset to account for time of day 0 should equal midnight */
		const float PitchRotation = 360 * ((m_GS->GetElapsedGameMinutes() + PredictedIncrement) / MinutesInDay);

		FRotator NewSunRotation = FRotator(PitchRotation + PitchOffset, 45.0f, 0);
		m_PrimarySunLight->SetActorRelativeRotation(NewSunRotation);

		m_LastTimeOfDay = m_GS->GetElapsedGameMinutes();
	}

	bool CurrentNightState = m_GS->IsNight();
	if (CurrentNightState != m_bLastNightState)
	{
		if (CurrentNightState)
		{
			m_TargetSunBrightness = 0.01f;
		}
		else
		{
			m_TargetSunBrightness = m_OriginalSunBrightness;
		}
	}


	// TODO: Check to make sure ptrs aren't null etc.
	if (m_PrimarySunLight == nullptr) return;

	/* Update sun brightness to transition between day and night
	(Note: We cannot disable the sunlight because BP_SkySphere depends on an enabled light to update the skydome) */
	const float LerpSpeed = 0.1f * GetWorldSettings()->GetEffectiveTimeDilation();
	float CurrentSunBrightness = m_PrimarySunLight->GetBrightness();
	float NewSunBrightness = FMath::Lerp(CurrentSunBrightness, m_TargetSunBrightness, LerpSpeed);
	m_PrimarySunLight->SetBrightness(NewSunBrightness);

	m_bLastNightState = CurrentNightState;

	UpdateSkylight();
}


void ATimeofDayManager::GatherConfig()
{
	TArray<AActor*> SkySpheres = GetAllActorsWithName<AActor>(*m_GS->m_SkySphereName);
	TArray<ADirectionalLight*> DirectionalLights = GetAllActorsWithName<ADirectionalLight>(*m_GS->m_PrimarySunLightName);
	TArray<ASkyLight*> SkyLights = GetAllActorsWithName<ASkyLight>(*m_GS->m_SkyLightName);

	if (SkySpheres.Num() > 0)
	{
		m_SkySphere = SkySpheres[0];
	}

	if (DirectionalLights.Num() > 0)
	{
		m_PrimarySunLight = DirectionalLights[0];
	}

	if (SkyLights.Num() > 0)
	{
		m_SkyLightActor = SkyLights[0];
	}
}

void ATimeofDayManager::UpdateSkylight()
{
	if (m_SkyLightActor)
	{
		if (m_GS)
		{
			const float NewIntensity = FMath::Lerp(0.1, 1.0, FMath::Sin((m_GS->GetElapsedMinutesCurrentDay() / m_GS->MINUTESINDAY) * PI));
			m_SkyLightActor->GetLightComponent()->SetIntensity(NewIntensity);

			// possible to uncomment without breaking, see what looks better.
			float ColorChannel = 1.0f * NewIntensity;
			m_SkyLightActor->GetLightComponent()->SetLightColor(FLinearColor(ColorChannel, ColorChannel, ColorChannel, 1.0f));

			if (m_RequiredCaptureDelta < FMath::Abs(NewIntensity - m_LastCapturedIntensity))
			{
				/** Force re-capture of the sky w/ new intensity */
				/** Hacky and costly solution to recapturing the sky, official support NYI */
				m_SkyLightActor->GetLightComponent()->RecaptureSky();
				m_LastCapturedIntensity = NewIntensity;
			}
		}
	}
}

template<typename T> 
TArray<T*> ATimeofDayManager::GetAllActorsWithName(const FName& NameOfActor)
{
	TArray<T*> TempArray;
	for (TActorIterator<T> It(GetWorld()); It; ++It)
	{
		T* actor = *It;
		if (actor && actor->GetName().Contains(NameOfActor.ToString()))
		{
			TempArray.Add(actor);
		}
	}

	return TempArray;
}