#define DEFERRED1
#define PRECOMPUTED_ATMOSPHERIC_SCATTERING

out vec4 cloudData;

/* DRAWBUFFERS:2 */

flat in vec3 sunIlluminance;
flat in vec3 moonIlluminance;
flat in vec3 skyIlluminance;

#include "/lib/Head/Common.inc"

uniform sampler2D noisetex;

uniform sampler3D colortex4;
uniform sampler2D colortex5;

uniform sampler2D colortex15;
uniform sampler2D colortex13;
uniform sampler2D colortex14;
uniform sampler2D colortex12; 

uniform sampler2D depthtex0;

#if defined DISTANT_HORIZONS
	uniform sampler2D dhDepthTex0;
#endif

uniform float nightVision;
uniform float viewWidth;
uniform float viewHeight;
uniform float wetness;
uniform float eyeAltitude;
uniform float frameTimeCounter;
uniform float isLightningFlashing;

uniform vec3 cameraPosition;
uniform vec3 worldSunVector;
uniform vec3 worldLightVector;

uniform int frameCounter;
uniform int moonPhase;

#ifdef IS_IRIS
    uniform int dimension;
#else
    uniform int worldTime;
    #define dimension (worldTime < 0 ? 1 : (worldTime > 24000 ? -1 : 0))
#endif

uniform mat4 gbufferProjectionInverse;
uniform mat4 gbufferModelViewInverse;

uniform vec2 screenSize;
uniform vec2 screenPixelSize;

#define GetDepth(texel) texelFetch(depthtex0, texel, 0).x

vec3 ScreenToViewSpaceRaw(in vec3 screenPos) {	
	vec3 NDCPos = screenPos * 2.0 - 1.0;
	vec3 viewPos = projMAD(gbufferProjectionInverse, NDCPos);
	viewPos /= gbufferProjectionInverse[2].w * NDCPos.z + gbufferProjectionInverse[3].w;

	return viewPos;
}

#include "/lib/Head/Noise.inc"

#include "/lib/Atmosphere/Atmosphere.glsl"

#include "/lib/Atmosphere/VolumetricClouds.glsl"

#include "/lib/Atmosphere/PlanarClouds.glsl"

#ifdef AURORA
	#include "/lib/Atmosphere/Aurora.glsl"
#endif

#if defined DISTANT_HORIZONS
	float depthMax4x4DH(in vec2 coord) {
		vec4 depthSamples0 = textureGather(dhDepthTex0, coord + vec2( 2.0,  2.0) * screenPixelSize);
		vec4 depthSamples1 = textureGather(dhDepthTex0, coord + vec2(-2.0,  2.0) * screenPixelSize);
		vec4 depthSamples2 = textureGather(dhDepthTex0, coord + vec2( 2.0, -2.0) * screenPixelSize);
		vec4 depthSamples3 = textureGather(dhDepthTex0, coord + vec2(-2.0, -2.0) * screenPixelSize);

		return max(
			max(maxOf(depthSamples0), maxOf(depthSamples1)),
			max(maxOf(depthSamples2), maxOf(depthSamples3))
		);
	}
#else
	float depthMax4x4(in vec2 coord) {
		vec4 depthSamples0 = textureGather(depthtex0, coord + vec2( 2.0,  2.0) * screenPixelSize);
		vec4 depthSamples1 = textureGather(depthtex0, coord + vec2(-2.0,  2.0) * screenPixelSize);
		vec4 depthSamples2 = textureGather(depthtex0, coord + vec2( 2.0, -2.0) * screenPixelSize);
		vec4 depthSamples3 = textureGather(depthtex0, coord + vec2(-2.0, -2.0) * screenPixelSize);

		return max(
			max(maxOf(depthSamples0), maxOf(depthSamples1)),
			max(maxOf(depthSamples2), maxOf(depthSamples3))
		);
	}
#endif

#if TEMPORAL_UPSCALING == 2
	const ivec2[4] checkerboardOffset = ivec2[4](
		ivec2(0, 0), ivec2(1, 1),
		ivec2(1, 0), ivec2(0, 1)
	);
#elif TEMPORAL_UPSCALING == 3
	const ivec2[9] checkerboardOffset = ivec2[9](
		ivec2(0, 0), ivec2(2, 0), ivec2(0, 2),
		ivec2(2, 2), ivec2(1, 1), ivec2(1, 0),
		ivec2(1, 2), ivec2(0, 1), ivec2(2, 1)
	);
#elif TEMPORAL_UPSCALING == 4
	const ivec2[16] checkerboardOffset = ivec2[16](
		ivec2(0, 0), ivec2(2, 0), ivec2(0, 2), ivec2(2, 2),
		ivec2(1, 1), ivec2(3, 1), ivec2(1, 3), ivec2(3, 3),
		ivec2(1, 0), ivec2(3, 0), ivec2(1, 2), ivec2(3, 2),
		ivec2(0, 1), ivec2(2, 1), ivec2(0, 3), ivec2(2, 3)
	);
