using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;

namespace AllIn13DShader
{
	public static class ShaderVariantCreator
	{
		private const string INCLUDE_LINE_FORMAT = @"#include ""{0}""";
		
		private const string TAG_VARIANT_NAME = @"<VARIANT_NAME>";
		private const string TAG_SHADER_PROPERTIES = @"<SHADER_PROPERTIES>";

		private const string TAG_SHADER_PASSES_URP	= @"<SHADER_PASSES_URP>";
		private const string TAG_SHADER_PASSES_BIRP = @"<SHADER_PASSES_BIRP>";

		//
		private const string TAG_PASS_NAME					= @"<PASS_NAME>";
		private const string TAG_LIGHT_MODE					= @"<LIGHT_MODE>";
		private const string TAG_BLEND_COMMAND				= @"<BLEND_COMMAND>";
		private const string TAG_CULL_COMMAND				= @"<CULL_COMMAND>";
		

		private const string TAG_Z_WRITE_COMMAND			= @"<Z_WRITE_COMMAND>";

		private const string TAG_Z_TEST_COMMAND				= @"<Z_TEST_COMMAND>";

		private const string TAG_COLOR_MASK_COMMAND			= @"<COLOR_MASK_COMMAND>";


		private const string TAG_STENCIL_BLOCK				= @"<STENCIL_BLOCK>";
		private const string TAG_FEATURES_URP_DEFINES		= @"<FEATURES_URP_DEFINES>";
		private const string TAG_FEATURES_URP_LIBRARY		= @"<FEATURES_URP_LIBRARY>";
		private const string TAG_VERTEX_PROGRAM_NAME		= @"<VERTEX_PROGRAM_NAME>";
		private const string TAG_FRAGMENT_PROGRAM_NAME		= @"<FRAGMENT_PROGRAM_NAME>";
		private const string TAG_ALL_IN_1_FEATURES			= @"<ALLIN1_FEATURES>";
		private const string TAG_PIPELINE_PASS_SYMBOL		= @"<PIPELINE_PASS_SYMBOL>";
		private const string TAG_THIS_PASS_SYMBOL			= @"<THIS_PASS_SYMBOL>";
		private const string TAG_INCLUDE_PIPELINE_HELPER	= @"<INCLUDE_PIPELINE_HELPER>";
		private const string TAG_INCLUDE_EFFECT_LIBRARIES	= @"<INCLUDE_EFFECT_LIBRARIES>";
		private const string TAG_INCLUDE_PASS				= @"<INCLUDE_PASS>";
		private const string TAG_EXTRA_PRAGMA_LINES			= @"<EXTRA_PRAGMA_LINES>";

		private const string TAG_INCLUDE_ALL_IN_SHADER_FEATURES = @"<INCLUDE_ALL_IN_1_SHADER_FEATURES>";
		private const string TAG_INCLUDE_ALL_IN_1_SHADER_LIGHT	= @"<INCLUDE_ALL_IN_1_SHADER_LIGHT>";
		private const string TAG_INCLUDE_CORE_LIBRARY			= @"<INCLUDE_CORE_LIBRARY>";
		private const string TAG_INCLUDE_COMMON_FUNCTIONS		= @"<INCLUDE_COMMON_FUNCTIONS>";
		private const string TAG_INCLUDE_COMMON_STRUCTS			= @"<INCLUDE_COMMON_STRUCTS>";
		//

		private static string TEMPLATE_PATH = GlobalConfiguration.GetRootPluginFolderPath() + "/Editor/Effects Profiles/Templates/ShaderTemplate.allin1template";
		private static string SHADER_PROPERTIES_PATH = GlobalConfiguration.GetRootPluginFolderPath() + "/Editor/Templates/AllIn13DShader_ShaderProperties.allIn13DData";
		private static string SHADER_PASS_URP_TAMPLATE_PATH = GlobalConfiguration.GetRootPluginFolderPath() + "/Editor/Effects Profiles/Templates/PassTemplate_URP.allin1template";
		private static string SHADER_PASS_BIRP_TAMPLATE_PATH = GlobalConfiguration.GetRootPluginFolderPath() + "/Editor/Effects Profiles/Templates/PassTemplate_BIRP.allin1template";


		public static string SHADER_VARIANTS_FOLDER_NAME = "Baked Variants";
		private static string SHADER_VARIANT_FILE_NAME = "AllIn13D_BakedEffects_{0}.shader";
		
