﻿using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Mochie;

namespace Mochie {

    public class TakenEditor : ShaderGUI {

        public static Dictionary<Material, Toggles> foldouts = new Dictionary<Material, Toggles>();
        Toggles toggles = new Toggles(new string[] {
                "Base",
                "Lighting",
                "Rim",
                "Emission",
                "Gradient",
                "Noise Patches",
                "Dissolve",
                "Outline",
        }, 1);

        string versionLabel = "v1.2";

        // Avatar Props
        MaterialProperty _BlendMode = null;
        MaterialProperty _QueueOffset = null;
        MaterialProperty _ZWrite = null;
        MaterialProperty _Cutoff = null;
        MaterialProperty _Color = null;
        MaterialProperty _Invert = null;
        MaterialProperty _Smoothstep = null;
        MaterialProperty _Culling = null;
        MaterialProperty _MainTex = null;
        MaterialProperty _BumpScale = null;
        MaterialProperty _BumpMap = null;

        MaterialProperty _EnableLighting = null;
        MaterialProperty _EnableSH = null;
        MaterialProperty _EnableReflections = null;
        MaterialProperty _Metallic = null;
        MaterialProperty _Roughness = null;
        MaterialProperty _ReflectionStr = null;
        MaterialProperty _MetallicMap = null;
        MaterialProperty _RoughnessMap = null;
        MaterialProperty _ReflectionMask = null;

        MaterialProperty _EnableRim = null;
        MaterialProperty _RimBrightness = null;
        MaterialProperty _RimWidth = null;
        MaterialProperty _RimEdge = null;
        MaterialProperty _RimGradMask = null;
        MaterialProperty _RimMask = null;

        MaterialProperty _EmissStr = null;
        MaterialProperty _EmissPow = null;
        MaterialProperty _EmissGradMasking = null;
        MaterialProperty _EmissTex = null;
        MaterialProperty _EmissGradMask = null;
        MaterialProperty _EmissInvert = null;

        MaterialProperty _EnableGradient = null;
        MaterialProperty _GradientInvert = null;
        MaterialProperty _GradientAxis = null;
        MaterialProperty _GradientNoiseScale = null;
        MaterialProperty _GradientBrightness = null;
        MaterialProperty _GradientHeightMax = null;
        MaterialProperty _GradientHeightMin = null;
        MaterialProperty _GradientSpeed = null;
        MaterialProperty _GradientContrast = null;
        MaterialProperty _GradientMask = null;

        MaterialProperty _EnablePatches = null;
        MaterialProperty _NoiseScale = null;
        MaterialProperty _NoiseBrightness = null;
        MaterialProperty _NoiseCutoff = null;
        MaterialProperty _NoiseSmooth = null;
        MaterialProperty _NoiseMask = null;

        MaterialProperty _EnableDissolve = null;
        MaterialProperty _DissolveTex = null;
        MaterialProperty _DissolveAmt = null;
        MaterialProperty _DissolveRimBrightness = null;
        MaterialProperty _DissolveRimWidth = null;

        MaterialProperty _EnableOutline = null;
        MaterialProperty _InvertedOutline = null;
        MaterialProperty _Thickness = null;
        MaterialProperty _OutlineMask = null;
        MaterialProperty _StencilOutline = null;


        BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;

        bool m_FirstTimeApply = true;

