using UnityEngine;
using UnityEngine.Rendering.Universal;

#if UNITY_2022_3_OR_NEWER
using ExternPropertyAttributes;

namespace FlatKit {
public class FlatKitFog : ScriptableRendererFeature {
    [Tooltip("To create new settings use 'Create > FlatKit > Fog Settings'.")]
    [Expandable]
    public FogSettings settings;

    private Material _effectMaterial;
    private ScreenRenderPass _fullScreenPass;
    private bool _requiresColor;
    private bool _injectedBeforeTransparents;
    private ScriptableRenderPassInput _requirements = ScriptableRenderPassInput.Color;

    private Texture2D _lutDepth;
    private Texture2D _lutHeight;

    private const string ShaderName = "Hidden/FlatKit/FogWrap";
    private const string CameraRelativePosition = "FOG_CAMERA_RELATIVE";
    private const string UseDistanceFog = "USE_DISTANCE_FOG";
    private const string UseHeightFog = "USE_HEIGHT_FOG";
    private static int distanceLut => Shader.PropertyToID("_DistanceLUT");
    private static int near => Shader.PropertyToID("_Near");
    private static int far => Shader.PropertyToID("_Far");
    private static int distanceFogIntensity => Shader.PropertyToID("_DistanceFogIntensity");
    private static int heightLut => Shader.PropertyToID("_HeightLUT");
    private static int lowWorldY => Shader.PropertyToID("_LowWorldY");
    private static int highWorldY => Shader.PropertyToID("_HighWorldY");
    private static int heightFogIntensity => Shader.PropertyToID("_HeightFogIntensity");
    private static int distanceHeightBlend => Shader.PropertyToID("_DistanceHeightBlend");

    /// <summary>
    /// Access the runtime effect material to override fog parameters at runtime.
    /// This enables minimal, code-driven tweaks without changing the Settings asset.
    ///
    /// Shader (2022.3+ path): "Hidden/FlatKit/FogWrap"
    ///
    /// Common shader properties you can set:
    /// - Textures: _DistanceLUT (Texture2D), _HeightLUT (Texture2D)
    /// - Floats: _Near, _Far, _LowWorldY, _HighWorldY,
    ///           _DistanceFogIntensity, _HeightFogIntensity, _DistanceHeightBlend
    /// - Keywords: USE_DISTANCE_FOG, USE_HEIGHT_FOG, FOG_CAMERA_RELATIVE
    ///
    /// Notes:
    /// - If you supply your own LUTs, set _DistanceLUT/_HeightLUT directly.
    /// - Settings asset changes (via its inspector) can overwrite your values when applied.
    /// </summary>
    public Material EffectMaterial => _effectMaterial;

    public override void Create() {
        // Settings.
        {
            if (settings == null) return;
            settings.onSettingsChanged = null;
            settings.onReset = null;
            settings.onSettingsChanged += SetMaterialProperties;
            settings.onReset += SetMaterialProperties;
        }

        // Material.
        {
#if UNITY_EDITOR
            settings.effectMaterial = SubAssetMaterial.GetOrCreate(settings, ShaderName);
            if (settings.effectMaterial == null) return;
#endif
            _effectMaterial = settings.effectMaterial;
            SetMaterialProperties();
        }

        {
            _fullScreenPass = new ScreenRenderPass {
                renderPassEvent = settings.renderEvent,
            };

            _requirements = ScriptableRenderPassInput.Depth | ScriptableRenderPassInput.Color;
            ScriptableRenderPassInput modifiedRequirements = _requirements;

            _requiresColor = (_requirements & ScriptableRenderPassInput.Color) != 0;
            _injectedBeforeTransparents = settings.renderEvent <= RenderPassEvent.BeforeRenderingTransparents;

            if (_requiresColor && !_injectedBeforeTransparents) {
                modifiedRequirements ^= ScriptableRenderPassInput.Color;
            }

            _fullScreenPass.ConfigureInput(modifiedRequirements);
        }
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) {
        if (settings == null || !settings.applyInSceneView && renderingData.cameraData.isSceneViewCamera) return;
        if (renderingData.cameraData.isPreviewCamera) return;
        if (_effectMaterial == null) return;

        _fullScreenPass.Setup(_effectMaterial, _requiresColor, _injectedBeforeTransparents, "Flat Kit Fog",
            renderingData);
        renderer.EnqueuePass(_fullScreenPass);
    }

    // Re-generate LUT textures when unity disposes them on scene save. 
#if UNITY_EDITOR
    public override void OnCameraPreCull(ScriptableRenderer renderer, in CameraData cameraData) {
        base.OnCameraPreCull(renderer, in cameraData);
        if (settings == null || _effectMaterial == null) return;
        if (settings.useDistance && !_effectMaterial.GetTexture(distanceLut)) UpdateDistanceLut();
        if (settings.useHeight && !_effectMaterial.GetTexture(heightLut)) UpdateHeightLut();
    }
#endif

    protected override void Dispose(bool disposing) {
        _fullScreenPass?.Dispose();
    }

