using UnityEditor;
using UnityEngine;

namespace AllIn13DShader 
{
	[CanEditMultipleObjects]
	public class AllIn13DShaderMaterialInspector : ShaderGUI
	{
		private PropertiesConfigCollection propertiesConfigCollection;
		
		private PropertiesConfig currentPropertiesConfig;

		private AllIn13DShaderInspectorReferences inspectorReferences;
		private AbstractEffectDrawer[] drawers;
		private GlobalPropertiesDrawer globalPropertiesDrawer;
		private AdvancedPropertiesDrawer advancedPropertiesDrawer;

		private MaterialPresetCollection blendingModeCollection;

		private MaterialProperty matPropertyRenderPreset;
		private MaterialProperty matPropertyBlendSrc;
		private MaterialProperty matPropertyBlendDst;
		private MaterialProperty matPropertyZWrite;
		
		private int lastRenderQueue;

		private static CommonStyles commonStyles;

		private void RefreshReferences(MaterialEditor materialEditor, MaterialProperty[] properties)
		{
			if (inspectorReferences == null)
			{
				inspectorReferences = new AllIn13DShaderInspectorReferences();
				inspectorReferences.Setup(materialEditor, properties);

				if (lastRenderQueue > 0)
				{
					for(int i = 0; i < inspectorReferences.targetMatInfos.Length; i++)
					{
						inspectorReferences.targetMatInfos[i].mat.renderQueue = lastRenderQueue;
					}
				}
			}

			if (propertiesConfigCollection == null)
			{
				string[] guids = AssetDatabase.FindAssets("PropertiesConfigCollection t:PropertiesConfigCollection");
				if(guids.Length == 0)
				{
					Debug.LogWarning("PropertiesConfigCollection not found in the project. Configuring...");
					this.propertiesConfigCollection = PropertiesConfigCreator.CreateConfig();
					Debug.LogWarning("AllIn13DShader configured");
				}
				else
				{
					string path = AssetDatabase.GUIDToAssetPath(guids[0]);
					propertiesConfigCollection = AssetDatabase.LoadAssetAtPath<PropertiesConfigCollection>(path);
				}

				RefreshPropertiesConfig();
			}

			if (blendingModeCollection == null)
			{
				blendingModeCollection = (MaterialPresetCollection)EditorUtils.FindAsset<ScriptableObject>("BlendingModeCollection");
			}

			CreateDrawers();

			matPropertyRenderPreset = inspectorReferences.matProperties[currentPropertiesConfig.renderPreset];
			matPropertyBlendSrc = inspectorReferences.matProperties[currentPropertiesConfig.blendSrcIdx];
			matPropertyBlendDst = inspectorReferences.matProperties[currentPropertiesConfig.blendDstIdx];
			matPropertyZWrite = inspectorReferences.matProperties[currentPropertiesConfig.zWriteIndex];

			//We ensure that data is refreshed. Sometimes objects are not null but we need to refresh the references
			inspectorReferences.Setup(materialEditor, properties);

			if(commonStyles == null)
			{
				commonStyles = new CommonStyles();
			}

			RefreshDrawers();
		}

		private void ResetReferences()
		{
			this.propertiesConfigCollection = null;
			this.currentPropertiesConfig = null;

			inspectorReferences = null;
			drawers = null;

			globalPropertiesDrawer = null;
			advancedPropertiesDrawer = null;
		}

		private void CreateDrawers()
		{
			if (propertiesConfigCollection == null || 
				currentPropertiesConfig == null ||
				inspectorReferences == null ||
				drawers == null ||
				globalPropertiesDrawer == null ||
				advancedPropertiesDrawer == null)
			{
				drawers = new AbstractEffectDrawer[0];

				GeneralEffectDrawer generalEffectDrawer = new GeneralEffectDrawer(inspectorReferences, currentPropertiesConfig);
				
				EffectProperty mainNormalMapProperty = currentPropertiesConfig.FindEffectProperty("NORMAL_MAP", "_NormalMap");

				TriplanarEffectDrawer triplanarEffectDrawer = new TriplanarEffectDrawer(mainNormalMapProperty, inspectorReferences, currentPropertiesConfig);
				ColorRampEffectDrawer colorRampEffectDrawer = new ColorRampEffectDrawer(inspectorReferences, currentPropertiesConfig);
				OutlineEffectDrawer outlineEffectDrawer = new OutlineEffectDrawer(inspectorReferences, currentPropertiesConfig);
				TextureBlendingEffectDrawer vertexColorEffectDrawer = new TextureBlendingEffectDrawer(mainNormalMapProperty, inspectorReferences, currentPropertiesConfig);
				NormalMapEffectDrawer normalMapEffectDrawer = new NormalMapEffectDrawer(inspectorReferences, currentPropertiesConfig);

				drawers = new AbstractEffectDrawer[]
				{
					generalEffectDrawer,
					triplanarEffectDrawer,
					colorRampEffectDrawer, 
					outlineEffectDrawer,
					vertexColorEffectDrawer,
					normalMapEffectDrawer
				};

				advancedPropertiesDrawer = new AdvancedPropertiesDrawer(currentPropertiesConfig.advancedProperties, currentPropertiesConfig.blendSrcIdx, currentPropertiesConfig.blendDstIdx, inspectorReferences);
			}

			if (globalPropertiesDrawer == null)
			{
				globalPropertiesDrawer = new GlobalPropertiesDrawer();
			}
		}