        public override void OnGUI(MaterialEditor me, MaterialProperty[] props) {
            Material mat = (Material)me.target;
            mat.DisableKeyword("_");
            if (m_FirstTimeApply) {
                m_FirstTimeApply = false;
            }

            // Find properties
            foreach (var property in GetType().GetFields(bindingFlags)){
                if (property.FieldType == typeof(MaterialProperty)){
                    property.SetValue(this, FindProperty(property.Name, props));
                }
            }

            // Check name of shader to determine if certain properties should be displayed
            bool isTransparent = _BlendMode.floatValue >= 3;
            bool isCutout = _BlendMode.floatValue > 0 && _BlendMode.floatValue < 3;

            // Add mat to foldout dictionary if it isn't in there yet
            if (!foldouts.ContainsKey(mat))
                foldouts.Add(mat, toggles);

            // Return here to reduce editor overhead if it's not visible
            if (!me.isVisible)
                return;

            if (mat.GetInt("_MaterialResetCheck") == 0){
                mat.SetInt("_MaterialResetCheck", 1);
                ApplyMaterialSettings(mat);
                SetBlendMode(mat);
            }

            MGUI.DoHeader("TAKEN");

            EditorGUI.BeginChangeCheck(); {

                bool baseToggle = Foldouts.DoFoldout(foldouts, mat, "Base", 1, Foldouts.Style.StandardButton);
                if (Foldouts.DoFoldoutButton(MGUI.collapseLabel, 11)) Toggles.CollapseFoldouts(mat, foldouts, 1);
                if (baseToggle) {
                    MGUI.PropertyGroupParent(()=>{
                        MGUI.PropertyGroup(()=>{
                            me.ShaderProperty(_BlendMode, "Blending Mode");
                            if (_BlendMode.floatValue == 4)
                                me.ShaderProperty(_ZWrite, "ZWrite");
                            me.ShaderProperty(_Culling, "Culling");
                            _QueueOffset.floatValue = (int)_QueueOffset.floatValue;
                            me.ShaderProperty(_QueueOffset, Tips.queueOffset);
                            MGUI.SpaceN1();
                            MGUI.DummyProperty("Render Queue:", mat.renderQueue.ToString());
                        });
                        MGUI.PropertyGroup(()=>{
                            me.ShaderProperty(_Color, Tips.globalTint);
                            if (_BlendMode.floatValue == 1){
                                me.ShaderProperty(_Cutoff, "Cutout");
                            }
                            me.ShaderProperty(_Smoothstep, "Smoothstep");
                        });
                        MGUI.PropertyGroup(()=>{
                            me.TexturePropertySingleLine(new GUIContent("Base Color"), _MainTex, _Invert);
                            MGUI.TexPropLabel("Invert", 95, false);
                            me.TexturePropertySingleLine(Tips.emissionAO, _EmissTex, _EmissInvert);
                            MGUI.TexPropLabel("Invert", 95, false);
                            me.TexturePropertySingleLine(Tips.normalMapText, _BumpMap, _BumpScale);
                            MGUI.TextureSO(me, _MainTex);
                        });
                    });
                }

                if (Foldouts.DoFoldout(foldouts, mat, me, _EnableLighting, "Lighting", Foldouts.Style.StandardToggle)){
                    MGUI.PropertyGroupParent(()=>{
                        MGUI.ToggleGroup(_EnableLighting.floatValue == 0);
                        MGUI.PropertyGroup(()=>{
                            me.ShaderProperty(_EnableSH, Tips.shStr);
                            me.ShaderProperty(_EnableReflections, "Reflections");
                        });
                        MGUI.ToggleGroup(_EnableReflections.floatValue == 0);
                        MGUI.PropertyGroup(()=>{
                            me.TexturePropertySingleLine(Tips.metallicText, _MetallicMap, _Metallic);
                            me.TexturePropertySingleLine(Tips.roughnessTexLabel, _RoughnessMap, _Roughness);
                            me.TexturePropertySingleLine(new GUIContent("Reflections"), _ReflectionMask, _ReflectionStr);
                        });
                        MGUI.ToggleGroupEnd();
                        MGUI.ToggleGroupEnd();
                    });
                }

                if (Foldouts.DoFoldout(foldouts, mat, me, _EnableRim, "Rim", Foldouts.Style.StandardToggle)){
                    MGUI.PropertyGroupParent(()=>{
                        MGUI.ToggleGroup(_EnableRim.floatValue == 0);
                        MGUI.PropertyGroup(()=>{
                            me.ShaderProperty(_RimBrightness, "Strength");
                            me.ShaderProperty(_RimWidth, "Width");
                            me.ShaderProperty(_RimEdge, "Sharpness");
                            me.ShaderProperty(_RimGradMask, Tips.gradientRestriction);		
                            me.TexturePropertySingleLine(new GUIContent("Mask"), _RimMask);
                        });
                        MGUI.ToggleGroupEnd();
                    });
                }

                if (Foldouts.DoFoldout(foldouts, mat, "Emission", Foldouts.Style.Standard)){
                    MGUI.PropertyGroupParent(()=>{
                        MGUI.PropertyGroup(()=>{
                            me.ShaderProperty(_EmissStr, "Strength");
                            me.ShaderProperty(_EmissPow, "Exponent");
                            me.ShaderProperty(_EmissGradMasking, Tips.emissionGradRestrict);
                            me.TexturePropertySingleLine(Tips.restrictionMask, _EmissGradMask);
                        });
                    });
                }

                if (Foldouts.DoFoldout(foldouts, mat, me, _EnableGradient, "Gradient", Foldouts.Style.StandardToggle)){
                    MGUI.PropertyGroupParent(()=>{
                        MGUI.ToggleGroup(_EnableGradient.floatValue == 0);
                        MGUI.PropertyGroup(()=>{
                            me.ShaderProperty(_GradientInvert, "Invert Axis");
                            me.ShaderProperty(_GradientAxis, Tips.gradientAxis);
                        });
                        MGUI.PropertyGroup(()=>{
                            MGUI.Vector3Field(_GradientNoiseScale, "Noise Scale", false);
                            me.ShaderProperty(_GradientBrightness, "Brightness");
                            me.ShaderProperty(_GradientHeightMax, Tips.endPos);
                            me.ShaderProperty(_GradientHeightMin, Tips.startPos);
                            me.ShaderProperty(_GradientSpeed, "Scroll Speed");
                            me.ShaderProperty(_GradientContrast, "Contrast");
                            me.TexturePropertySingleLine(new GUIContent("Mask"), _GradientMask);
                        });
                        MGUI.ToggleGroupEnd();
                    });
                }

                if (Foldouts.DoFoldout(foldouts, mat, me, _EnablePatches, "Noise Patches", Foldouts.Style.StandardToggle)){
                    MGUI.PropertyGroupParent(()=>{
                        MGUI.ToggleGroup(_EnablePatches.floatValue == 0);
                        MGUI.PropertyGroup(()=>{
                            MGUI.Vector2Field(_NoiseScale, "Scale");
                            me.ShaderProperty(_NoiseBrightness, "Brightness");
                            me.ShaderProperty(_NoiseCutoff, "Cutoff");
                            me.ShaderProperty(_NoiseSmooth, Tips.noiseSmoothing);
                            me.TexturePropertySingleLine(new GUIContent("Mask"), _NoiseMask);
                        });
                        MGUI.ToggleGroupEnd();
                    });
                }

                if (Foldouts.DoFoldout(foldouts, mat, me, _EnableDissolve, "Dissolve", Foldouts.Style.StandardToggle)){
                    MGUI.PropertyGroupParent(()=>{
                        if (isCutout || isTransparent){
                            MGUI.ToggleGroup(_EnableDissolve.floatValue == 0);
                            MGUI.PropertyGroup(()=>{
                                me.TexturePropertySingleLine(new GUIContent("Dissolve Map"), _DissolveTex);
                                MGUI.TextureSO(me, _DissolveTex, _DissolveTex.textureValue);
                                MGUI.Space4();
                                me.ShaderProperty(_DissolveAmt, "Strength");
                                me.ShaderProperty(_DissolveRimBrightness, "Rim Brightness");
                                me.ShaderProperty(_DissolveRimWidth, "Rim Width");
                            });
                            MGUI.ToggleGroupEnd();
                        }
                        else {
                            MGUI.CenteredText("REQUIRES NON-OPAQUE BLEND MODE", 11, 0,0);
                        }
                    });
                }

                if (Foldouts.DoFoldout(foldouts, mat, me, _EnableOutline, "Outline", Foldouts.Style.StandardToggle)){
                    MGUI.PropertyGroupParent(()=>{
                        if (!isTransparent){
                            MGUI.ToggleGroup(_EnableOutline.floatValue == 0);
                            MGUI.PropertyGroup(()=>{
                                me.ShaderProperty(_InvertedOutline, Tips.invertTint);
                                me.ShaderProperty(_StencilOutline, Tips.stencilMode);
                            });
                            MGUI.PropertyGroup(()=>{
                                me.ShaderProperty(_Thickness, "Thickness");
                                me.TexturePropertySingleLine(new GUIContent("Mask"), _OutlineMask);
                            });
                            MGUI.ToggleGroupEnd();
                        }
                        else MGUI.CenteredText("REQUIRES NON-TRANSPARENT BLEND MODE", 10, 0, 0);
                    });
                }
            }
            if (EditorGUI.EndChangeCheck()){
                ApplyMaterialSettings(mat);
                SetBlendMode(mat);
            }

            MGUI.DoFooter(versionLabel);
        }

