using UnityEngine;
using UnityEditor.AssetImporters;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace UnityEditor.Rendering.Universal
{
    class AutodeskInteractiveMaterialImport : AssetPostprocessor
    {
        static readonly uint k_Version = 2;
        static readonly int k_Order = -970;
        public override uint GetVersion()
        {
            return k_Version;
        }

        public override int GetPostprocessOrder()
        {
            return k_Order;
        }

        public void OnPreprocessMaterialDescription(MaterialDescription description, Material material, AnimationClip[] clips)
        {
            var pipelineAsset = GraphicsSettings.currentRenderPipeline;
            if (!pipelineAsset || pipelineAsset.GetType() != typeof(UniversalRenderPipelineAsset))
                return;

            if (IsAutodeskInteractiveMaterial(description))
            {
                float floatProperty;
                Vector4 vectorProperty;
                TexturePropertyDescription textureProperty;

                bool isMasked = description.TryGetProperty("mask_threshold", out floatProperty);
                bool isTransparent = description.TryGetProperty("opacity", out floatProperty);

                Shader shader;
                if (isMasked)
                    shader = GraphicsSettings.currentRenderPipeline.autodeskInteractiveMaskedShader;
                else if (isTransparent)
                    shader = GraphicsSettings.currentRenderPipeline.autodeskInteractiveTransparentShader;
                else
                    shader = GraphicsSettings.currentRenderPipeline.autodeskInteractiveShader;

                if (shader == null)
                    return;

                material.shader = shader;
                foreach (var clip in clips)
                {
                    clip.ClearCurves();
                }

                if (description.TryGetProperty("base_color", out vectorProperty))
                    material.SetColor("_Color", vectorProperty);
                if (description.TryGetProperty("emissive", out vectorProperty))
                    material.SetColor("_EmissionColor", vectorProperty);

                if (description.TryGetProperty("roughness", out floatProperty))
                    material.SetFloat("_Roughness", floatProperty);

                if (description.TryGetProperty("metallic", out floatProperty))
                    material.SetFloat("_Metallic", floatProperty);

                if (description.TryGetProperty("uvTransform", out vectorProperty))
                {
                    material.SetVector("_UvOffset", new Vector4(vectorProperty.x, vectorProperty.y, .0f, .0f));
                    material.SetVector("_UvTiling", new Vector4(vectorProperty.w, vectorProperty.z, .0f, .0f));
                }

                if (description.TryGetProperty("TEX_color_map", out textureProperty))
                {
                    material.SetTexture("_MainTex", textureProperty.texture);
                    material.SetFloat("_UseColorMap", 1.0f);
                }
                else
                {
                    material.SetFloat("_UseColorMap", 0.0f);
                }

                if (description.TryGetProperty("TEX_normal_map", out textureProperty))
                {
                    material.SetTexture("_BumpMap", textureProperty.texture);
                    material.SetFloat("_UseNormalMap", 1.0f);
                }
                else
                {
                    material.SetFloat("_UseNormalMap", 0.0f);
                }

                if (description.TryGetProperty("TEX_roughness_map", out textureProperty))
                {
                    material.SetTexture("RoughnessMap", textureProperty.texture);
                    material.SetFloat("_UseRoughnessMap", 1.0f);
                }
                else
                {
                    material.SetFloat("_UseRoughnessMap", 0.0f);
                }

                if (description.TryGetProperty("TEX_metallic_map", out textureProperty))
                {
                    material.SetTexture("_MetallicMap", textureProperty.texture);
                    material.SetFloat("_UseMetallicMap", 1.0f);
                }
                else
                {
                    material.SetFloat("_UseMetallicMap", 0.0f);
                }

                if (description.TryGetProperty("TEX_emissive_map", out textureProperty))
                {
                    material.SetTexture("_EmissionMap", textureProperty.texture);
                    material.SetFloat("_UseEmissiveMap", 1.0f);
                }
                else
                {
                    material.SetFloat("_UseEmissiveMap", 0.0f);
                }

                if (description.TryGetProperty("hasTransparencyTexture", out floatProperty))
                    material.SetFloat("_UseOpacityMap", floatProperty);

                if (description.TryGetProperty("transparencyMaskThreshold", out floatProperty))
                    material.SetFloat("_OpacityThreshold", floatProperty);

                if (description.TryGetProperty("TEX_ao_map", out textureProperty))
                {
                    var tex = AssetDatabase.LoadAssetAtPath<Texture>(textureProperty.relativePath);
                    material.SetTexture("AoMap", tex);
                    material.SetFloat("UseAoMap", 1.0f);
                }
                else
                {
                    material.SetFloat("UseAoMap", 0.0f);
                }

                RemapColorCurves(description, clips, "base_color", "_Color");
                RemapCurve(description, clips, "mask_threshold", "_Cutoff");
                RemapCurve(description, clips, "metallic", "_Metallic");
                RemapCurve(description, clips, "roughness", "_Glossiness");

                for (int i = 0; i < clips.Length; i++)
                {
                    if (description.HasAnimationCurveInClip(clips[i].name, "uv_scale.x") || description.HasAnimationCurveInClip(clips[i].name, "uv_scale.y"))
                    {
                        AnimationCurve curve;
                        if (description.TryGetAnimationCurve(clips[i].name, "uv_scale.x", out curve))
                            clips[i].SetCurve("", typeof(Material), "_UvTiling.x", curve);
                        else
                            clips[i].SetCurve("", typeof(Material), "_UvTiling.x", AnimationCurve.Constant(0.0f, 1.0f, 1.0f));

                        if (description.TryGetAnimationCurve(clips[i].name, "uv_scale.y", out curve))
                            clips[i].SetCurve("", typeof(Material), "_UvTiling.y", curve);
                        else
                            clips[i].SetCurve("", typeof(Material), "_UvTiling.y", AnimationCurve.Constant(0.0f, 1.0f, 1.0f));
                    }

                    if (description.HasAnimationCurveInClip(clips[i].name, "uv_offset.x") || description.HasAnimationCurveInClip(clips[i].name, "uv_offset.y"))
                    {
                        AnimationCurve curve;
                        if (description.TryGetAnimationCurve(clips[i].name, "uv_offset.x", out curve))
                            clips[i].SetCurve("", typeof(Material), "_UvOffset.x", curve);
                        else
                            clips[i].SetCurve("", typeof(Material), "_UvOffset.x", AnimationCurve.Constant(0.0f, 1.0f, 0.0f));

                        if (description.TryGetAnimationCurve(clips[i].name, "uv_offset.y", out curve))
                        {
                            ConvertKeys(curve, ConvertFloatNegate);
                            clips[i].SetCurve("", typeof(Material), "_UvOffset.y", curve);
                        }
                        else
                            clips[i].SetCurve("", typeof(Material), "_UvOffset.y", AnimationCurve.Constant(0.0f, 1.0f, 0.0f));
                    }
                }

                if (description.HasAnimationCurve("emissive_intensity"))
                {
                    Vector4 emissiveColor;
                    description.TryGetProperty("emissive", out emissiveColor);

                    for (int i = 0; i < clips.Length; i++)
                    {
                        AnimationCurve curve;
                        description.TryGetAnimationCurve(clips[i].name, "emissive_intensity", out curve);
                        // remap emissive intensity to emission color
                        clips[i].SetCurve("", typeof(Material), "_EmissionColor.r", curve);
                        clips[i].SetCurve("", typeof(Material), "_EmissionColor.g", curve);
                        clips[i].SetCurve("", typeof(Material), "_EmissionColor.b", curve);
                    }
                }
                else if (description.TryGetProperty("emissive", out vectorProperty))
                {
                    if (vectorProperty.x > 0.0f || vectorProperty.y > 0.0f || vectorProperty.z > 0.0f)
                    {
                        material.globalIlluminationFlags |= MaterialGlobalIlluminationFlags.RealtimeEmissive;
                        material.EnableKeyword("_EMISSION");
                    }

                    if (description.TryGetProperty("emissive_intensity", out floatProperty))
                    {
                        vectorProperty *= floatProperty;
                    }

                    material.SetColor("_EmissionColor", vectorProperty);


                    if (description.HasAnimationCurve("emissive.x"))
                    {
                        if (description.HasAnimationCurve("emissive_intensity"))
                        {
                            // combine color and intensity.
                            for (int i = 0; i < clips.Length; i++)
                            {
                                AnimationCurve curve;
                                AnimationCurve intensityCurve;
                                description.TryGetAnimationCurve(clips[i].name, "emissive_intensity", out intensityCurve);

                                description.TryGetAnimationCurve(clips[i].name, "emissive.x", out curve);
                                MultiplyCurves(curve, intensityCurve);
                                clips[i].SetCurve("", typeof(Material), "_EmissionColor.r", curve);

                                description.TryGetAnimationCurve(clips[i].name, "emissive.y", out curve);
                                MultiplyCurves(curve, intensityCurve);
                                clips[i].SetCurve("", typeof(Material), "_EmissionColor.g", curve);

                                description.TryGetAnimationCurve(clips[i].name, "emissive.z", out curve);
                                MultiplyCurves(curve, intensityCurve);
                                clips[i].SetCurve("", typeof(Material), "_EmissionColor.b", curve);
                            }
                        }
                        else
                        {
                            RemapColorCurves(description, clips, "emissive", "_EmissionColor");
                        }
                    }
                    else if (description.HasAnimationCurve("emissive_intensity"))
                    {
                        Vector4 emissiveColor;
                        description.TryGetProperty("emissive", out emissiveColor);

                        for (int i = 0; i < clips.Length; i++)
                        {
                            AnimationCurve curve;
                            description.TryGetAnimationCurve(clips[i].name, "emissive_intensity", out curve);
                            // remap emissive intensity to emission color
                            AnimationCurve curveR = new AnimationCurve();
                            ConvertAndCopyKeys(curveR, curve, value => ConvertFloatMultiply(emissiveColor.x, value));
                            clips[i].SetCurve("", typeof(Material), "_EmissionColor.r", curveR);

                            AnimationCurve curveG = new AnimationCurve();
                            ConvertAndCopyKeys(curveG, curve, value => ConvertFloatMultiply(emissiveColor.y, value));
                            clips[i].SetCurve("", typeof(Material), "_EmissionColor.g", curveG);

                            AnimationCurve curveB = new AnimationCurve();
                            ConvertAndCopyKeys(curveB, curve, value => ConvertFloatMultiply(emissiveColor.z, value));
                            clips[i].SetCurve("", typeof(Material), "_EmissionColor.b", curveB);
                        }
                    }
                }
            }
        }

        static bool IsAutodeskInteractiveMaterial(MaterialDescription description)
        {
            return description.TryGetProperty("renderAPI", out string stringValue) && stringValue == "SFX_PBS_SHADER";
        }

        static void ConvertKeys(AnimationCurve curve, System.Func<float, float> convertionDelegate)
        {
            Keyframe[] keyframes = curve.keys;
            for (int i = 0; i < keyframes.Length; i++)
            {
                keyframes[i].value = convertionDelegate(keyframes[i].value);
            }
            curve.keys = keyframes;
        }

        static void ConvertAndCopyKeys(AnimationCurve curveDest, AnimationCurve curveSource, System.Func<float, float> convertionDelegate)
        {
            for (int i = 0; i < curveSource.keys.Length; i++)
            {
                var sourceKey = curveSource.keys[i];
                curveDest.AddKey(new Keyframe(sourceKey.time, convertionDelegate(sourceKey.value), sourceKey.inTangent, sourceKey.outTangent, sourceKey.inWeight, sourceKey.outWeight));
            }
        }

        static float ConvertFloatNegate(float value)
        {
            return -value;
        }

        static float ConvertFloatMultiply(float value, float multiplier)
        {
            return value * multiplier;
        }

        static void MultiplyCurves(AnimationCurve curve, AnimationCurve curveMultiplier)
        {
            Keyframe[] keyframes = curve.keys;
            for (int i = 0; i < keyframes.Length; i++)
            {
                keyframes[i].value *= curveMultiplier.Evaluate(keyframes[i].time);
            }
            curve.keys = keyframes;
        }

        static void RemapCurve(MaterialDescription description, AnimationClip[] clips, string originalPropertyName, string newPropertyName)
        {
            AnimationCurve curve;
            for (int i = 0; i < clips.Length; i++)
            {
                if (description.TryGetAnimationCurve(clips[i].name, originalPropertyName, out curve))
                {
                    clips[i].SetCurve("", typeof(Material), newPropertyName, curve);
                }
            }
        }

        static void RemapColorCurves(MaterialDescription description, AnimationClip[] clips, string originalPropertyName, string newPropertyName)
        {
            AnimationCurve curve;
            for (int i = 0; i < clips.Length; i++)
            {
                if (description.TryGetAnimationCurve(clips[i].name, originalPropertyName + ".x", out curve))
                {
                    clips[i].SetCurve("", typeof(Material), newPropertyName + ".r", curve);
                }

                if (description.TryGetAnimationCurve(clips[i].name, originalPropertyName + ".y", out curve))
                {
                    clips[i].SetCurve("", typeof(Material), newPropertyName + ".g", curve);
                }

                if (description.TryGetAnimationCurve(clips[i].name, originalPropertyName + ".z", out curve))
                {
                    clips[i].SetCurve("", typeof(Material), newPropertyName + ".b", curve);
                }
            }
        }
    }
}