		private void RefreshDrawers()
		{
			for(int i = 0; i < drawers.Length; i++)
			{
				drawers[i].Refresh(inspectorReferences);
			}
		}

		private AbstractEffectDrawer FindEffectDrawerByID(string drawerID)
		{
			AbstractEffectDrawer res = null;

			for (int i = 0; i < drawers.Length; i++)
			{
				if (drawers[i].ID == drawerID)
				{
					res = drawers[i];
					break;
				}
			}

			return res;
		}

		public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader)
		{
			base.AssignNewShaderToMaterial(material, oldShader, newShader);
		}

		private void RefreshPropertiesConfig()
		{
			Shader shader = inspectorReferences.GetShader();
			currentPropertiesConfig = propertiesConfigCollection.FindPropertiesConfigByShader(shader);

			inspectorReferences.SetOutlineEffect(currentPropertiesConfig);
			inspectorReferences.SetCastShadowsEffect(currentPropertiesConfig);
		}

		public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
		{
			if (inspectorReferences != null && drawers != null)
			{
				inspectorReferences.Setup(materialEditor, properties);
			}

			RefreshReferences(materialEditor, properties);

			commonStyles.InitStyles();

#if ALLIN13DSHADER_URP
			bool urpCorrectlyConfigured = URPConfigurator.IsURPCorrectlyConfigured();
			if (!urpCorrectlyConfigured)
			{
				EditorGUILayout.LabelField(CommonMessages.URP_PIPELINE_NOT_ASSIGNED, commonStyles.warningLabel);
				EditorUtils.DrawButtonLink(CommonMessages.URP_PIPELINE_NOT_ASSIGNED_DOC_LINK);

				GUILayout.Space(50f);
			}

			EditorGUI.BeginDisabledGroup(!urpCorrectlyConfigured);
#endif

			DrawPresetsTabs();
			DrawAdvancedProperties();
			DrawGlobalProperties();
			DrawEffects();

#if ALLIN13DSHADER_URP
			EditorGUI.EndDisabledGroup();
#endif

			CheckLightmapFlags();
			bool shaderChanged = false;
			for (int i = 0; i < inspectorReferences.targetMatInfos.Length; i++)
			{
				Material targetMat = inspectorReferences.targetMatInfos[i].mat;

				lastRenderQueue = targetMat.renderQueue;

				if (!inspectorReferences.targetMatInfos[i].IsShaderVariant())
				{
					shaderChanged = shaderChanged || MaterialUtils.CheckMaterialShader(targetMat);
				}
			}



			EditorGUILayout.Separator();
			EditorUtils.DrawLine(Color.grey, 1, 3);

			if (inspectorReferences.AreAllMaterialsShaderGeneric())
			{
				if (GUILayout.Button("Bake Shader Keywords"))
				{
					for (int i = 0; i < inspectorReferences.targetMatInfos.Length; i++)
					{
						string variantName = Selection.objects[i].name;
						Shader newShader = ShaderVariantCreator.CreateVariantByMaterialInfo(currentPropertiesConfig, inspectorReferences.targetMatInfos[i], variantName);
						inspectorReferences.targetMatInfos[i].mat.shader = newShader;
					}
					//Shader newShader = ShaderVariantCreator.CreateVariantByMaterialInfo(currentPropertiesConfig, inspectorReferences.targetMatInfos[0], variantName);
					//materialEditor.SetShader(newShader);

					shaderChanged = true;
				}
			}
			else
			{
				if (GUILayout.Button("Revert To Generic Shader"))
				{
					Shader currentShader = inspectorReferences.GetShader();
					GlobalConfiguration.instance.effectsProfileCollection.RemoveEffectsProfileByShader(currentShader);

					Shader newShader = Shader.Find(Constants.SHADER_FULL_NAME_ALLIN13D);
					materialEditor.SetShader(newShader);

					shaderChanged = true;
				}
			}

			if (shaderChanged)
			{
				ResetReferences();
			}
		}

		//private void CheckPasses(Material targetMat)
		//{
		//	if (targetMat.IsKeywordEnabled("_LIGHTMODEL_FASTLIGHTING") || targetMat.IsKeywordEnabled("_LIGHTMODEL_NONE"))
		//	{
		//		targetMat.SetShaderPassEnabled("ForwardAdd", false);
		//	}
		//	else
		//	{
		//		targetMat.SetShaderPassEnabled("ForwardAdd", true);
		//	}
		//}