    private void SetMaterialProperties() {
        if (_effectMaterial == null) return;

        RendererFeatureUtils.SetKeyword(_effectMaterial, UseDistanceFog, settings.useDistance);
        if (settings.useDistance) {
            UpdateDistanceLut();
            _effectMaterial.SetFloat(near, settings.near);
            _effectMaterial.SetFloat(far, settings.far);
            _effectMaterial.SetFloat(distanceFogIntensity, settings.distanceFogIntensity);
        }

        RendererFeatureUtils.SetKeyword(_effectMaterial, UseHeightFog, settings.useHeight);
        if (settings.useHeight) {
            UpdateHeightLut();
            _effectMaterial.SetFloat(lowWorldY, settings.low);
            _effectMaterial.SetFloat(highWorldY, settings.high);
            _effectMaterial.SetFloat(heightFogIntensity, settings.heightFogIntensity);
            _effectMaterial.SetFloat(distanceHeightBlend, settings.distanceHeightBlend);
        }

        RendererFeatureUtils.SetKeyword(_effectMaterial, CameraRelativePosition, settings.cameraRelativePosition);
    }

    private void UpdateDistanceLut() {
        if (settings.distanceGradient == null) return;

        const int width = 256;
        const int height = 1;
        if (_lutDepth == null) {
            _lutDepth = new Texture2D(width, height, TextureFormat.RGBA32, /*mipChain=*/false) {
                wrapMode = TextureWrapMode.Clamp,
                hideFlags = HideFlags.HideAndDontSave,
                filterMode = FilterMode.Bilinear
            };
        }

        for (float x = 0; x < width; x++) {
            Color color = settings.distanceGradient.Evaluate(x / (width - 1));
            for (float y = 0; y < height; y++) {
                _lutDepth.SetPixel(Mathf.CeilToInt(x), Mathf.CeilToInt(y), color);
            }
        }

        _lutDepth.Apply();
        _effectMaterial.SetTexture(distanceLut, _lutDepth);
    }

    private void UpdateHeightLut() {
        if (settings.heightGradient == null) return;

        const int width = 256;
        const int height = 1;
        if (_lutHeight == null) {
            _lutHeight = new Texture2D(width, height, TextureFormat.RGBA32, /*mipChain=*/false) {
                wrapMode = TextureWrapMode.Clamp,
                hideFlags = HideFlags.HideAndDontSave,
                filterMode = FilterMode.Bilinear
            };
        }

        for (float x = 0; x < width; x++) {
            Color color = settings.heightGradient.Evaluate(x / (width - 1));
            for (float y = 0; y < height; y++) {
                _lutHeight.SetPixel(Mathf.CeilToInt(x), Mathf.CeilToInt(y), color);
            }
        }

        _lutHeight.Apply();
        _effectMaterial.SetTexture(heightLut, _lutHeight);
    }
}
}
#else
namespace FlatKit {
public class FlatKitFog : ScriptableRendererFeature {
    [Tooltip("To create new settings use 'Create > FlatKit > Fog Settings'.")]
    public FogSettings settings;

    [SerializeField, HideInInspector]
    private Material _effectMaterial;

    private BlitTexturePass _blitTexturePass;

    private Texture2D _lutDepth;
    private Texture2D _lutHeight;

    private static readonly string FogShaderName = "Hidden/FlatKit/FogFilter";
    private static readonly int DistanceLut = Shader.PropertyToID("_DistanceLUT");
    private static readonly int Near = Shader.PropertyToID("_Near");
    private static readonly int Far = Shader.PropertyToID("_Far");
    private static readonly int UseDistanceFog = Shader.PropertyToID("_UseDistanceFog");
    private static readonly int UseDistanceFogOnSky = Shader.PropertyToID("_UseDistanceFogOnSky");
    private static readonly int DistanceFogIntensity = Shader.PropertyToID("_DistanceFogIntensity");
    private static readonly int HeightLut = Shader.PropertyToID("_HeightLUT");
    private static readonly int LowWorldY = Shader.PropertyToID("_LowWorldY");
    private static readonly int HighWorldY = Shader.PropertyToID("_HighWorldY");
    private static readonly int UseHeightFog = Shader.PropertyToID("_UseHeightFog");
    private static readonly int UseHeightFogOnSky = Shader.PropertyToID("_UseHeightFogOnSky");
    private static readonly int HeightFogIntensity = Shader.PropertyToID("_HeightFogIntensity");
    private static readonly int DistanceHeightBlend = Shader.PropertyToID("_DistanceHeightBlend");

    /// <summary>
    /// Access the runtime effect material to override fog parameters at runtime.
    /// This enables minimal, code-driven tweaks without changing the Settings asset.
    ///
    /// Shader (legacy path): "Hidden/FlatKit/FogFilter"
    ///
    /// Common shader properties you can set:
    /// - Textures: _DistanceLUT (Texture2D), _HeightLUT (Texture2D)
    /// - Floats: _Near, _Far, _LowWorldY, _HighWorldY,
    ///           _UseDistanceFog (0/1), _UseDistanceFogOnSky (0/1), _DistanceFogIntensity,
    ///           _UseHeightFog (0/1), _UseHeightFogOnSky (0/1), _HeightFogIntensity, _DistanceHeightBlend
    ///
    /// Notes:
    /// - If you supply your own LUTs, set _DistanceLUT/_HeightLUT directly.
    /// - Settings asset changes (via its inspector) can overwrite your values when applied.
    /// </summary>
    public Material EffectMaterial => _effectMaterial;