		public static Shader CreateVariantByMaterialInfo(PropertiesConfig propertiesConfig, AbstractMaterialInfo matInfo, string profileName)
		{
			EffectsProfileCollection effectsProfileCollection = GlobalConfiguration.instance.effectsProfileCollection;
			
			EffectsProfile effectsProfile = effectsProfileCollection.CreateNewProfile(profileName);

			effectsProfile.InitFromOtherProfile(effectsProfileCollection.generalProfile);
			effectsProfileCollection.ConfigureEffectProfileByMaterialInfo(
				target: effectsProfile, 
				activeEffectsList: effectsProfileCollection.generalProfile, 
				matInfo: matInfo, 
				propertiesConfig: propertiesConfig);

			string folderPath = GlobalConfiguration.instance.BakedShaderSavePath;
			Shader res = CreateVariant(effectsProfile, effectsProfileCollection, folderPath, false);
			
			return res;
		}

		public static Shader CreateVariant(EffectsProfileCollection effectsProfileCollection, string profileName)
		{
			EffectsProfile effectProfile = effectsProfileCollection.CreateNewProfile(profileName);
			effectProfile.InitFromOtherProfile(effectsProfileCollection.generalProfile);

			string folderPath = GlobalConfiguration.instance.BakedShaderSavePath;
			Shader res = CreateVariant(effectProfile, effectsProfileCollection, folderPath, false);
			return res;
		}

		public static Shader CreateVariant(EffectsProfile effectsProfile, EffectsProfileCollection effectsProfileCollection, string folderPath, bool overwrite)
		{
			string txtShaderVariant = File.ReadAllText(TEMPLATE_PATH);

			txtShaderVariant = ConfigureVariantName(txtShaderVariant, effectsProfile.profileName);
			txtShaderVariant = ConfigureShaderProperties(txtShaderVariant);

			ShaderPassCollection shaderPassCollection = GlobalConfiguration.instance.shaderPassCollection;

			string urpPassesEntry = GetShaderPassesEntry(effectsProfile, shaderPassCollection, RenderPipelineEnum.URP, folderPath);
			txtShaderVariant = txtShaderVariant.Replace(TAG_SHADER_PASSES_URP, urpPassesEntry);

			string birpPassesEntry = GetShaderPassesEntry(effectsProfile, shaderPassCollection, RenderPipelineEnum.BIRP, folderPath);
			txtShaderVariant = txtShaderVariant.Replace(TAG_SHADER_PASSES_BIRP, birpPassesEntry);

			
			string filePath;
			if (overwrite)
			{
				Shader shader = effectsProfile.FindShader();
				filePath = AssetDatabase.GetAssetPath(shader);
			}
			else
			{
				string fileName = string.Format(SHADER_VARIANT_FILE_NAME, effectsProfile.profileName);
				filePath = Path.Combine(folderPath, fileName);

				filePath = AssetDatabase.GenerateUniqueAssetPath(filePath);
			}

			effectsProfile.shaderGUID = AssetDatabase.AssetPathToGUID(filePath);


			txtShaderVariant = EditorUtils.UnifyEOL(txtShaderVariant);
			SaveFile(filePath, txtShaderVariant);
			AssetDatabase.Refresh();

			Shader res = AssetDatabase.LoadAssetAtPath(filePath, typeof(Shader)) as Shader;
			EditorUtility.SetDirty(effectsProfileCollection);

			return res;
		}

		private static void SaveFile(string filePath, string txtFile)
		{
			File.WriteAllText(filePath, txtFile);
		}

		private static string ConfigureVariantName(string input, string variantName)
		{
			string res = input.Replace(TAG_VARIANT_NAME, variantName);
			return res;
		}

		private static string ConfigureShaderProperties(string input)
		{
			string res = input;

			string shaderPropertiesTxt = EditorUtils.ReadFileTextWithTabs(SHADER_PROPERTIES_PATH, 2);
			res = res.Replace(TAG_SHADER_PROPERTIES, shaderPropertiesTxt);

			return res;
		}

		private static string GetShaderPassesEntry(EffectsProfile effectsProfile, ShaderPassCollection shaderPassCollection, RenderPipelineEnum renderPipeline, string shaderFolderPath)
		{
			string shaderPassTemplateTxt = EditorUtils.ReadFileTextWithTabs(GetTemplatePathByRenderPipeline(renderPipeline), 2);

			List<ShaderPassConfig> shaderPasses = effectsProfile.GetShaderPasses(shaderPassCollection, renderPipeline);

			bool hasStencilBlock = false;
			for(int i = 0; i < shaderPasses.Count; i++)
			{
				if (shaderPasses[i].passType == AllIn13DPassType.OUTLINE)
				{
					hasStencilBlock = true;
					break;
				}
			}

			string res = string.Empty;
			for (int i = 0; i < shaderPasses.Count; i++)
			{
				string currentPass = shaderPassTemplateTxt;
				currentPass = ConfigureShaderPass(effectsProfile, currentPass, shaderPasses[i], renderPipeline, hasStencilBlock, shaderFolderPath);

				res += currentPass;
				res += "\n";
			}

			return res;
		}

