// Copyright Pacificao. All Rights Reserved.

#include "AgrarianSkyLightingController.h"

#include "AgrarianGameState.h"
#include "AgrarianPerformanceStats.h"
#include "Components/DirectionalLightComponent.h"
#include "Components/ExponentialHeightFogComponent.h"
#include "Components/SceneComponent.h"
#include "Components/SkyLightComponent.h"
#include "Engine/World.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"

AAgrarianSkyLightingController::AAgrarianSkyLightingController()
{
	PrimaryActorTick.bCanEverTick = true;
	bReplicates = false;

	SceneRoot = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
	RootComponent = SceneRoot;

	SunLight = CreateDefaultSubobject<UDirectionalLightComponent>(TEXT("SunLight"));
	SunLight->SetupAttachment(SceneRoot);
	SunLight->SetIntensity(NoonSunIntensity);
	SunLight->SetLightColor(FLinearColor(1.0f, 0.96f, 0.86f));
	SunLight->SetMobility(EComponentMobility::Movable);

	SkyLight = CreateDefaultSubobject<USkyLightComponent>(TEXT("SkyLight"));
	SkyLight->SetupAttachment(SceneRoot);
	SkyLight->SetIntensity(ClearSkyLightIntensity);
	SkyLight->SetMobility(EComponentMobility::Movable);

	HeightFog = CreateDefaultSubobject<UExponentialHeightFogComponent>(TEXT("HeightFog"));
	HeightFog->SetupAttachment(SceneRoot);
	HeightFog->SetFogDensity(ClearFogDensity);
	HeightFog->SetMobility(EComponentMobility::Movable);
}

void AAgrarianSkyLightingController::BeginPlay()
{
	Super::BeginPlay();
	RefreshSkyLighting();
}

void AAgrarianSkyLightingController::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
	RefreshSkyLighting();
}

void AAgrarianSkyLightingController::RefreshSkyLighting()
{
	SCOPE_CYCLE_COUNTER(STAT_AgrarianSkyLightingRefresh);
	TRACE_CPUPROFILER_EVENT_SCOPE(AgrarianSkyLightingRefresh);

	const UWorld* World = GetWorld();
	const AAgrarianGameState* GameState = World ? World->GetGameState<AAgrarianGameState>() : nullptr;
	if (!GameState)
	{
		return;
	}

	const float SunriseHour = GameState->bHasActiveTileSolarData ? GameState->SunriseHourLocal : 6.0f;
	const float SunsetHour = GameState->bHasActiveTileSolarData ? GameState->SunsetHourLocal : 20.0f;
	CurrentSunAlpha = CalculateSunAlpha(GameState->WorldHours, SunriseHour, SunsetHour);
	CurrentWeather = GameState->Weather;
	CurrentCloudAlpha = CalculateWeatherCloudAlpha(
		GameState->Weather,
		GameState->ActiveWeatherInputs.CloudCoverPercent,
		GameState->ActiveWeatherInputs.bHasProviderData);

	const float WeatherLightMultiplier = FMath::Lerp(1.0f, 0.35f, CurrentCloudAlpha);
	const float SunIntensity = FMath::Lerp(NightSunIntensity, NoonSunIntensity, CurrentSunAlpha) * WeatherLightMultiplier;
	const float SkyIntensity = FMath::Lerp(NightSkyLightIntensity, ClearSkyLightIntensity, CurrentSunAlpha) * FMath::Lerp(1.0f, 0.55f, CurrentCloudAlpha);
	const float FogDensity = FMath::Lerp(ClearFogDensity, StormFogDensity, CurrentCloudAlpha);
	const float SunPitch = FMath::Lerp(-8.0f, -72.0f, CurrentSunAlpha);

	if (SunLight)
	{
		SunLight->SetWorldRotation(FRotator(SunPitch, NorthYawDegrees, 0.0f));
		SunLight->SetIntensity(SunIntensity);
		SunLight->SetLightColor(CalculateSunColor(CurrentSunAlpha, CurrentCloudAlpha));
	}

	if (SkyLight)
	{
		SkyLight->SetIntensity(SkyIntensity);
	}

	if (HeightFog)
	{
		HeightFog->SetFogDensity(FogDensity);
	}
}

float AAgrarianSkyLightingController::CalculateSunAlpha(float HourOfDay, float SunriseHour, float SunsetHour) const
{
	const float NormalizedHour = FMath::Fmod(HourOfDay + 24.0f, 24.0f);
	const float SafeSunrise = FMath::Fmod(SunriseHour + 24.0f, 24.0f);
	const float SafeSunset = FMath::Fmod(SunsetHour + 24.0f, 24.0f);
	const float DayLength = FMath::Max(0.1f, SafeSunset >= SafeSunrise ? SafeSunset - SafeSunrise : (24.0f - SafeSunrise) + SafeSunset);

	float HoursSinceSunrise = NormalizedHour - SafeSunrise;
	if (HoursSinceSunrise < 0.0f)
	{
		HoursSinceSunrise += 24.0f;
	}

	if (HoursSinceSunrise > DayLength)
	{
		return 0.0f;
	}

	const float DayProgress = FMath::Clamp(HoursSinceSunrise / DayLength, 0.0f, 1.0f);
	return FMath::Clamp(FMath::Sin(PI * DayProgress), 0.0f, 1.0f);
}

float AAgrarianSkyLightingController::CalculateWeatherCloudAlpha(EAgrarianWeatherType Weather, float ProviderCloudCoverPercent, bool bHasProviderCloudCover) const
{
	float WeatherAlpha = 0.0f;
	switch (Weather)
	{
	case EAgrarianWeatherType::Rain:
		WeatherAlpha = 0.65f;
		break;
	case EAgrarianWeatherType::ColdWind:
		WeatherAlpha = 0.45f;
		break;
	case EAgrarianWeatherType::Storm:
		WeatherAlpha = 1.0f;
		break;
	default:
		WeatherAlpha = 0.0f;
		break;
	}

	if (bHasProviderCloudCover)
	{
		WeatherAlpha = FMath::Max(WeatherAlpha, FMath::Clamp(ProviderCloudCoverPercent / 100.0f, 0.0f, 1.0f));
	}

	return WeatherAlpha;
}

FLinearColor AAgrarianSkyLightingController::CalculateSunColor(float SunAlpha, float CloudAlpha) const
{
	const FLinearColor DawnColor(1.0f, 0.62f, 0.38f);
	const FLinearColor NoonColor(1.0f, 0.96f, 0.86f);
	const FLinearColor StormColor(0.52f, 0.58f, 0.66f);
	const FLinearColor TimeColor = FLinearColor::LerpUsingHSV(DawnColor, NoonColor, FMath::Clamp(SunAlpha, 0.0f, 1.0f));
	return FLinearColor::LerpUsingHSV(TimeColor, StormColor, FMath::Clamp(CloudAlpha, 0.0f, 1.0f));
}
