﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using UnityEngine.Rendering.HighDefinition;

namespace ScreenSpaceCavityCurvature.HighDefinition
{
    [ExecuteInEditMode, VolumeComponentMenu("SreenSpaceCavityCurvature")]
    public class SSCC : CustomPostProcessVolumeComponent, IPostProcessComponent
    {
        public enum DebugMode { Disabled, EffectOnly, ViewNormals }
        public enum PerPixelNormals { DeferredGBuffer, ReconstructedFromDepth }
        public enum CavitySamples { Low6, Medium8, High12 }

        public Shader shader;

        [Serializable] public sealed class DebugModeParameter : VolumeParameter<DebugMode> { public DebugModeParameter(DebugMode value, bool overrideState = false) : base(value, overrideState) { } }
        [Serializable] public sealed class GetNormalsFromParameter : VolumeParameter<PerPixelNormals> { public GetNormalsFromParameter(PerPixelNormals value, bool overrideState = false) : base(value, overrideState) { } }
        [Serializable] public sealed class CavitySamplesParameter : VolumeParameter<CavitySamples> { public CavitySamplesParameter(CavitySamples value, bool overrideState = false) : base(value, overrideState) { } }

        //
        [Tooltip("Lerps the whole effect from 0 to 1.")] public ClampedFloatParameter effectIntensity = new ClampedFloatParameter(1f, 0f, 1f);
        [Tooltip("Divides effect intensity by (depth * distanceFade).\nZero means effect doesn't fade with distance.")] public ClampedFloatParameter distanceFade = new ClampedFloatParameter(0f, 0f, 1f);

        [Space(6)]

        [Tooltip("The radius of curvature calculations in pixels.")] public ClampedIntParameter curvaturePixelRadius = new ClampedIntParameter(2, 0, 4);
        [Tooltip("How bright does curvature get.")] public ClampedFloatParameter curvatureBrights = new ClampedFloatParameter(2f, 0f, 5f);
        [Tooltip("How dark does curvature get.")] public ClampedFloatParameter curvatureDarks = new ClampedFloatParameter(3f, 0f, 5f);

        [Space(6)]

        [Tooltip("The amount of samples used for cavity calculation.")] public CavitySamplesParameter cavitySamples = new CavitySamplesParameter(CavitySamples.Low6);
        [Tooltip("True: uses pow() blending to make colors more saturated in bright/dark areas of cavity.\n\nFalse: uses additive blending.")] public BoolParameter saturateCavity = new BoolParameter(true);
        [Tooltip("The radius of cavity calculations in world units.")] public ClampedFloatParameter cavityRadius = new ClampedFloatParameter(0.25f, 0f, 0.5f);
        [Tooltip("How bright does cavity get.")] public ClampedFloatParameter cavityBrights = new ClampedFloatParameter(3f, 0f, 5f);
        [Tooltip("How dark does cavity get.")] public ClampedFloatParameter cavityDarks = new ClampedFloatParameter(2f, 0f, 5f);

        [Space(6)]

        [Tooltip("Where to get normals from.")] public GetNormalsFromParameter normalsSource = new GetNormalsFromParameter(PerPixelNormals.DeferredGBuffer);
        [Tooltip("May be useful to check what objects contribute normals, as objects that do not contribute their normals will not contribute to the effect.")] public DebugModeParameter debugMode = new DebugModeParameter(DebugMode.Disabled);
        //

        void CheckParameters()
        {
            
        }

        
        public override CustomPostProcessInjectionPoint injectionPoint => CustomPostProcessInjectionPoint.AfterOpaqueAndSky;

        //public override bool visibleInSceneView => true;

        static class Pass
        {
            public const int Copy = 0;
            public const int Composite = 1;
        }

        static class ShaderProperties
        {
            public static int mainTex = Shader.PropertyToID("_MainTex");
            public static int tempTex = Shader.PropertyToID("_TempTex");
            public static int uvTransform = Shader.PropertyToID("_UVTransform");
            public static int inputTexelSize = Shader.PropertyToID("_Input_TexelSize");
            public static int uvToView = Shader.PropertyToID("_UVToView");
            public static int worldToCameraMatrix = Shader.PropertyToID("_WorldToCameraMatrix");

            public static int effectIntensity = Shader.PropertyToID("_EffectIntensity");
            public static int distanceFade = Shader.PropertyToID("_DistanceFade");

            public static int curvaturePixelRadius = Shader.PropertyToID("_CurvaturePixelRadius");
            public static int curvatureRidge = Shader.PropertyToID("_CurvatureBrights");
            public static int curvatureValley = Shader.PropertyToID("_CurvatureDarks");

