WorldBuilder/WeatherManager.cs
using System;
using Clover.Player;
using Clover.WorldBuilder.Weather;

namespace Clover.WorldBuilder;

[Category( "Clover/World" )]
public class WeatherManager : Component, IWorldEvent, ITimeEvent
{
	public enum WeatherEffects
	{
		None = 0,
		Rain = 1,
		Lightning = 2,
		Wind = 4,
		Fog = 8
	}

	public static DirectionalLight Sun =>
		Game.ActiveScene.GetAllComponents<DirectionalLight>().FirstOrDefault( x => x.Tags.Has( "sun" ) );

	private int StringToInt( string input )
	{
		var hash = 0;
		for ( int i = 0; i < input.Length; i++ )
		{
			hash = input[i] + (hash << 6) + (hash << 16) - hash;
		}

		return hash;
	}

	[Property] public Rain RainComponent { get; set; }
	[Property] public Fog FogComponent { get; set; }
	[Property] public Wind WindComponent { get; set; }
	[Property] public Lightning LightningComponent { get; set; }

	public bool IsInside { get; set; } = false;
	public bool PrecipitationEnabled { get; private set; }
	public bool LightningEnabled { get; private set; }
	public bool WindEnabled { get; private set; }
	public bool FogEnabled { get; private set; }

	protected float GetStaticFloat( string seed )
	{
		return GetStaticFloat( StringToInt( seed ) );
	}

	protected float GetStaticFloat( int seed )
	{
		var random = new Random( seed );
		return (float)random.NextSingle();
	}

	protected float GetPrecipitationChance( DateTime time )
	{
		var input = $"{time.DayOfYear}{time.Hour}-precipitation";
		return GetStaticFloat( input );
	}

	protected int GetPrecipitationLevel( DateTime time )
	{
		var input = $"{time.DayOfYear}{time.Hour}-precipitation-level";
		var value = GetStaticFloat( input );
		if ( value < 0.2f )
		{
			return 1;
		}
		else if ( value < 0.5f )
		{
			return 2;
		}
		else
		{
			return 3;
		}
	}

	/// <summary>
	/// Returns a float between -180 and 180 representing the wind direction.
	/// </summary>
	protected float GetWindDirection( DateTime time )
	{
		var input = $"{time.DayOfYear}{time.Hour}-wind-direction";
		return GetStaticFloat( input ) * 360f - 180f;
	}

	protected float GetLightningChance( DateTime time )
	{
		var input = $"{time.DayOfYear}{time.Hour}-lightning";
		return GetStaticFloat( input );
	}

	protected int GetLightningLevel( DateTime time )
	{
		var input = $"{time.DayOfYear}{time.Hour}-lightning-level";
		var value = GetStaticFloat( input );
		if ( value < 0.2f )
		{
			return 1;
		}
		else if ( value < 0.5f )
		{
			return 2;
		}
		else
		{
			return 3;
		}
	}

	protected float GetFogChance( DateTime time )
	{
		var input = $"{time.DayOfYear}{time.Hour}-fog";
		return GetStaticFloat( input );
	}

	protected float GetCloudDensity( DateTime time )
	{
		var input = $"{time.DayOfYear}{time.Hour}-cloud-density";
		return GetStaticFloat( input );
	}

	protected override void OnStart()
	{
		// debug check today's weather
		/* for ( int i = 0; i < 24; i++ )
		{
			var time = new DateTime( 2024, 6, 14, i, 0, 0 );
			var weather = GetWeather( time );
			Logger.Info( "WeatherManager", $"Weather @ {time.ToString( "h tt" )}: Rain: {weather.RainLevel}, Lightning: {weather.Lightning}, Wind: {weather.WindLevel}, Fog: {weather.Fog}, CloudDensity: {weather.CloudDensity}" );
		} */

		Setup();

		/*NodeManager.TimeManager.OnNewHour += ( hour ) =>
		{
			Setup();
		};

		NodeManager.WorldManager.WorldLoaded += ( world ) =>
		{
			if ( IsProxy || !Networking.IsHost ) return;
			IsInside = world.Data.IsInside;
			Setup( true );
		};*/
	}

	protected override void OnAwake()
	{
		base.OnAwake();
		SetPrecipitation( 0, 0, 0, true );
		SetLightning( 0, true );
		SetWind( 0, true );
		SetFog( 0, true );
		SetCloudDensity( 0.0f, true );
	}