		private static string ConfigureShaderPass(EffectsProfile effectsProfile, 
			string templatePass, ShaderPassConfig shaderPass, RenderPipelineEnum renderPipeline, bool hasStencilBlock, 
			string shaderFolderPath)
		{
			string res = templatePass;

			res = res.Replace(TAG_PASS_NAME, shaderPass.GetPassName(renderPipeline));

			res = res.Replace(TAG_LIGHT_MODE, shaderPass.GetLightModeShaderEntry(renderPipeline));

			res = res.Replace(TAG_BLEND_COMMAND, shaderPass.blendCommand.GetShaderEntry());
			
			res = res.Replace(TAG_CULL_COMMAND, shaderPass.cullCommand.GetShaderEntry());
			res = res.Replace(TAG_Z_WRITE_COMMAND, shaderPass.zWriteCommand.GetShaderEntry());
			res = res.Replace(TAG_Z_TEST_COMMAND, shaderPass.zTestCommand.GetShaderEntry());
			res = res.Replace(TAG_COLOR_MASK_COMMAND, shaderPass.colorMaskCommand.GetShaderEntry());

			if (hasStencilBlock)
			{
				res = res.Replace(TAG_STENCIL_BLOCK, shaderPass.GetStencilBlockShaderEntry(effectsProfile));
			}
			else
			{
				res = res.Replace(TAG_STENCIL_BLOCK, string.Empty);
			}

			res = res.Replace(TAG_FEATURES_URP_DEFINES, shaderPass.GetPipelineFeaturesDefinesShaderEntry(renderPipeline, shaderFolderPath));
			res = res.Replace(TAG_FEATURES_URP_LIBRARY, shaderPass.GetPipelineFeaturesLibraryShaderEntry(renderPipeline, shaderFolderPath));

			res = res.Replace(TAG_VERTEX_PROGRAM_NAME, shaderPass.vertexProgramName);
			res = res.Replace(TAG_FRAGMENT_PROGRAM_NAME, shaderPass.fragmentProgramName);

			res = res.Replace(TAG_INCLUDE_PIPELINE_HELPER, string.Format(INCLUDE_LINE_FORMAT, shaderPass.GetHelperLibraryPath(renderPipeline, shaderFolderPath)));

			string effectsLibraries = shaderPass.GetEffectsLibrariesShaderEntry(effectsProfile, shaderFolderPath);
			res = res.Replace(TAG_INCLUDE_EFFECT_LIBRARIES, effectsLibraries);

			res = res.Replace(TAG_INCLUDE_PASS, string.Format(INCLUDE_LINE_FORMAT, shaderPass.GetPassFilePath(shaderFolderPath)));

			List<EffectsProfileEntry> enabledEntries = effectsProfile.GetEnabledEntriesFlatList();
			string entryFeaturesTxt = ShaderPassConfig.GetAllIn1FeaturesEntries(enabledEntries);
			
			res = res.Replace(TAG_ALL_IN_1_FEATURES, entryFeaturesTxt);

			res = res.Replace(TAG_PIPELINE_PASS_SYMBOL, shaderPass.GetPipelinePassSymbolShaderEntry(renderPipeline));
			res = res.Replace(TAG_THIS_PASS_SYMBOL, shaderPass.GetPassSymbolShaderEntry());

			res = res.Replace(TAG_EXTRA_PRAGMA_LINES, shaderPass.GetExtraPragmaLines(renderPipeline));

			res = res.Replace(TAG_INCLUDE_ALL_IN_SHADER_FEATURES, shaderPass.GetShaderFeaturesLibraryShaderEntry(shaderFolderPath));
			res = res.Replace(TAG_INCLUDE_ALL_IN_1_SHADER_LIGHT, shaderPass.GetLightLibraryShaderEntry(shaderFolderPath));
			res = res.Replace(TAG_INCLUDE_CORE_LIBRARY, shaderPass.GetCoreLibraryShaderEntry(shaderFolderPath));
			res = res.Replace(TAG_INCLUDE_COMMON_STRUCTS, shaderPass.GetCommonStructsShaderEntry(shaderFolderPath));
			res = res.Replace(TAG_INCLUDE_COMMON_FUNCTIONS, shaderPass.GetCommonFunctionsShaderEntry(shaderFolderPath));

			return res;
		}

		private static string GetTemplatePathByRenderPipeline(RenderPipelineEnum renderPipeline)
		{
			string res = string.Empty;
			switch (renderPipeline)
			{
				case RenderPipelineEnum.BIRP:
					res = SHADER_PASS_BIRP_TAMPLATE_PATH;
					break;

				case RenderPipelineEnum.URP:
					res = SHADER_PASS_URP_TAMPLATE_PATH;
					break;
			}

			return res;
		}

		public static string GetShaderVariantsFolderPath()
		{
			string res = GlobalConfiguration.instance.BakedShaderSavePath;
			return res;
		}
	}
}