        void ApplyMaterialSettings(Material mat){
            int blendMode = mat.GetInt("_BlendMode");
            int outlineToggle = mat.GetInt("_EnableOutline");
            int lightingToggle = mat.GetInt("_EnableLighting");
            int reflectionsToggle = mat.GetInt("_EnableReflections");
            int rimToggle = mat.GetInt("_EnableRim");
            int gradientToggle = mat.GetInt("_EnableGradient");
            int dissolveToggle = mat.GetInt("_EnableDissolve");
            int patchesToggle = mat.GetInt("_EnablePatches");
            int invertToggle = mat.GetInt("_InvertedOutline");
            int stencilToggle = mat.GetInt("_StencilOutline");

            mat.SetInt("_ATM", blendMode == 1 ? 1 : 0);
            mat.SetShaderPassEnabled("Always", outlineToggle  == 1);

            // Sync the outline stencil settings with base pass stencil settings when not using stencil mode
            if (outlineToggle == 0 || stencilToggle == 0){
                ApplyOutlineNormalConfig(mat);
            }
            if (outlineToggle == 1 && stencilToggle == 1){
                ApplyOutlineStencilConfig(mat);
            }

            MGUI.SetKeyword(mat, "_ALPHATEST_ON", blendMode == 1);
            MGUI.SetKeyword(mat, "_ALPHADITHER_ON", blendMode == 2);
            MGUI.SetKeyword(mat, "_ALPHABLEND_ON", blendMode == 3);
            MGUI.SetKeyword(mat, "_ALPHAPREMULTIPLY_ON", blendMode == 4);
            MGUI.SetKeyword(mat, "_LIGHTING_ON", lightingToggle == 1);
            MGUI.SetKeyword(mat, "_REFLECTIONS_ON", lightingToggle == 1 && reflectionsToggle == 1);
            MGUI.SetKeyword(mat, "_RIM_ON", rimToggle == 1);
            MGUI.SetKeyword(mat, "_GRADIENT_ON", gradientToggle == 1);
            MGUI.SetKeyword(mat, "_DISSOLVE_ON", dissolveToggle == 1);
            MGUI.SetKeyword(mat, "_PATCHES_ON", patchesToggle == 1);
            MGUI.SetKeyword(mat, "_OUTLINE_INVERT_ON", outlineToggle == 1 && invertToggle == 1);
        }