	public class WeatherReport
	{
		public DateTime Time;

		public int RainLevel;
		public int LightningLevel;
		public int WindLevel;
		public int FogLevel;

		public float CloudDensity;

		public float WindDirection;
		// public float WindSpeed;

		public bool Rain => RainLevel > 0;
		public bool Wind => WindLevel > 0;
		public bool Lightning => LightningLevel > 0;

		public WeatherReport( DateTime time )
		{
			Time = time;
			RainLevel = 0;
			LightningLevel = 0;
			WindLevel = 0;
			FogLevel = 0;
			CloudDensity = 0.0f;
			WindDirection = 0.0f;
		}
	}

	public WeatherReport GetWeather( DateTime time )
	{
		var weather = new WeatherReport( time );
		var precipitationChance = GetPrecipitationChance( time );
		var lightningChance = GetLightningChance( time );
		var fogChance = GetFogChance( time );

		if ( precipitationChance > 0.8f )
		{
			weather.RainLevel = GetPrecipitationLevel( time );
			weather.LightningLevel = GetLightningLevel( time );
			// weather.Fog = fogChance > 0.6f;
			weather.FogLevel = fogChance > 0.6f ? 1 : 0;
			weather.WindLevel = 1;
			weather.CloudDensity = 0.5f + GetCloudDensity( time ) * 0.5f;
		}
		else
		{
			weather.RainLevel = 0;
			weather.LightningLevel = 0;

			// higher chance of fog in the morning
			// weather.Fog = time.Hour > 3 && time.Hour < 7 ? fogChance > 0.2f : fogChance > 0.8f;
			weather.FogLevel = time.Hour > 3 && time.Hour < 7 ? fogChance > 0.2f ? 1 : 0 : fogChance > 0.8f ? 1 : 0;

			weather.WindLevel = 0;
			weather.CloudDensity = GetCloudDensity( time ) * 0.5f;
		}

		weather.WindDirection = GetWindDirection( time );

		return weather;
	}

	public WeatherReport GetCurrentWeather()
	{
		return GetWeather( TimeManager.Time );
	}

	public WeatherReport GetLastPrecipitation( DateTime time )
	{
		var weather = new WeatherReport( time );

		if ( !weather.Rain )
		{
			while ( weather.Time > time.AddHours( -168 ) )
			{
				weather.Time = weather.Time.AddHours( -1 );
				var report = GetWeather( weather.Time );
				if ( report.Rain )
				{
					return report;
				}
			}
		}

		if ( !weather.Rain )
		{
			return null;
		}

		return weather;
	}

	private void Setup( bool instant = false )
	{
		Log.Info( "Setting up weather" );

		var now = TimeManager.Time;


		var weather = GetWeather( now );

		SetPrecipitation( weather.RainLevel, weather.WindDirection, weather.WindLevel * 3f, instant );
		SetLightning( weather.LightningLevel, instant );
		SetWind( weather.WindLevel, instant );
		SetFog( weather.FogLevel, instant );
		SetCloudDensity( weather.CloudDensity, instant );

		Log.Info(
			$"Weather {now.Hour}: Rain: {weather.RainLevel}, Lightning: {weather.LightningLevel}, Wind: {weather.WindLevel}, Fog: {weather.FogLevel}, CloudDensity: {weather.CloudDensity}" );
	}

	protected override void OnFixedUpdate()
	{
		if ( !PlayerCharacter.Local.IsValid() ) return;
		WorldPosition = PlayerCharacter.Local.WorldPosition;
	}

	private void SetPrecipitation( int level, float direction, float windSpeed, bool instant = false )
	{
		PrecipitationEnabled = level > 0;
		// var rainInside = GetNode<Rain>( "RainInside" );
		// var rainOutside = GetNode<Rain>( "RainOutside" );

		if ( !PrecipitationEnabled )
		{
			RainComponent.SetEnabled( false );
			return;
		}

		if ( IsInside )
		{
			RainComponent.SetEnabled( false );
			// fade in rain sound
		}
		else
		{
			RainComponent.SetEnabled( true );
			RainComponent.SetLevel( 0 );
			if ( instant )
			{
				RainComponent.SetLevel( level );
			}
			else
			{
				RainComponent.SetLevel( level, true );
			}

			RainComponent.SetWind( direction, windSpeed );
		}
	}