#endif

const int cloudsRenderFactor = TEMPORAL_UPSCALING * TEMPORAL_UPSCALING;

void GetPlanetCurvePosition(inout vec3 p) {
    p.y = length(p + vec3(0.0, planetRadius, 0.0)) - planetRadius;
}

vec4 RenderSkybox(in vec3 worldDir, in float dither, in CloudProperties cloudProperties) {
	vec4 cloudsData = vec4(0.0, 0.0, 0.0, 1.0);
	vec3 skyRadiance = texture(colortex5, ProjectSky(worldDir)).rgb;

	float LdotV = dot(worldDir, worldLightVector);

	vec4 phases;
	phases.x = 	HenyeyGreensteinPhase(LdotV, cloudForwardG) 	  * 0.7  + HenyeyGreensteinPhase(LdotV, cloudBackwardG)		  * cloudBackwardWeight  	  + CornetteShanksPhase(LdotV, 0.9) * cloudProperties.cloudPeakWeight;
	phases.y = 	HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.7) * 0.35 + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.7) * cloudBackwardWeight * 0.6 + CornetteShanksPhase(LdotV, 0.6) * cloudProperties.cloudPeakWeight * 0.5;
	phases.z = 	HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.5) * 0.17 + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.5) * cloudBackwardWeight * 0.3 + CornetteShanksPhase(LdotV, 0.4) * cloudProperties.cloudPeakWeight * 0.2;
	phases.w = 	HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.3) * 0.08 + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.3) * cloudBackwardWeight * 0.2 + CornetteShanksPhase(LdotV, 0.2) * cloudProperties.cloudPeakWeight * 0.1;

	vec3 planeOriginVol = vec3(0.0, planetRadius + eyeAltitude, 0.0);
	float noiseDetail = GetNoiseDetail(worldDir);

	#ifdef VOLUMETRIC_CLOUDS
		cloudsData = RenderCloudLayer(
			cloudProperties,
			worldDir,
			dither,
			phases,
			wind,
			skyRadiance,
			planeOriginVol,
			noiseDetail
		);

		#if CLOUD_LAYER2_ENABLED == 1
		{
			CloudProperties layer2Props = GetLayer2CloudProperties();
			vec4 phases2;
			phases2.x = HenyeyGreensteinPhase(LdotV, cloudForwardG) 	   * 0.65 + HenyeyGreensteinPhase(LdotV, cloudBackwardG) 		* cloudBackwardWeight 		+ CornetteShanksPhase(LdotV, 0.8) * layer2Props.cloudPeakWeight;
			phases2.y = HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.7) * 0.3  + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.7) * cloudBackwardWeight * 0.5 + CornetteShanksPhase(LdotV, 0.5) * layer2Props.cloudPeakWeight * 0.5;
			phases2.z = HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.5) * 0.15 + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.5) * cloudBackwardWeight * 0.25 + CornetteShanksPhase(LdotV, 0.3) * layer2Props.cloudPeakWeight * 0.2;
			phases2.w = HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.3) * 0.07 + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.3) * cloudBackwardWeight * 0.15 + CornetteShanksPhase(LdotV, 0.15) * layer2Props.cloudPeakWeight * 0.1;

			vec4 layer2Clouds = RenderCloudLayer(
				layer2Props,
				worldDir,
				dither,
				phases2,
				wind2,
				skyRadiance,
				planeOriginVol,
				noiseDetail * 0.8
			);

			if (eyeAltitude < layer2Props.altitude) {
				cloudsData.rgb += layer2Clouds.rgb * cloudsData.a;
				cloudsData.a *= layer2Clouds.a;
			} else {
				cloudsData.rgb = cloudsData.rgb * layer2Clouds.a + layer2Clouds.rgb;
				cloudsData.a *= layer2Clouds.a;
			}
		}
		#endif