            public static int cavityWorldRadius = Shader.PropertyToID("_CavityWorldRadius");
            public static int cavityRidge = Shader.PropertyToID("_CavityBrights");
            public static int cavityValley = Shader.PropertyToID("_CavityDarks");
        }
        
        Material material { get; set; }
        HDCamera cameraData { get; set; }
        int width { get; set; }
        int height { get; set; }

        public override void Setup()
        {
            if (!FindShader())
                return;
        }
        
        //public bool IsActive() => true;
        public bool IsActive() => effectIntensity.value > 0f;

        public override void Render(CommandBuffer cmd, HDCamera hdCamera, RTHandle source, RTHandle destination)
        {
            if (material == null) material = CoreUtils.CreateEngineMaterial(shader);
            if (material == null)
            {
                Debug.LogError("SSCC material has not been correctly initialized...");
                return;
            }

            if (!IsActive()) return;
            
            width = hdCamera.actualWidth;
            height = hdCamera.actualHeight;
            cameraData = hdCamera;

            CheckParameters();
            UpdateMaterialProperties();
            UpdateShaderKeywords();

            // Composite
            DrawFullScreen(cmd, source, destination, material, Pass.Composite);
        }

        public override void Cleanup()
        {
            CoreUtils.Destroy(material);
        }

        private bool FindShader()
        {
            if (!SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.Depth))
            {
                Debug.LogWarning("SSCC shader is not supported on this platform.");
                return false;
            }

            if (shader == null) shader = Shader.Find("Hidden/High Definition Render Pipeline/SSCC");
            if (shader == null)
            {
                Debug.LogError("SSCC shader was not found...");
                return false;
            }

            return true;
        }
        
        void UpdateMaterialProperties()
        {
            float tanHalfFovY = Mathf.Tan(0.5f * cameraData.camera.fieldOfView * Mathf.Deg2Rad);
            float invFocalLenX = 1.0f / (1.0f / tanHalfFovY * (height / (float)width));
            float invFocalLenY = 1.0f / (1.0f / tanHalfFovY);

            material.SetVector(ShaderProperties.inputTexelSize, new Vector4(1f / width, 1f / height, width, height));
            material.SetVector(ShaderProperties.uvToView, new Vector4(2.0f * invFocalLenX, -2.0f * invFocalLenY, -1.0f * invFocalLenX, 1.0f * invFocalLenY));
            //material.SetMatrix(ShaderProperties.worldToCameraMatrix, cameraData.camera.worldToCameraMatrix);

            material.SetFloat(ShaderProperties.effectIntensity, effectIntensity.value);
            material.SetFloat(ShaderProperties.distanceFade, distanceFade.value);

            material.SetFloat(ShaderProperties.curvaturePixelRadius, new float[] { 0f, 0.5f, 1f, 1.5f, 2.5f }[curvaturePixelRadius.value]);
            material.SetFloat(ShaderProperties.curvatureRidge, curvatureBrights.value == 0f ? 999f : (5f - curvatureBrights.value));
            material.SetFloat(ShaderProperties.curvatureValley, curvatureDarks.value == 0f ? 999f : (5f - curvatureDarks.value));

            material.SetFloat(ShaderProperties.cavityWorldRadius, cavityRadius.value);
            material.SetFloat(ShaderProperties.cavityRidge, cavityBrights.value * 2f);
            material.SetFloat(ShaderProperties.cavityValley, cavityDarks.value * 2f);
        }

        void UpdateShaderKeywords()
        {
            material.shaderKeywords = new string[]
            {
                cameraData.camera.orthographic ? "ORTHOGRAPHIC_PROJECTION" :  "__",
                debugMode.value == DebugMode.EffectOnly ? "DEBUG_EFFECT" : debugMode.value == DebugMode.ViewNormals ? "DEBUG_NORMALS" : "__",
                normalsSource.value == PerPixelNormals.ReconstructedFromDepth ? "NORMALS_RECONSTRUCT" : "__",
                cavitySamples.value == CavitySamples.Low6 ? "CAVITY_SAMPLES_6" : cavitySamples.value == CavitySamples.Medium8 ? "CAVITY_SAMPLES_8" : cavitySamples.value == CavitySamples.High12 ? "CAVITY_SAMPLES_12" : "",
                saturateCavity.value ? "SATURATE_CAVITY" : "__"
            };
        }

        private void DrawFullScreen(CommandBuffer cmd, RTHandle source, RTHandle destination, Material material, int pass = 0)
        {
            cmd.SetGlobalTexture(ShaderProperties.mainTex, source);
            HDUtils.DrawFullScreen(cmd, material, destination, shaderPassId: pass);
        }

    }
}