	private void SetLightning( int level, bool instant = false )
	{
		LightningComponent.SetEnabled( level > 0, !instant );
		LightningComponent.SetLevel( level, !instant );
	}

	private void SetWind( int level, bool instant = false )
	{
		WindEnabled = level > 0;
		if ( WindEnabled && IsInside )
		{
			WindComponent.SetEnabled( false );
			return; // no wind inside
		}
		// GetNode<Wind>( "Wind" ).SetEnabledSmooth( state );
		/*if ( instant )
		{
			// GetNode<Wind>( "Wind" ).SetEnabled( state );
		}
		else
		{
			// GetNode<Wind>( "Wind" ).SetEnabledSmooth( state );
		}*/

		WindComponent.SetEnabled( level > 0, !instant );
	}

	private void SetFog( int level, bool instant = false )
	{
		FogEnabled = level > 0;
		// if ( Environment == null ) return;
		// Environment.Environment.FogDensity = state ? 0.04f : 0.0f;
		// GetNode<Fog>( "Fog" ).SetEnabledSmooth( state );
		/* if ( PrecipitationEnabled )
		{
			GetNode<Rain>( "RainOutside" )?.SetFogState( state );
		} */

		/*if ( instant )
		{
			// GetNode<Fog>( "Fog" ).SetEnabled( state );
		}
		else
		{
			// GetNode<Fog>( "Fog" ).SetEnabledSmooth( state );
		}*/

		if ( IsInside )
		{
			FogComponent.SetEnabled( false );
			return;
		}

		FogComponent.SetEnabled( level > 0, !instant );
		FogComponent.SetLevel( level, !instant );
	}

	private void SetCloudDensity( float density, bool instant = false )
	{
		/*if ( SunLight == null )
		{
			Logger.LogError( "WeatherManager", "SunLight is null" );
			return;
		}
		Logger.Info( "WeatherManager", $"Setting cloud density to {density}" );
		SunLight.ShadowBlur = density * 2f;*/
	}

	public Texture GetWeatherIcon()
	{
		var now = TimeManager.Time;
		var weather = GetWeather( now );

		var isDay = TimeManager.IsDay;

		if ( isDay )
		{
			if ( weather.RainLevel > 0 )
			{
				return Texture.LoadFromFileSystem( "ui/icons/weather/partly-cloudy-day-rain.png", FileSystem.Mounted );
			}

			if ( weather.FogLevel > 0 )
			{
				return Texture.LoadFromFileSystem( "ui/icons/weather/fog-day.png", FileSystem.Mounted );
			}

			return Texture.LoadFromFileSystem( "ui/icons/weather/clear-day.png", FileSystem.Mounted );
		}
		else
		{
			if ( weather.Rain && weather.Lightning )
			{
				return Texture.LoadFromFileSystem( "ui/icons/weather/thunderstorms-night-overcast-rain.png",
					FileSystem.Mounted );
			}

			if ( weather.Rain )
			{
				return Texture.LoadFromFileSystem( "ui/icons/weather/partly-cloudy-night-rain.png",
					FileSystem.Mounted );
			}

			if ( weather.FogLevel > 0 )
			{
				return Texture.LoadFromFileSystem( "ui/icons/weather/fog-night.png", FileSystem.Mounted );
			}

			return Texture.LoadFromFileSystem( "ui/icons/weather/clear-night.png", FileSystem.Mounted );
		}

		/* if ( weather.RainLevel > 0 )
		{
			return Texture.Load( FileSystem.Mounted, "ui/icons/weather/rainy.png" );
		}
		else if ( weather.Lightning )
		{
			return Texture.Load( FileSystem.Mounted, "ui/icons/weather/thunderstorm.png" );
		}
		else if ( weather.Fog )
		{
			// return Texture.Load( FileSystem.Mounted, "res://assets/weather/fog.png" );
		}*/

		// return Texture.Load( FileSystem.Mounted, "ui/icons/weather/barometer.png" );
	}

	public void OnWorldLoaded( World world )
	{
	}

	public void OnWorldUnloaded( World world )
	{
	}

	public void OnWorldChanged( World world )
	{
		Log.Info( "IS INSIDE: " + world.Data.IsInside );
		IsInside = world.Data.IsInside;
		Setup( true );
	}

	public void OnNewHour( int hour )
	{
		Setup();
	}

	public void OnNewMinute( int minute )
	{
	}
}