        void ApplyOutlineNormalConfig(Material mat){
            mat.SetFloat("_OutlineStencilRef", mat.GetFloat("_StencilRef"));
            mat.SetFloat("_OutlineStencilPass", mat.GetFloat("_StencilPass"));
            mat.SetFloat("_OutlineStencilFail", mat.GetFloat("_StencilFail"));
            mat.SetFloat("_OutlineStencilZFail", mat.GetFloat("_StencilZFail"));
            mat.SetFloat("_OutlineStencilCompare", mat.GetFloat("_StencilCompare"));
            mat.SetInt("_OutlineCulling", 1);
        }

        void ApplyOutlineStencilConfig(Material mat){
            mat.SetFloat("_StencilPass", (float)UnityEngine.Rendering.StencilOp.Replace);
            mat.SetFloat("_StencilFail", (float)UnityEngine.Rendering.StencilOp.Keep);
            mat.SetFloat("_StencilZFail", (float)UnityEngine.Rendering.StencilOp.Keep);
            mat.SetFloat("_StencilCompare", (float)UnityEngine.Rendering.CompareFunction.Always);

            mat.SetFloat("_OutlineStencilPass", (float)UnityEngine.Rendering.StencilOp.Keep);
            mat.SetFloat("_OutlineStencilFail", (float)UnityEngine.Rendering.StencilOp.Keep);
            mat.SetFloat("_OutlineStencilZFail", (float)UnityEngine.Rendering.StencilOp.Keep);
            mat.SetFloat("_OutlineStencilCompare", (float)UnityEngine.Rendering.CompareFunction.NotEqual);
            mat.SetInt("_OutlineCulling", 0);
        }

        public override void AssignNewShaderToMaterial(Material mat, Shader oldShader, Shader newShader) {
            m_FirstTimeApply = true;
            if (mat.HasProperty("_Emission"))
                mat.SetColor("_EmissionColor", mat.GetColor("_Emission"));
            base.AssignNewShaderToMaterial(mat, oldShader, newShader);
            SetBlendMode(mat);
            ApplyMaterialSettings(mat);
        }

        void SetBlendMode(Material material){
            int blendMode = material.GetInt("_BlendMode");
            switch (blendMode){
                case 0:
                    material.SetOverrideTag("RenderType", "");
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                    material.SetInt("_ZWrite", 1);
                    material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Geometry+material.GetInt("_QueueOffset");
                    break;

                case 1:
                    material.SetOverrideTag("RenderType", "TransparentCutout");
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                    material.SetInt("_ZWrite", 1);
                    material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.AlphaTest+material.GetInt("_QueueOffset");
                    break;

                case 2:
                    material.SetOverrideTag("RenderType", "TransparentCutout");
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
                    material.SetInt("_ZWrite", 1);
                    material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.AlphaTest+material.GetInt("_QueueOffset");
                    break;

                case 3:
                    material.SetOverrideTag("RenderType", "Transparent");
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                    material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent+material.GetInt("_QueueOffset");
                    break;

                case 4:
                    material.SetOverrideTag("RenderType", "Transparent");
                    material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
                    material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
                    material.SetInt("_ZWrite", 0);
                    material.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent+material.GetInt("_QueueOffset");
                    break;

                default: break;
            }
        }
    }
}