#if CLOUD_LAYER3_ENABLED == 1
{
    CloudProperties layer3Props = GetLayer3CloudProperties();
    vec4 phases3;

    phases3.x = HenyeyGreensteinPhase(LdotV, cloudForwardG)       * 0.6
              + HenyeyGreensteinPhase(LdotV, cloudBackwardG)     * cloudBackwardWeight
              + CornetteShanksPhase(LdotV, 0.7)                  * layer3Props.cloudPeakWeight;

    phases3.y = HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.7) * 0.25
              + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.7) * cloudBackwardWeight * 0.4
              + CornetteShanksPhase(LdotV, 0.4)                   * layer3Props.cloudPeakWeight * 0.4;

    phases3.z = HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.5) * 0.12
              + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.5) * cloudBackwardWeight * 0.2
              + CornetteShanksPhase(LdotV, 0.25)                  * layer3Props.cloudPeakWeight * 0.15;

    phases3.w = HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.3) * 0.05
              + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.3) * cloudBackwardWeight * 0.1
              + CornetteShanksPhase(LdotV, 0.1)                   * layer3Props.cloudPeakWeight * 0.08;

    vec4 layer3Clouds = RenderCloudLayer(
        layer3Props,
        worldDir,
        dither,
        phases3,
        wind3,
        skyRadiance,
        planeOriginVol,
        noiseDetail * 0.6
    );

    if (eyeAltitude < layer3Props.altitude) {
        cloudsData.rgb += layer3Clouds.rgb * cloudsData.a;
        cloudsData.a *= layer3Clouds.a;
    } else {
        cloudsData.rgb = cloudsData.rgb * layer3Clouds.a + layer3Clouds.rgb;
        cloudsData.a *= layer3Clouds.a;
    }
}
#endif


	#if CLOUD_BOXY_ENABLED == 1
	{
		CloudProperties boxyProps = GetBoxyCloudProperties();
		vec4 phasesB;
		phasesB.x = HenyeyGreensteinPhase(LdotV, cloudForwardG) 	   * 0.55 + HenyeyGreensteinPhase(LdotV, cloudBackwardG) 		* cloudBackwardWeight 		+ CornetteShanksPhase(LdotV, 0.6) * boxyProps.cloudPeakWeight;
		phasesB.y = HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.7) * 0.2  + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.7) * cloudBackwardWeight * 0.3 + CornetteShanksPhase(LdotV, 0.35) * boxyProps.cloudPeakWeight * 0.3;
		phasesB.z = HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.5) * 0.1  + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.5) * cloudBackwardWeight * 0.15 + CornetteShanksPhase(LdotV, 0.2) * boxyProps.cloudPeakWeight * 0.1;
		phasesB.w = HenyeyGreensteinPhase(LdotV, cloudForwardG * 0.3) * 0.04 + HenyeyGreensteinPhase(LdotV, cloudBackwardG * 0.3) * cloudBackwardWeight * 0.08 + CornetteShanksPhase(LdotV, 0.08) * boxyProps.cloudPeakWeight * 0.05;

		vec4 boxyClouds = RenderBoxyCloudLayer(
			boxyProps,
			worldDir,
			dither,
			phasesB,
			windBoxy,
			skyRadiance,
			planeOriginVol,
			noiseDetail * 0.5
		);

		if (eyeAltitude < boxyProps.altitude) {
			cloudsData.rgb += boxyClouds.rgb * cloudsData.a;
			cloudsData.a *= boxyClouds.a;
		} else {
			cloudsData.rgb = cloudsData.rgb * boxyClouds.a + boxyClouds.rgb;
			cloudsData.a *= boxyClouds.a;
		}
	}
	#endif

	#ifdef PLANAR_CLOUDS
		vec3 planeOriginPlan = vec3(0.0, planetRadius + eyeAltitude, 0.0);
		bool intersectsGround = RaySphereIntersection(planeOriginPlan, worldDir, planetRadius).y >= 0.0;
		
		if ((worldDir.y > 0.0 && eyeAltitude < CLOUD_PLANE_ALTITUDE)
		|| (intersectsGround && eyeAltitude > CLOUD_PLANE_ALTITUDE)) {
			vec2 intersection = RaySphereIntersection(planeOriginPlan, worldDir, planetRadius + CLOUD_PLANE_ALTITUDE);
			float cloudDistance = eyeAltitude > CLOUD_PLANE_ALTITUDE ? intersection.x : intersection.y;

			if (cloudDistance > 0.0 && cloudDistance < planetRadius + CLOUD_PLANE_ALTITUDE) {
				vec3 cloudPos = worldDir * cloudDistance;

				#ifdef FULL_AERIAL_PERSPECTIVE
					vec3 airTransmittance;
					vec3 aerialPerspective = 20.0 * GetSkyRadianceToPoint(atmosphereModel, cloudPos, worldSunVector, airTransmittance);
				#endif
				cloudPos += cameraPosition;

				vec4 cloudsTemp = vec4(0.0, 0.0, 0.0, 1.0);

				#ifdef CIRROCUMULUS_CLOUDS
					#ifdef CLOUDS_WEATHER
						if (cloudDynamicWeather.x < 0.4)
					#endif
					{
						vec4 sampleTemp = PlanarSample1(cloudDistance, cloudPos.xz, LdotV, dither, phases, worldDir);

						if (sampleTemp.a > minTransmittance) {
							#ifdef FULL_AERIAL_PERSPECTIVE
								float atmosFade = expf(-cloudDistance * fma(0.025, wetness, 0.05) * 0.00015);
								sampleTemp.rgb *= airTransmittance;
								sampleTemp.rgb += aerialPerspective * sampleTemp.a;
							#else
								float atmosFade = expf(-cloudDistance * fma(0.05, wetness, 0.1) * 0.00015);
							#endif
							sampleTemp.rgb = sampleTemp.rgb * atmosFade + skyRadiance * sampleTemp.a * oneMinus(atmosFade);
						}

						cloudsTemp.rgb = sampleTemp.rgb;
						cloudsTemp.a -= sampleTemp.a;
					}
				#endif
				#if CIRRUS_CLOUDS > 0
					#ifdef CLOUDS_WEATHER
						if (cloudDynamicWeather.y < 0.5)
					#endif
					{
						vec4 sampleTemp = PlanarSample0(cloudDistance, cloudPos.xz, LdotV);

						if (sampleTemp.a > minTransmittance) {
							#ifdef FULL_AERIAL_PERSPECTIVE
								float atmosFade = expf(-cloudDistance * fma(0.025, wetness, 0.05) * 0.00015);
								sampleTemp.rgb *= airTransmittance;
								sampleTemp.rgb += aerialPerspective * sampleTemp.a;
							#else
								float atmosFade = expf(-cloudDistance * fma(0.05, wetness, 0.1) * 0.00015);
							#endif
							sampleTemp.rgb = sampleTemp.rgb * atmosFade + skyRadiance * sampleTemp.a * oneMinus(atmosFade);
						}

						cloudsTemp.rgb += sampleTemp.rgb * cloudsTemp.a;
						cloudsTemp.a *= 1.0 - sampleTemp.a;
					}
				#endif

				if (eyeAltitude < CLOUD_PLANE_ALTITUDE) {
					cloudsData.rgb += cloudsTemp.rgb * cloudsData.a;
				} else {
					cloudsData.rgb = cloudsData.rgb * cloudsTemp.a + cloudsTemp.rgb;
				}

				cloudsData.a *= cloudsTemp.a;
			}
		}
	#endif

	vec4 outData;
	outData.rgb = cloudsData.rgb;

	#ifdef AURORA
		if (auroraAmount > 1e-2) outData.rgb += NightAurora(worldDir) * cloudsData.a;
	#endif

	#if CLOUD_BOXY_ENABLED == 1
	{
		vec4 makeupClouds = RenderMakeupClouds(worldDir, skyRadiance, dither);
		
		if (makeupClouds.a < 0.99) {
			if (eyeAltitude < MAKEUP_CLOUD_PLANE) {
				// Below clouds - add on top
				outData.rgb += makeupClouds.rgb * outData.a;
			} else {
				// Above clouds - blend
				outData.rgb = outData.rgb * makeupClouds.a + makeupClouds.rgb;
			}
			outData.a *= makeupClouds.a;
		}
	}
	#endif

	#if BOXY_CLOUDS == 1
		vec4 boxyData = RenderBoxyClouds(worldDir, dither, phases);
		if (boxyData.a < 0.99) {
			if (eyeAltitude < BOXY_CLOUDS_ALTITUDE) {
				outData.rgb += boxyData.rgb * outData.a;
			} else {
				outData.rgb = outData.rgb * boxyData.a + boxyData.rgb;
			}
			outData.a *= boxyData.a;
		}
	#endif

	outData.rgb += RenderPlanet(worldDir, worldSunVector) * remap(minTransmittance, 1.0, cloudsData.a);
	outData.rgb += RenderPlanet2(worldDir, worldSunVector) * remap(minTransmittance, 1.0, cloudsData.a);
	outData.rgb += RenderMilkyWay(worldDir, worldSunVector, dimension) * remap(minTransmittance, 1.0, cloudsData.a);
	outData.rgb += RenderEarth(worldDir, worldSunVector, dimension) * remap(minTransmittance, 1.0, cloudsData.a);

	outData.a = remap(minTransmittance, 1.0, cloudsData.a);

	return clamp16F(outData);
}

void main() {
	ivec2 texel = ivec2(gl_FragCoord.xy);

	ivec2 cloudTexel = texel * TEMPORAL_UPSCALING + checkerboardOffset[frameCounter % cloudsRenderFactor];
	vec2 coord = cloudTexel * screenPixelSize;

	#if defined DISTANT_HORIZONS
		if (min(GetDepth(RawCoord(coord)), depthMax4x4DH(coord)) >= 1.0) {
	#else
		if (depthMax4x4(coord) >= 1.0) {
	#endif
		vec3 viewPos  = ScreenToViewSpaceRaw(vec3(coord, 1.0));
		vec3 worldDir = mat3(gbufferModelViewInverse) * normalize(viewPos);

		float dither = R1(frameCounter / cloudsRenderFactor, texelFetch(noisetex, cloudTexel & 255, 0).a);

		cloudData = RenderSkybox(worldDir, dither, GetGlobalCloudProperties());
	}
}