		private void DrawPresetsTabs()
		{
			EditorGUI.BeginChangeCheck();

			string[] texts = blendingModeCollection.CreateStringsArray();


			int presetIndex = (int)matPropertyRenderPreset.floatValue;
			if (presetIndex >= blendingModeCollection.presets.Length)
			{
				presetIndex = 1;
				matPropertyRenderPreset.floatValue = presetIndex;
			}

			BlendingMode previousPreset = blendingModeCollection[presetIndex];
			if(previousPreset == null)
			{
				previousPreset = blendingModeCollection[0];
			}

			int newIndex = (int)matPropertyRenderPreset.floatValue;
			newIndex = GUILayout.SelectionGrid(newIndex, texts, 3, inspectorReferences.tabButtonStyle);
			matPropertyRenderPreset.floatValue = newIndex;
			if (EditorGUI.EndChangeCheck())
			{
				BlendingMode selectedPreset = blendingModeCollection[newIndex];
				for(int i = 0; i < inspectorReferences.targetMatInfos.Length; i++)
				{
					Material targetMat = inspectorReferences.targetMatInfos[i].mat;
					ApplyMaterialPreset(targetMat, previousPreset, selectedPreset);
				}
			}
		}

		private void DrawAdvancedProperties()
		{
			//Security check
			if (advancedPropertiesDrawer != null)
			{
				advancedPropertiesDrawer.Draw();
			}
		}

		private void DrawGlobalProperties()
		{
			globalPropertiesDrawer.Draw(currentPropertiesConfig.singleProperties, inspectorReferences);
		}

		private void DrawEffects()
		{
			int globalEffectIndex = 0;
			for (int groupIdx = 0; groupIdx < currentPropertiesConfig.effectsGroups.Length; groupIdx++)
			{
				EffectGroup effectGroup = currentPropertiesConfig.effectsGroups[groupIdx];
				if (effectGroup.effects.Length <= 0) { continue; }

				EditorGUILayout.Separator();
				EditorUtils.DrawLine(Color.grey, 1, 3);
				GUILayout.Label(effectGroup.DisplayName, inspectorReferences.bigLabelStyle);

				for (int effectIdx = 0; effectIdx < effectGroup.effects.Length; effectIdx++)
				{
					AllIn13DEffectConfig effectConfig = effectGroup.effects[effectIdx];

					globalEffectIndex++;

					AbstractEffectDrawer drawer = FindEffectDrawerByID(effectConfig.effectDrawerID);
					drawer.Draw(currentPropertiesConfig, effectConfig, globalEffectIndex);
				}
			}
		}

		private void CheckLightmapFlags()
		{
			for (int i = 0; i < inspectorReferences.targetMatInfos.Length; i++)
			{
				AbstractMaterialInfo matInfo = inspectorReferences.targetMatInfos[i];
				AllIn13DEffectConfig emissionEffectConfig = propertiesConfigCollection.propertiesConfig.FindEffectConfigByID(Constants.EFFECT_ID_EMISSION);
				bool emissionEnabled = AllIn13DEffectConfig.IsEffectEnabled(emissionEffectConfig, matInfo);
				if (emissionEnabled)
				{
					matInfo.mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive;
				}
				else
				{
					matInfo.mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.EmissiveIsBlack;
				}
			}
		}

		private void ApplyMaterialPreset(Material targetMat, BlendingMode previousPresset, BlendingMode newPreset)
		{
			matPropertyBlendSrc.floatValue = (float)newPreset.blendSrc;
			matPropertyBlendDst.floatValue = (float)newPreset.blendDst;
			matPropertyZWrite.floatValue = newPreset.depthWrite ? 1.0f : 0.0f;


			lastRenderQueue = (int)newPreset.renderQueue;
			targetMat.renderQueue = lastRenderQueue;

			if (previousPresset != newPreset && previousPresset.defaultEnabledEffects != null)
			{
				for (int i = 0; i < previousPresset.defaultEnabledEffects.Length; i++)
				{
					string effectID = previousPresset.defaultEnabledEffects[i];
					AllIn13DEffectConfig effectConfig = currentPropertiesConfig.FindEffectConfigByID(effectID);

					for(int matIdx = 0; matIdx < inspectorReferences.targetMatInfos.Length; matIdx++)
					{
						AbstractMaterialInfo matInfo = inspectorReferences.targetMatInfos[matIdx];
						AllIn13DEffectConfig.DisableEffectToggle(effectConfig, inspectorReferences, matInfo);
					}
				}
			}

			if (newPreset.defaultEnabledEffects != null)
			{
				for (int matIdx = 0; matIdx < inspectorReferences.targetMatInfos.Length; matIdx++)
				{
					AbstractMaterialInfo matInfo = inspectorReferences.targetMatInfos[matIdx];

					if (newPreset.isTransparent)
					{
						matInfo.EnableKeyword(Constants.KEYWORD_ALLIN13D_SURFACE_TRANSPARENT);
					}
					else
					{
						matInfo.DisableKeyword(Constants.KEYWORD_ALLIN13D_SURFACE_TRANSPARENT);
					}

					for (int i = 0; i < newPreset.defaultEnabledEffects.Length; i++)
					{
						string effectID = newPreset.defaultEnabledEffects[i];
						AllIn13DEffectConfig effectConfig = currentPropertiesConfig.FindEffectConfigByID(effectID);
						AllIn13DEffectConfig.EnableEffectToggle(effectConfig, inspectorReferences, matInfo);
					}
				}
			}
		}
	}
}