    public override void Create() {
#if UNITY_EDITOR
        if (_effectMaterial == null) {
            SubAssetMaterial.AlwaysInclude(BlitTexturePass.CopyEffectShaderName);
            SubAssetMaterial.AlwaysInclude(FogShaderName);
        }
#endif

        if (settings == null) {
            return;
        }

        if (!CreateMaterials()) {
            return;
        }

        SetMaterialProperties();

        _blitTexturePass = new BlitTexturePass(_effectMaterial, useDepth: true, useNormals: false, useColor: false);
    }

    protected override void Dispose(bool disposing) {
        _blitTexturePass.Dispose();
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData) {
#if UNITY_EDITOR
        if (renderingData.cameraData.isPreviewCamera) return;
        if (!settings.applyInSceneView && renderingData.cameraData.cameraType == CameraType.SceneView) return;
#endif

        SetMaterialProperties();

        _blitTexturePass.Setup(renderingData);
        _blitTexturePass.renderPassEvent = settings.renderEvent;

        renderer.EnqueuePass(_blitTexturePass);
    }

    private bool CreateMaterials() {
        if (_effectMaterial == null) {
            var effectShader = Shader.Find(FogShaderName);
            var blitShader = Shader.Find(BlitTexturePass.CopyEffectShaderName);
            if (effectShader == null || blitShader == null) return false;
            _effectMaterial = UnityEngine.Rendering.CoreUtils.CreateEngineMaterial(effectShader);
        }

        return _effectMaterial != null;
    }

    private void SetMaterialProperties() {
        if (_effectMaterial == null) {
            return;
        }

        UpdateDistanceLut();
        _effectMaterial.SetTexture(DistanceLut, _lutDepth);
        _effectMaterial.SetFloat(Near, settings.near);
        _effectMaterial.SetFloat(Far, settings.far);
        _effectMaterial.SetFloat(UseDistanceFog, settings.useDistance ? 1f : 0f);
        _effectMaterial.SetFloat(UseDistanceFogOnSky, settings.useDistanceFogOnSky ? 1f : 0f);
        _effectMaterial.SetFloat(DistanceFogIntensity, settings.distanceFogIntensity);

        UpdateHeightLut();
        _effectMaterial.SetTexture(HeightLut, _lutHeight);
        _effectMaterial.SetFloat(LowWorldY, settings.low);
        _effectMaterial.SetFloat(HighWorldY, settings.high);
        _effectMaterial.SetFloat(UseHeightFog, settings.useHeight ? 1f : 0f);
        _effectMaterial.SetFloat(UseHeightFogOnSky, settings.useHeightFogOnSky ? 1f : 0f);
        _effectMaterial.SetFloat(HeightFogIntensity, settings.heightFogIntensity);
        _effectMaterial.SetFloat(DistanceHeightBlend, settings.distanceHeightBlend);
    }

    private void UpdateDistanceLut() {
        if (settings.distanceGradient == null) return;

        if (_lutDepth != null) {
            DestroyImmediate(_lutDepth);
        }

        const int width = 256;
        const int height = 1;
        _lutDepth = new Texture2D(width, height, TextureFormat.RGBA32, /*mipChain=*/false) {
            wrapMode = TextureWrapMode.Clamp,
            hideFlags = HideFlags.HideAndDontSave,
            filterMode = FilterMode.Bilinear
        };

        //22b5f7ed-989d-49d1-90d9-c62d76c3081a

        for (float x = 0; x < width; x++) {
            Color color = settings.distanceGradient.Evaluate(x / (width - 1));
            for (float y = 0; y < height; y++) {
                _lutDepth.SetPixel(Mathf.CeilToInt(x), Mathf.CeilToInt(y), color);
            }
        }

        _lutDepth.Apply();
    }

    private void UpdateHeightLut() {
        if (settings.heightGradient == null) return;

        if (_lutHeight != null) {
            DestroyImmediate(_lutHeight);
        }

        const int width = 256;
        const int height = 1;
        _lutHeight = new Texture2D(width, height, TextureFormat.RGBA32, /*mipChain=*/false) {
            wrapMode = TextureWrapMode.Clamp,
            hideFlags = HideFlags.HideAndDontSave,
            filterMode = FilterMode.Bilinear
        };

        for (float x = 0; x < width; x++) {
            Color color = settings.heightGradient.Evaluate(x / (width - 1));
            for (float y = 0; y < height; y++) {
                _lutHeight.SetPixel(Mathf.CeilToInt(x), Mathf.CeilToInt(y), color);
            }
        }

        _lutHeight.Apply();
    }
}
}
#endif