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

// Cartoon FX Easy Editor
// (c) 2013-2020 - Jean Moreno

public class CFXEasyEditor : EditorWindow
{
	static private CFXEasyEditor SingleWindow;
	
	[MenuItem("Tools/Cartoon FX Easy Editor")]
	static void ShowWindow()
	{
		CFXEasyEditor window = EditorWindow.GetWindow<CFXEasyEditor>(EditorPrefs.GetBool("CFX_ShowAsToolbox", true), "Easy Editor", true);
		window.minSize = new Vector2(300, 8);
		window.maxSize = new Vector2(300, 8);
	}

	private int SelectedParticleSystemsCount;

	//Change Start Color
	private bool AffectAlpha = true;
	private Color ColorValue = Color.white;
	private Color ColorValue2 = Color.white;
	
	//Scale
	private float ScalingValue = 2.0f;
	private float LTScalingValue = 1.0f;
	
	//Delay
	private float DelayValue = 1.0f;
	
	//Duration
	private float DurationValue = 5.0f;
	
	//Tint
	private bool TintStartColor = true;
	private bool TintColorModule = true;
	private bool TintColorSpeedModule = true;
	private bool TintTrailsModule = true;
	private bool TintCustomData1 = true;
	private bool TintCustomData2 = true;
	private bool TintLights = true;
	private Color TintColorValue = Color.white;
	private float TintHueShiftValue = 0f;
	private float SatShiftValue = 0f;
	private float ValShiftValue = 0f;

	//Change Lightness
	private int LightnessStep = 10;
	
	//Module copying system
	private ParticleSystem copyModulesSourceParticleSystem;

	private class ParticleSystemModule
	{
		public string name;
		//public bool selected;
		//public bool enabledInSource;
		public string serializedPropertyPath;

		public ParticleSystemModule(string name, string serializedPropertyPath)
		{
			this.name = " " + name + " ";
			this.serializedPropertyPath = serializedPropertyPath;
		}

		public bool CheckIfEnabledInSource(SerializedObject serializedSource)
		{
			bool enabled = true;
			var sp = serializedSource.FindProperty(this.serializedPropertyPath);
			if(sp != null)
			{
				var enabledSp = sp.FindPropertyRelative("enabled");
				if(enabledSp != null)
				{
					enabled = enabledSp.boolValue;
				}
			}
			return enabled;
		}
	}

	private ParticleSystemModule[] modulesToCopy = null;
	private List<string> initialModuleExtraProperties = null;
	private GUIContent[] modulesLabels = null;

	private ParticleSystem selectedParticleSystem;
	private bool[] selectedModules;
	private bool[] enabledModules;
	/*
	private bool[] selectedModulesMutate;
	private bool[] enabledModulesMutate;

	private float mutatePercentage = 0.1f;
	*/

	//Foldouts
	bool basicFoldout = false;
	bool colorFoldout = false;
	bool copyFoldout = false;
	//bool mutateFoldout = false;
	
	//Editor Prefs
	private bool pref_ShowAsToolbox;
	private bool pref_IncludeChildren;
	private bool pref_HideDisabledModulesCopy;

	void OnEnable()
	{
		//Load Settings
		pref_ShowAsToolbox = EditorPrefs.GetBool("CFX_ShowAsToolbox", true);
		pref_IncludeChildren = EditorPrefs.GetBool("CFX_IncludeChildren", true);
		pref_HideDisabledModulesCopy = EditorPrefs.GetBool("CFX_HideDisabledModulesCopy", true);
		basicFoldout = EditorPrefs.GetBool("CFX_BasicFoldout", false);
		colorFoldout = EditorPrefs.GetBool("CFX_ColorFoldout", false);
		copyFoldout = EditorPrefs.GetBool("CFX_CopyFoldout", false);
		//mutateFoldout = EditorPrefs.GetBool("CFX_MutateFoldout", false);

		ParticleSystem ps = null;
		if (Selection.activeGameObject != null)
		{
			ps = Selection.activeGameObject.GetComponent<ParticleSystem>();
		}
		if (ps == null && copyModulesSourceParticleSystem != null)
		{
			ps = copyModulesSourceParticleSystem.GetComponent<ParticleSystem>();
		}
		FetchParticleSystemModules(ps);
		//RefreshCurrentlyEnabledModules(ps, ref enabledModulesMutate);

		OnSelectionChange();
	}

	void OnSelectionChange()
	{
		UpdateSelectionCount();
		this.Repaint();

		if (Selection.activeGameObject != null)
		{
			selectedParticleSystem = Selection.activeGameObject.GetComponent<ParticleSystem>();
			FetchParticleSystemModules(selectedParticleSystem);
		}
	}

	void FetchParticleSystemModules(ParticleSystem ps)
	{
		// Fetch the modules list from a Particle System
		if (modulesToCopy == null && ps != null)
		{
			initialModuleExtraProperties = new List<string>();
			var modulesList = new List<ParticleSystemModule>();
			var modulesLabelsList = new List<GUIContent>();
			var so = new SerializedObject(ps);
			var sp = so.GetIterator();
			sp.Next(true);
			while (sp.NextVisible(false))
			{
				if (sp.propertyPath.EndsWith("Module"))
				{
					var module = new ParticleSystemModule(sp.propertyPath.Replace("Module", ""), sp.propertyPath);
					modulesList.Add(module);
					modulesLabelsList.Add(new GUIContent(sp.propertyPath.Replace("Module", "")));
				}
				else
				{
					initialModuleExtraProperties.Add(sp.propertyPath);
				}
			}
			modulesToCopy = modulesList.ToArray();

			// add one entry for renderer (not a module but a component)
			modulesLabelsList.Add(new GUIContent("Renderer"));
			modulesLabels = modulesLabelsList.ToArray();
		}
	}

	void OnDisable()
	{
		//Save Settings
		EditorPrefs.SetBool("CFX_BasicFoldout", basicFoldout);
		EditorPrefs.SetBool("CFX_ColorFoldout", colorFoldout);
		EditorPrefs.SetBool("CFX_CopyFoldout", copyFoldout);
		//EditorPrefs.SetBool("CFX_MutateFoldout", mutateFoldout);
	}

	void UpdateSelectionCount()
	{
		SelectedParticleSystemsCount = 0;
		foreach(var go in Selection.gameObjects)
		{
			ParticleSystem[] systems;
			if(pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();

			SelectedParticleSystemsCount += systems.Length;
		}
	}
	
	void OnGUI()
	{
		GUILayout.Space(4);
		
		GUILayout.BeginHorizontal();
		var rect = EditorGUILayout.GetControlRect(false, EditorGUIUtility.singleLineHeight, EditorStyles.label);
		GUI.Label(rect, "Cartoon FX Easy Editor", EditorStyles.boldLabel);
		var guiColor = GUI.color;
		GUI.color *= new Color(.8f,.8f,.8f,1f);
		pref_ShowAsToolbox = GUILayout.Toggle(pref_ShowAsToolbox, new GUIContent("Toolbox", "If enabled, the window will be displayed as an external toolbox.\nElse it will act as a dockable Unity window."), EditorStyles.miniButton, GUILayout.Width(65));
		GUI.color = guiColor;
		if(GUI.changed)
		{
			EditorPrefs.SetBool("CFX_ShowAsToolbox", pref_ShowAsToolbox);
			this.Close();
			CFXEasyEditor.ShowWindow();
		}
		GUILayout.EndHorizontal();
		GUILayout.Label("Easily change properties of any Particle System!", EditorStyles.miniLabel);
		
	//----------------------------------------------------------------
		
		pref_IncludeChildren = GUILayout.Toggle(pref_IncludeChildren, new GUIContent("Include Children", "If checked, changes will affect every Particle Systems from each child of the selected GameObject(s)"));
		if(GUI.changed)
		{
			EditorPrefs.SetBool("CFX_IncludeChildren", pref_IncludeChildren);
			UpdateSelectionCount();
		}

		GUILayout.Label(string.Format("{0} selected Particle System{1}", SelectedParticleSystemsCount, SelectedParticleSystemsCount > 1 ? "s" : ""), EditorStyles.helpBox);
		
		EditorGUILayout.BeginHorizontal();
		
		GUILayout.Label("Test effect(s):");
		using(new EditorGUI.DisabledScope(SelectedParticleSystemsCount <= 0))
		{

			if(GUILayout.Button("Play", EditorStyles.miniButtonLeft, GUILayout.Width(50f)))
			{
				foreach(GameObject go in Selection.gameObjects)
				{
					ParticleSystem[] systems = go.GetComponents<ParticleSystem>();
					if(systems.Length == 0) continue;
					foreach(ParticleSystem system in systems)
						system.Play(pref_IncludeChildren);
				}
			}
			if(GUILayout.Button("Pause", EditorStyles.miniButtonMid, GUILayout.Width(50f)))
			{
				foreach(GameObject go in Selection.gameObjects)
				{
					ParticleSystem[] systems = go.GetComponents<ParticleSystem>();
					if(systems.Length == 0) continue;
					foreach(ParticleSystem system in systems)
						system.Pause(pref_IncludeChildren);
				}
			}
			if(GUILayout.Button("Stop", EditorStyles.miniButtonMid, GUILayout.Width(50f)))
			{
				foreach(GameObject go in Selection.gameObjects)
				{
					ParticleSystem[] systems = go.GetComponents<ParticleSystem>();
					if(systems.Length == 0) continue;
					foreach(ParticleSystem system in systems)
						system.Stop(pref_IncludeChildren);
				}
			}
			if(GUILayout.Button("Clear", EditorStyles.miniButtonRight, GUILayout.Width(50f)))
			{
				foreach(GameObject go in Selection.gameObjects)
				{
					ParticleSystem[] systems = go.GetComponents<ParticleSystem>();
					if(systems.Length == 0) continue;
					foreach(ParticleSystem system in systems)
					{
						system.Stop(pref_IncludeChildren);
						system.Clear(pref_IncludeChildren);
					}
				}
			}
		}
		
		GUILayout.FlexibleSpace();
		EditorGUILayout.EndHorizontal();

		//----------------------------------------------------------------

		//Separator
		GUISeparator();
		//GUILayout.Box("",GUILayout.Width(this.position.width - 12), GUILayout.Height(3));
		
		basicFoldout = EditorGUILayout.Foldout(basicFoldout, "QUICK EDIT", true);
		if(basicFoldout)
		{
			GUILayout.Space(4);

			//----------------------------------------------------------------

			GUILayout.BeginHorizontal();
			if(GUILayout.Button(new GUIContent("Scale Size", "Changes the size of the Particle System(s) and other values accordingly (speed, gravity, etc.)"), GUILayout.Width(120)))
			{
				applyScale();
			}
			GUILayout.Label("Multiplier:",GUILayout.Width(110));
			ScalingValue = EditorGUILayout.FloatField(ScalingValue,GUILayout.Width(50));
			if(ScalingValue <= 0) ScalingValue = 0.1f;
			GUILayout.EndHorizontal();
			
		//----------------------------------------------------------------
			
			GUILayout.BeginHorizontal();
			if(GUILayout.Button(new GUIContent("Set Speed", "Changes the simulation speed of the Particle System(s)\n1 = default speed"), GUILayout.Width(120)))
			{
				applySpeed();
			}
			GUILayout.Label("Speed:",GUILayout.Width(110));
			LTScalingValue = EditorGUILayout.FloatField(LTScalingValue,GUILayout.Width(50));
			if(LTScalingValue < 0.0f) LTScalingValue = 0.0f;
			else if(LTScalingValue > 9999) LTScalingValue = 9999;
			GUILayout.EndHorizontal();
			
		//----------------------------------------------------------------
			
			GUILayout.BeginHorizontal();
			if(GUILayout.Button(new GUIContent("Set Duration", "Changes the duration of the Particle System(s)"), GUILayout.Width(120)))
			{
				applyDuration();
			}
			GUILayout.Label("Duration (sec):",GUILayout.Width(110));
			DurationValue = EditorGUILayout.FloatField(DurationValue,GUILayout.Width(50));
			if(DurationValue < 0.1f) DurationValue = 0.1f;
			else if(DurationValue > 9999) DurationValue = 9999;
			GUILayout.EndHorizontal();
			
		//----------------------------------------------------------------
			
			GUILayout.BeginHorizontal();
			if(GUILayout.Button(new GUIContent("Set Delay", "Changes the delay of the Particle System(s)"), GUILayout.Width(120)))
			{
				applyDelay();
			}
			GUILayout.Label("Delay :",GUILayout.Width(110));
			DelayValue = EditorGUILayout.FloatField(DelayValue,GUILayout.Width(50));
			if(DelayValue < 0.0f) DelayValue = 0.0f;
			else if(DelayValue > 9999f) DelayValue = 9999f;
			GUILayout.EndHorizontal();
			
		//----------------------------------------------------------------
			
			GUILayout.Space(2);
			
			GUILayout.BeginHorizontal();
			if(GUILayout.Button(new GUIContent("Loop", "Loop the effect (might not work properly on some effects such as explosions)"), EditorStyles.miniButtonLeft))
			{
				loopEffect(true);
			}
			if(GUILayout.Button(new GUIContent("Unloop", "Remove looping from the effect"), EditorStyles.miniButtonRight))
			{
				loopEffect(false);
			}
			if(GUILayout.Button(new GUIContent("Prewarm On", "Prewarm the effect (if looped)"), EditorStyles.miniButtonLeft))
			{
				prewarmEffect(true);
			}
			if(GUILayout.Button(new GUIContent("Prewarm Off", "Don't prewarm the effect (if looped)"), EditorStyles.miniButtonRight))
			{
				prewarmEffect(false);
			}
			GUILayout.EndHorizontal();
			
			GUILayout.Space(2);
		
	//----------------------------------------------------------------
		
		}

		//Separator
		GUISeparator();
		//GUILayout.Box("",GUILayout.Width(this.position.width - 12), GUILayout.Height(3));
		
		EditorGUI.BeginChangeCheck();
		colorFoldout = EditorGUILayout.Foldout(colorFoldout, "COLOR EDIT", true);
		if(colorFoldout)
		{
			GUILayout.Space(4);

			//----------------------------------------------------------------

			GUILayout.BeginHorizontal();
			if(GUILayout.Button(new GUIContent("Set Start Color(s)", "Changes the color(s) of the Particle System(s)\nSecond Color is used when Start Color is 'Random Between Two Colors'."),GUILayout.Width(120)))
			{
				applyColor();
			}
			ColorValue = EditorGUILayout.ColorField(ColorValue);
			ColorValue2 = EditorGUILayout.ColorField(ColorValue2);
			AffectAlpha = GUILayout.Toggle(AffectAlpha, new GUIContent("Alpha", "If checked, the alpha value will also be changed"));
			GUILayout.EndHorizontal();

			//----------------------------------------------------------------

			GUILayout.Space(8);

			using(new EditorGUI.DisabledScope(!TintStartColor && !TintColorModule && !TintColorSpeedModule && !TintTrailsModule && !TintCustomData1 && !TintCustomData2))
			{
				GUILayout.BeginHorizontal();
				if(GUILayout.Button(new GUIContent("Tint Colors", "Colorize the Particle System(s) to a specific color, including gradients!\n(preserving their saturation and value)"), GUILayout.Width(120)))
				{
					tintColor();
				}
				TintColorValue = EditorGUILayout.ColorField(TintColorValue);
				TintColorValue = HSLColor.FromRGBA(TintColorValue).VividColor();
				GUILayout.EndHorizontal();

				//----------------------------------------------------------------

				GUILayout.BeginHorizontal();
				if(GUILayout.Button(new GUIContent("Hue Shift", "Tints the colors of the Particle System(s) by shifting the original hues by a value\n(preserving differences between colors)"), GUILayout.Width(120)))
				{
					hueShift();
				}
				TintHueShiftValue = EditorGUILayout.Slider(TintHueShiftValue, -180f, 180f);
				GUILayout.EndHorizontal();

				//----------------------------------------------------------------

				GUILayout.BeginHorizontal();
				if(GUILayout.Button(new GUIContent("Saturation Shift", "Change the saturation of the colors of the Particle System(s)\n(preserving their hue and value)"), GUILayout.Width(120)))
				{
					satShift();
				}
				SatShiftValue = EditorGUILayout.Slider(SatShiftValue, -1, 1);
				GUILayout.EndHorizontal();

				//----------------------------------------------------------------

				GUILayout.BeginHorizontal();
				if(GUILayout.Button(new GUIContent("Value Shift", "Change the value of the colors of the Particle System(s)\n(preserving their hue and saturation)"), GUILayout.Width(120)))
				{
					valShift();
				}
				ValShiftValue = EditorGUILayout.Slider(ValShiftValue, -1, 1);
				GUILayout.EndHorizontal();

				//----------------------------------------------------------------

				GUILayout.BeginHorizontal();
				if (GUILayout.Button(new GUIContent("Gamma -> Linear", "Converts the colors from Gamma to Linear color space")))
				{
					gammaToLinear();
				}
				if (GUILayout.Button(new GUIContent("Linear -> Gamma", "Converts the colors from Linear to Gamma color space")))
				{
					linearToGamma();
				}
				GUILayout.EndHorizontal();
			}

			GUILayout.Space(4);
		
		//----------------------------------------------------------------
			
			/*
			GUILayout.BeginHorizontal();
			GUILayout.Label("Add/Substract Lightness:");
			
			LightnessStep = EditorGUILayout.IntField(LightnessStep, GUILayout.Width(30));
			if(LightnessStep > 99) LightnessStep = 99;
			else if(LightnessStep < 1) LightnessStep = 1;
			GUILayout.Label("%");
			
			if(GUILayout.Button("-", EditorStyles.miniButtonLeft, GUILayout.Width(22)))
			{
				addLightness(true);
			}
			if(GUILayout.Button("+", EditorStyles.miniButtonRight, GUILayout.Width(22)))
			{
				addLightness(false);
			}
			GUILayout.FlexibleSpace();
			GUILayout.EndHorizontal();
			*/
			
		//----------------------------------------------------------------
			
			GUILayout.Label("Color Modules to affect:");
			
			GUILayout.BeginHorizontal();
			GUILayout.Space(4);
			TintStartColor = GUILayout.Toggle(TintStartColor, new GUIContent("Start Color", "If checked, the \"Start Color\" value(s) will be affected."), EditorStyles.toolbarButton);
			TintColorModule = GUILayout.Toggle(TintColorModule, new GUIContent("Color over Lifetime", "If checked, the \"Color over Lifetime\" value(s) will be affected."), EditorStyles.toolbarButton);
			TintColorSpeedModule = GUILayout.Toggle(TintColorSpeedModule, new GUIContent("Color by Speed", "If checked, the \"Color by Speed\" value(s) will be affected."), EditorStyles.toolbarButton);
			GUILayout.Space(4);
			GUILayout.EndHorizontal();
			GUILayout.BeginHorizontal();
			GUILayout.Space(4);
			TintTrailsModule = GUILayout.Toggle(TintTrailsModule, new GUIContent("Trails", "If checked, the \"Trails\" value(s) will be affected."), EditorStyles.toolbarButton);
			TintCustomData1 = GUILayout.Toggle(TintCustomData1, new GUIContent("Custom Data 1", "If checked, the \"Custom Data 1\" value(s) will be affected if they have been set to a color value."), EditorStyles.toolbarButton);
			TintCustomData2 = GUILayout.Toggle(TintCustomData2, new GUIContent("Custom Data 2", "If checked, the \"Custom Data 2\" value(s) will be affected if they have been set to a color value."), EditorStyles.toolbarButton);
			GUILayout.Space(4);
			GUILayout.EndHorizontal();

			TintLights = GUILayout.Toggle(TintLights, new GUIContent(" Tint Lights found in the effect's Hierarchy", "Will search for and tint any Lights found in the effect hierarchy."));

			GUILayout.Space(4);
			
		//----------------------------------------------------------------
			
		}

		GUISeparator();
		
	//----------------------------------------------------------------
		
		EditorGUI.BeginChangeCheck();
		copyFoldout = EditorGUILayout.Foldout(copyFoldout, "COPY MODULES", true);
		if(copyFoldout)
		{
			GUILayout.Space(4);

			EditorGUILayout.HelpBox("Copy selected modules from a Particle System to others!", MessageType.Info);
			
			GUILayout.Label("Source Particle System to copy from:");
			GUILayout.BeginHorizontal();
			{
				EditorGUI.BeginChangeCheck();
				copyModulesSourceParticleSystem = (ParticleSystem)EditorGUILayout.ObjectField(copyModulesSourceParticleSystem, typeof(ParticleSystem), true);
				if (GUILayout.Button("Get Selected", EditorStyles.miniButton))
				{
					if (Selection.activeGameObject != null)
					{
						var ps = Selection.activeGameObject.GetComponent<ParticleSystem>();
						if (ps != null)
						{
							copyModulesSourceParticleSystem = ps;
						}
					}
				}
				if (EditorGUI.EndChangeCheck())
				{
					if (copyModulesSourceParticleSystem != null)
					{
						FetchParticleSystemModules(copyModulesSourceParticleSystem);
						RefreshCurrentlyEnabledModules(copyModulesSourceParticleSystem, ref enabledModules);
					}
				}
			}
			GUILayout.EndHorizontal();
			
			EditorGUILayout.LabelField("Modules to Copy:");
			
			GUILayout.Space(4);

			DrawSelectableModules(ref selectedModules, ref enabledModules, copyModulesSourceParticleSystem);

			/*

			GUILayout.BeginHorizontal();
			GUILayout.Space(8f);
			GUISelectModule(modulesToCopy[0]);
			GUISelectModule(modulesToCopy[1]);
			GUISelectModule(modulesToCopy[2]);
			GUILayout.EndHorizontal();

			SelectModulesSpace();
			GUILayout.BeginHorizontal();
			GUILayout.Space(8f);
#if UNITY_5_4_OR_NEWER
			GUISelectModule(modulesToCopy[3]);
			GUISelectModule(modulesToCopy[4]);
			GUISelectModule(modulesToCopy[16]);
			GUISelectModule(modulesToCopy[5]);
#else
			GUISelectModule(modulesToCopy[3]);
			GUISelectModule(modulesToCopy[4]);
			GUISelectModule(modulesToCopy[5]);
#endif
			GUILayout.EndHorizontal();

			SelectModulesSpace();
			GUILayout.BeginHorizontal();
			GUILayout.Space(8f);
			GUISelectModule(modulesToCopy[6]);
			GUISelectModule(modulesToCopy[7]);
			GUILayout.EndHorizontal();

			SelectModulesSpace();
			GUILayout.BeginHorizontal();
			GUILayout.Space(8f);
			GUISelectModule(modulesToCopy[8]);
			GUISelectModule(modulesToCopy[9]);
			GUILayout.EndHorizontal();

			SelectModulesSpace();
			GUILayout.BeginHorizontal();
			GUILayout.Space(8f);
			GUISelectModule(modulesToCopy[10]);
			GUISelectModule(modulesToCopy[11]);
			GUILayout.EndHorizontal();

			SelectModulesSpace();
			GUILayout.BeginHorizontal();
			GUILayout.Space(8f);
			GUISelectModule(modulesToCopy[12]);
#if UNITY_5_4_OR_NEWER
			GUISelectModule(modulesToCopy[17]);
#endif
			GUISelectModule(modulesToCopy[13]);
			GUILayout.EndHorizontal();

			SelectModulesSpace();
			GUILayout.BeginHorizontal();
			GUILayout.Space(8f);
#if UNITY_5_5_OR_NEWER
			GUISelectModule(modulesToCopy[20]);
			GUISelectModule(modulesToCopy[19]);
			GUISelectModule(modulesToCopy[18]);
#endif
			GUISelectModule(modulesToCopy[14]);
			GUILayout.EndHorizontal();

			SelectModulesSpace();
			GUILayout.BeginHorizontal();
			GUILayout.Space(8f);
			GUISelectModule(modulesToCopy[15]);
			GUILayout.EndHorizontal();

			GUI.color = guiColor;

			SelectModulesSpace();

			*/

			GUILayout.BeginHorizontal();
			{
				GUILayout.FlexibleSpace();
				if (GUILayout.Button("Copy properties to\nselected Object(s)", GUILayout.Height(Mathf.Ceil(EditorGUIUtility.singleLineHeight * 2.1f))))
				{
					bool foundPs = false;
					foreach (GameObject go in Selection.gameObjects)
					{
						var system = go.GetComponent<ParticleSystem>();
						if (system != null)
						{
							foundPs = true;
							CopyModules(copyModulesSourceParticleSystem, system);
						}
					}

					if (!foundPs)
					{
						EditorApplication.Beep();
						Debug.LogWarning("Cartoon FX Easy Editor: No Particle System found in the selected GameObject(s)!");
					}
				}
			}
			GUILayout.EndHorizontal();
		}

		/*

		GUISeparator();

		// TODO full list of the Properties with saving/loading presets

		mutateFoldout = EditorGUILayout.Foldout(mutateFoldout, "MUTATE MODULES", true);
		if (mutateFoldout)
		{
			GUILayout.Space(4);

			DrawSelectableModules(ref selectedModulesMutate, ref enabledModulesMutate, selectedParticleSystem);

			EditorGUI.BeginChangeCheck();
			float newValue = EditorGUILayout.Slider("� percent", Mathf.Floor(mutatePercentage * 100), 0, 100);
			if (EditorGUI.EndChangeCheck())
			{
				mutatePercentage = newValue / 100f;
			}

			if (GUILayout.Button("Mutate Modules", GUILayout.Height(EditorGUIUtility.singleLineHeight*2.1f)))
			{
				MutateModules();
			}
		}

		*/

		//----------------------------------------------------------------

		GUILayout.Space(8);

		//Resize window if needed
		if (Event.current.type == EventType.Repaint)
		{
			float h = GUILayoutUtility.GetLastRect().yMax;
			if (lastHeight != h)
			{
				lastHeight = h;
				this.minSize = new Vector2(300, h);
				this.maxSize = new Vector2(300, h);
			}
		}
	}

	float lastHeight = 0;

	void DrawSelectableModules(ref bool[] selected, ref bool[] enabled, ParticleSystem target)
	{
		if (modulesToCopy != null)
		{
			if (enabled == null || enabled.Length != modulesToCopy.Length)
			{
				enabled = new bool[modulesToCopy.Length];
			}


			// +1 for the Renderer component
			if (selected == null || selected.Length != (modulesToCopy.Length + 1))
			{
				selected = new bool[modulesToCopy.Length + 1];
			}
			int renderer = selected.Length - 1;

			// quick select:
			GUILayout.BeginHorizontal();
			GUILayout.Label("Select:", GUILayout.Width(60));
			if (GUILayout.Button(" All ", EditorStyles.miniButton))
			{
				for (int i = 0; i < modulesToCopy.Length; i++)
				{
					if (!pref_HideDisabledModulesCopy || enabled[i])
					{
						selected[i] = true;
					}
					selected[renderer] = true;
				}
			}

			var guiColor = GUI.color;
			GUI.color *= new Color(1.0f, 0.5f, 0.5f);
			if (GUILayout.Button(" None ", EditorStyles.miniButton))
			{
				for (int i = 0; i < modulesToCopy.Length; i++)
				{
					selected[i] = false;
				}
				selected[renderer] = false;
			}
			GUILayout.EndHorizontal();
			GUI.color = guiColor;

			GUILayout.BeginHorizontal();
			GUILayout.Label(" ", GUILayout.Width(60));
			if (GUILayout.Button(" Size ", EditorStyles.miniButton))
			{
				for (int i = 0; i < modulesToCopy.Length; i++)
				{
					switch (modulesToCopy[i].serializedPropertyPath)
					{
						case "SizeModule":
						case "SizeBySpeedModule":
							selected[i] = true;
							break;
					}
				}
			}
			if (GUILayout.Button(" Movement ", EditorStyles.miniButton))
			{
				for (int i = 0; i < modulesToCopy.Length; i++)
				{
					switch (modulesToCopy[i].serializedPropertyPath)
					{
						case "VelocityModule":
						case "InheritVelocityModule":
						case "ForceModule":
						case "ExternalForcesModule":
						case "ClampVelocityModule":
						case "NoiseModule":
						case "CollisionModule":
							selected[i] = true;
							break;
					}
				}
			}
			if (GUILayout.Button(" Color/Appearance ", EditorStyles.miniButton))
			{
				for (int i = 0; i < modulesToCopy.Length; i++)
				{
					switch (modulesToCopy[i].serializedPropertyPath)
					{
						case "ColorModule":
						case "ColorBySpeedModule":
						case "TrailModule":
						case "LightsModule":
						case "UVModule":
							selected[i] = true;
							break;
					}
					selected[renderer] = true;
				}
			}
			GUILayout.EndHorizontal();

			using (new EditorGUI.DisabledScope(target == null))
			{
				EditorGUI.BeginChangeCheck();
				pref_HideDisabledModulesCopy = GUILayout.Toggle(pref_HideDisabledModulesCopy, new GUIContent(" Hide disabled modules", "Will hide modules that are disabled on the current source Particle System"));
				if (EditorGUI.EndChangeCheck())
				{
					EditorPrefs.SetBool("CFX_HideDisabledModulesCopy", pref_HideDisabledModulesCopy);
					RefreshCurrentlyEnabledModules(target, ref enabled);
				}
			}

			const int row = 4;
			const int padding = 4;
			for (int i = 0; i < modulesToCopy.Length; i += row)
			{
				GUILayout.BeginHorizontal();
				{
					GUILayout.Space(padding);
					for (int j = 0; j < row; j++)
					{
						if (i+j < modulesLabels.Length)
						{
							var col = GUI.color;

							// special case: renderer
							/*
							if (i+j == modulesLabels.Length-1)
							{
								if (selectedModuleRenderer) GUI.color *= Color.cyan;
								selectedModuleRenderer = GUILayout.Toggle(selectedModuleRenderer, modulesLabels[i+j], EditorStyles.toolbarButton);
							}
							else
							*/
							{
								bool enbl = (i+j) >= enabled.Length ? true : enabled[i+j];
								if (pref_HideDisabledModulesCopy && !enbl)
								{
									continue;
								}
								if (selected[i+j]) GUI.color *= Color.cyan;
								selected[i+j] = GUILayout.Toggle(selected[i+j], modulesLabels[i+j], EditorStyles.toolbarButton);
							}
							GUI.color = col;
						}
					}
					GUILayout.Space(padding);
				}
				GUILayout.EndHorizontal();
			}
		}
		else
		{
			EditorGUILayout.HelpBox("Select a Particle System to initialize the module list.", MessageType.Info);
		}
	}

	void RefreshCurrentlyEnabledModules(ParticleSystem source, ref bool[] enabledModules)
	{
		if (modulesToCopy == null)
		{
			return;
		}

		if (enabledModules == null || enabledModules.Length != modulesToCopy.Length)
		{
			// note: no need for extra slot for renderer, consider it always enabled
			enabledModules = new bool[modulesToCopy.Length];
		}

		if (source != null)
		{
			var so = new SerializedObject(source);
			for (int i = 0; i < modulesToCopy.Length; i++)
			{
				enabledModules[i] = modulesToCopy[i].CheckIfEnabledInSource(so);
			}
		}
		else
		{
			for (int i = 0; i < enabledModules.Length; i++)
			{
				enabledModules[i] = true;
			}
		}
	}

	//Loop effects
	private void loopEffect(bool setLoop)
	{
		foreach(GameObject go in Selection.gameObjects)
		{
			//Scale Shuriken Particles Values
			ParticleSystem[] systems;
			if(pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();
			
			foreach(ParticleSystem ps in systems)
			{
				SerializedObject so = new SerializedObject(ps);
				so.FindProperty("looping").boolValue = setLoop;
				so.ApplyModifiedProperties();
			}
		}
	}
	
	//Prewarm effects
	private void prewarmEffect(bool setPrewarm)
	{
		foreach(GameObject go in Selection.gameObjects)
		{
			//Scale Shuriken Particles Values
			ParticleSystem[] systems;
			if(pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();
			
			foreach(ParticleSystem ps in systems)
			{
				SerializedObject so = new SerializedObject(ps);
				so.FindProperty("prewarm").boolValue = setPrewarm;
				so.ApplyModifiedProperties();
			}
		}
	}
	
	//Scale Size
	private void applyScale()
	{
		Undo.IncrementCurrentGroup();
		int groupId = Undo.GetCurrentGroup();

		foreach(GameObject go in Selection.gameObjects)
		{
			//Scale Shuriken Particles Values
			ParticleSystem[] systems;
			if(pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();
			
			foreach(ParticleSystem ps in systems)
			{
				Undo.RegisterCompleteObjectUndo(ps, "Apply Particle Scale");
				Undo.RegisterCompleteObjectUndo(ps.transform, "Apply Particle Scale");
				ScaleParticleValues(ps, go);
			}
			
			//Scale Lights' range
			Light[] lights = go.GetComponentsInChildren<Light>();
			foreach(Light light in lights)
			{
				Undo.RegisterCompleteObjectUndo(light, "Apply Particle Scale");
				Undo.RegisterCompleteObjectUndo(light.transform, "Apply Particle Scale");
				light.range *= ScalingValue;
				light.transform.localPosition *= ScalingValue;
			}
		}

		Undo.CollapseUndoOperations(groupId);
	}

	//Change Color
	private void applyColor()
	{
		foreach(GameObject go in Selection.gameObjects)
		{
			ParticleSystem[] systems;
			if(pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();
			
			foreach(ParticleSystem ps in systems)
			{
				SerializedObject psSerial = new SerializedObject(ps);
				if(!AffectAlpha)
				{
					psSerial.FindProperty("InitialModule.startColor.maxColor").colorValue = new Color(ColorValue.r, ColorValue.g, ColorValue.b, psSerial.FindProperty("InitialModule.startColor.maxColor").colorValue.a);
					psSerial.FindProperty("InitialModule.startColor.minColor").colorValue = new Color(ColorValue2.r, ColorValue2.g, ColorValue2.b, psSerial.FindProperty("InitialModule.startColor.minColor").colorValue.a);
				}
				else
				{
					psSerial.FindProperty("InitialModule.startColor.maxColor").colorValue = ColorValue;
					psSerial.FindProperty("InitialModule.startColor.minColor").colorValue = ColorValue2;
				}
				psSerial.ApplyModifiedProperties();
			}
		}
	}
	
	//TINT COLORS ================================================================================================================================
	
	private void tintColor()
	{
		float hue = HSLColor.FromRGBA(TintColorValue).h;
		GenericProcessColors(color =>
		{
			return HSLColor.FromRGBA(color).ColorWithHue(hue, false);
		});
	}

	private void hueShift()
	{
		GenericProcessColors(color =>
		{
			return HSLColor.FromRGBA(color).ColorWithHue(TintHueShiftValue, true);
		});
	}

	private void satShift()
	{
		GenericProcessColors(color =>
		{
			return HSLColor.FromRGBA(color).ColorWithSaturationOffset(SatShiftValue);
		});
	}

	private void valShift()
	{
		GenericProcessColors(color =>
		{
			return HSLColor.FromRGBA(color).ColorWithLightnessOffset(ValShiftValue);
		});
	}

	private void GenericTintColorProperty(SerializedProperty colorProperty, float hue, bool shift)
	{
		GenericEditColorProperty(colorProperty, color =>
		{
			return HSLColor.FromRGBA(color).ColorWithHue(hue, shift);
		});
	}

	private void GenericEditGradient(SerializedProperty gradientProperty, System.Func<Color, Color> callback)
	{
		gradientProperty.FindPropertyRelative("key0").colorValue = callback(gradientProperty.FindPropertyRelative("key0").colorValue);
		gradientProperty.FindPropertyRelative("key1").colorValue = callback(gradientProperty.FindPropertyRelative("key1").colorValue);
		gradientProperty.FindPropertyRelative("key2").colorValue = callback(gradientProperty.FindPropertyRelative("key2").colorValue);
		gradientProperty.FindPropertyRelative("key3").colorValue = callback(gradientProperty.FindPropertyRelative("key3").colorValue);
		gradientProperty.FindPropertyRelative("key4").colorValue = callback(gradientProperty.FindPropertyRelative("key4").colorValue);
		gradientProperty.FindPropertyRelative("key5").colorValue = callback(gradientProperty.FindPropertyRelative("key5").colorValue);
		gradientProperty.FindPropertyRelative("key6").colorValue = callback(gradientProperty.FindPropertyRelative("key6").colorValue);
		gradientProperty.FindPropertyRelative("key7").colorValue = callback(gradientProperty.FindPropertyRelative("key7").colorValue);
	}

	private void gammaToLinear()
	{
		GenericProcessColors(color =>
		{
			return color.gamma;
		});
	}

	private void linearToGamma()
	{
		GenericProcessColors(color =>
		{
			return color.linear;
		});
	}

	private void GenericProcessColors(System.Func<Color, Color> callback)
	{
		foreach (GameObject go in Selection.gameObjects)
		{
			ParticleSystem[] systems;
			if (pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();

			foreach (ParticleSystem ps in systems)
			{
				SerializedObject psSerial = new SerializedObject(ps);

				if (TintStartColor)					GenericEditColorProperty(psSerial.FindProperty("InitialModule.startColor"), callback);
				if (TintColorModule)				GenericEditColorProperty(psSerial.FindProperty("ColorModule.gradient"), callback);
				if (TintColorSpeedModule)			GenericEditColorProperty(psSerial.FindProperty("ColorBySpeedModule.gradient"), callback);
				if (TintCustomData1)				GenericEditColorProperty(psSerial.FindProperty("CustomDataModule.color0"), callback);
				if (TintCustomData2)				GenericEditColorProperty(psSerial.FindProperty("CustomDataModule.color1"), callback);

				psSerial.ApplyModifiedProperties();
			}

			if (TintLights)
			{
				var lights = go.GetComponentsInChildren<Light>();
				foreach (var light in lights)
				{
					var psLight = new SerializedObject(light);
					var colorProperty = psLight.FindProperty("m_Color");
					colorProperty.colorValue = callback(colorProperty.colorValue);
					psLight.ApplyModifiedProperties();
				}
			}
		}
	}

	private void GenericEditColorProperty(SerializedProperty colorProperty, System.Func<Color, Color> callback)
	{
		int state = colorProperty.FindPropertyRelative("minMaxState").intValue;
		switch (state)
		{
			//Constant Color
			case 0:
				colorProperty.FindPropertyRelative("maxColor").colorValue = callback(colorProperty.FindPropertyRelative("maxColor").colorValue);
				break;

			//Gradient
			case 1:
				GenericEditGradient(colorProperty.FindPropertyRelative("maxGradient"), callback);
				break;

			//Random between 2 Colors
			case 2:
				colorProperty.FindPropertyRelative("minColor").colorValue = callback(colorProperty.FindPropertyRelative("minColor").colorValue);
				colorProperty.FindPropertyRelative("maxColor").colorValue = callback(colorProperty.FindPropertyRelative("maxColor").colorValue);
				break;

			//Random between 2 Gradients
			case 3:
				GenericEditGradient(colorProperty.FindPropertyRelative("maxGradient"), callback);
				GenericEditGradient(colorProperty.FindPropertyRelative("minGradient"), callback);
				break;
		}
	}
	
	//LIGHTNESS OFFSET ================================================================================================================================
	
	private void addLightness(bool substract)
	{
		if(!TintStartColor && !TintColorModule && !TintColorSpeedModule)
		{
			Debug.LogWarning("Cartoon FX Easy Editor: You must toggle at least one of the three Color Modules to be able to change lightness!");
			return;
		}
		
		float lightness = (float)(LightnessStep/100f);
		if(substract)
			lightness *= -1f;
		
		foreach(GameObject go in Selection.gameObjects)
		{
			ParticleSystem[] systems;
			if(pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();
			
			foreach(ParticleSystem ps in systems)
			{
				SerializedObject psSerial = new SerializedObject(ps);
				
				if(TintStartColor)
					GenericAddLightness(psSerial.FindProperty("InitialModule.startColor"), lightness);
				
				if(TintColorModule)
					GenericAddLightness(psSerial.FindProperty("ColorModule.gradient"), lightness);
				
				if(TintColorSpeedModule)
					GenericAddLightness(psSerial.FindProperty("ColorBySpeedModule.gradient"), lightness);
				
				psSerial.ApplyModifiedProperties();
				psSerial.Update();
			}
		}
	}
	
	private void GenericAddLightness(SerializedProperty colorProperty, float lightness)
	{
		int state = colorProperty.FindPropertyRelative("minMaxState").intValue;
		switch(state)
		{
			//Constant Color
		case 0:
			colorProperty.FindPropertyRelative("maxColor").colorValue = HSLColor.FromRGBA(colorProperty.FindPropertyRelative("maxColor").colorValue).ColorWithLightnessOffset(lightness);
			break;
			
			//Gradient
		case 1:
			AddLightnessGradient(colorProperty.FindPropertyRelative("maxGradient"), lightness);
			break;
			
			//Random between 2 Colors
		case 2:
			colorProperty.FindPropertyRelative("minColor").colorValue = HSLColor.FromRGBA(colorProperty.FindPropertyRelative("minColor").colorValue).ColorWithLightnessOffset(lightness);
			colorProperty.FindPropertyRelative("maxColor").colorValue = HSLColor.FromRGBA(colorProperty.FindPropertyRelative("maxColor").colorValue).ColorWithLightnessOffset(lightness);
			break;
			
			//Random between 2 Gradients
		case 3:
			AddLightnessGradient(colorProperty.FindPropertyRelative("maxGradient"), lightness);
			AddLightnessGradient(colorProperty.FindPropertyRelative("minGradient"), lightness);
			break;
		}
	}
	
	private void AddLightnessGradient(SerializedProperty gradientProperty, float lightness)
	{
		gradientProperty.FindPropertyRelative("key0").colorValue = HSLColor.FromRGBA(gradientProperty.FindPropertyRelative("key0").colorValue).ColorWithLightnessOffset(lightness);
		gradientProperty.FindPropertyRelative("key1").colorValue = HSLColor.FromRGBA(gradientProperty.FindPropertyRelative("key1").colorValue).ColorWithLightnessOffset(lightness);
		gradientProperty.FindPropertyRelative("key2").colorValue = HSLColor.FromRGBA(gradientProperty.FindPropertyRelative("key2").colorValue).ColorWithLightnessOffset(lightness);
		gradientProperty.FindPropertyRelative("key3").colorValue = HSLColor.FromRGBA(gradientProperty.FindPropertyRelative("key3").colorValue).ColorWithLightnessOffset(lightness);
		gradientProperty.FindPropertyRelative("key4").colorValue = HSLColor.FromRGBA(gradientProperty.FindPropertyRelative("key4").colorValue).ColorWithLightnessOffset(lightness);
		gradientProperty.FindPropertyRelative("key5").colorValue = HSLColor.FromRGBA(gradientProperty.FindPropertyRelative("key5").colorValue).ColorWithLightnessOffset(lightness);
		gradientProperty.FindPropertyRelative("key6").colorValue = HSLColor.FromRGBA(gradientProperty.FindPropertyRelative("key6").colorValue).ColorWithLightnessOffset(lightness);
		gradientProperty.FindPropertyRelative("key7").colorValue = HSLColor.FromRGBA(gradientProperty.FindPropertyRelative("key7").colorValue).ColorWithLightnessOffset(lightness);
	}
	
	//RGB / HSL Conversions
	private struct HSLColor
	{
		public float h;
		public float s;
		public float l;
		public float a;
		
		public HSLColor(float h, float s, float l, float a)
		{
			this.h = h;
			this.s = s;
			this.l = l;
			this.a = a;
		}
		
		public HSLColor(float h, float s, float l)
		{
			this.h = h;
			this.s = s;
			this.l = l;
			this.a = 1f;
		}
		
		public HSLColor(Color c)
		{
			HSLColor temp = FromRGBA(c);
			h = temp.h;
			s = temp.s;
			l = temp.l;
			a = temp.a;
		}
		
		public static HSLColor FromRGBA(Color c)
		{
			float h, s, l, a;
			a = c.a;
			
			float cmin = Mathf.Min(Mathf.Min(c.r, c.g), c.b);
			float cmax = Mathf.Max(Mathf.Max(c.r, c.g), c.b);
			
			l = (cmin + cmax) / 2f;
			
			if (cmin == cmax)
			{
				s = 0;
				h = 0;
			}
			else
			{
				float delta = cmax - cmin;
				
				s = (l <= .5f) ? (delta / (cmax + cmin)) : (delta / (2f - (cmax + cmin)));
				
				h = 0;
				
				if (c.r == cmax)
				{
					h = (c.g - c.b) / delta;
				}
				else if (c.g == cmax)
				{
					h = 2f + (c.b - c.r) / delta;
				}
				else if (c.b == cmax)
				{
					h = 4f + (c.r - c.g) / delta;
				}
				
				h = Mathf.Repeat(h * 60f, 360f);
			}
			
			return new HSLColor(h, s, l, a);
		}
		
		public Color ToRGBA()
		{
			float r, g, b, a;
			a = this.a;
			
			float m1, m2;
			
			m2 = (l <= .5f) ? (l * (1f + s)) : (l + s - l * s);
			m1 = 2f * l - m2;
			
			if (s == 0f)
			{
				r = g = b = l;
			}
			else
			{
				r = Value(m1, m2, h + 120f);
				g = Value(m1, m2, h);
				b = Value(m1, m2, h - 120f);
			}
			
			return new Color(r, g, b, a);
		}
		
		static float Value(float n1, float n2, float hue)
		{
			hue = Mathf.Repeat(hue, 360f);
			
			if (hue < 60f)
			{
				return n1 + (n2 - n1) * hue / 60f;
			}
			else if (hue < 180f)
			{
				return n2;
			}
			else if (hue < 240f)
			{
				return n1 + (n2 - n1) * (240f - hue) / 60f;
			}
			else
			{
				return n1;
			}
		}
		
		public Color VividColor()
		{
			this.l = 0.5f;
			this.s = 1.0f;
			return this.ToRGBA();
		}
		
		public Color ColorWithHue(float hue, bool shift)
		{
			if(shift)
			{
				this.h += hue;
				while(this.h >= 360f)
					this.h -= 360f;
				while(this.h <= -360f)
					this.h += 360f;
			}
			else
				this.h = hue;

			return this.ToRGBA();
		}

		public Color ColorWithLightnessOffset(float lightness)
		{
			this.l += lightness;
			if(this.l > 1.0f) this.l = 1.0f;
			else if(this.l < 0.0f) this.l = 0.0f;
			
			return this.ToRGBA();
		}

		public Color ColorWithSaturationOffset(float saturation)
		{
			this.s += saturation;
			if(this.s > 1.0f) this.s = 1.0f;
			else if(this.s < 0.0f) this.s = 0.0f;
			
			return this.ToRGBA();
		}
		
		public static implicit operator HSLColor(Color src)
		{
			return FromRGBA(src);
		}
		
		public static implicit operator Color(HSLColor src)
		{
			return src.ToRGBA();
		}
	}
	
	//Scale Lifetime only
	private void applySpeed()
	{
		foreach(GameObject go in Selection.gameObjects)
		{
			ParticleSystem[] systems;
			if(pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();
			
			//Scale Lifetime
			foreach(ParticleSystem ps in systems)
			{
#if UNITY_5_5_OR_NEWER
				var main = ps.main;
				main.simulationSpeed = LTScalingValue;
#else
				ps.playbackSpeed = LTScalingValue;
#endif
			}
		}
	}
	
	//Set Duration
	private void applyDuration()
	{
		foreach(GameObject go in Selection.gameObjects)
		{
			//Scale Shuriken Particles Values
			ParticleSystem[] systems;
			if(pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();
			
			foreach(ParticleSystem ps in systems)
			{
				SerializedObject so = new SerializedObject(ps);
				so.FindProperty("lengthInSec").floatValue = DurationValue;
				so.ApplyModifiedProperties();
			}
		}
	}
	
	//Change delay
	private void applyDelay()
	{
		foreach(GameObject go in Selection.gameObjects)
		{
			ParticleSystem[] systems;
			if(pref_IncludeChildren)
				systems = go.GetComponentsInChildren<ParticleSystem>(true);
			else
				systems = go.GetComponents<ParticleSystem>();
			
			//Scale Lifetime
			foreach(ParticleSystem ps in systems)
			{
#if UNITY_5_5_OR_NEWER
				var main = ps.main;
				main.startDelay = DelayValue;
#else
				ps.startDelay = DelayValue;
#endif
			}
		}
	}
	
	//Copy Selected Modules
	private void CopyModules(ParticleSystem source, ParticleSystem dest)
	{
		if(source == null)
		{
			Debug.LogWarning("Cartoon FX Easy Editor: Select a source Particle System to copy properties from first!");
			return;
		}
		
		SerializedObject psSource = new SerializedObject(source);
		SerializedObject psDest = new SerializedObject(dest);

		for (int i = 0; i < modulesToCopy.Length; i++)
		{
			var module = modulesToCopy[i];

			if (!selectedModules[i])
			{
				continue;
			}

			var sp = psSource.FindProperty(module.serializedPropertyPath);
			if (sp == null)
			{
				Debug.LogError("Couldn't find module in SerializedObject: " + module.serializedPropertyPath);
				continue;
			}

			psDest.CopyFromSerializedProperty(sp);

			// special case: extra properties for initial module
			if (i == 0)
			{
				foreach (var path in initialModuleExtraProperties)
				{
					var extraSp = psSource.FindProperty(path);
					if (extraSp != null)
					{
						psDest.CopyFromSerializedProperty(extraSp);
					}
				}
			}

			psDest.ApplyModifiedProperties();
		}

		//Renderer
		if (selectedModules[selectedModules.Length-1])
		{
			ParticleSystemRenderer rendSource = source.GetComponent<ParticleSystemRenderer>();
			ParticleSystemRenderer rendDest = dest.GetComponent<ParticleSystemRenderer>();
			UnityEditorInternal.ComponentUtility.CopyComponent(rendSource);
			UnityEditorInternal.ComponentUtility.PasteComponentValues(rendDest);
		}
	}

	/*
	private void MutateModules()
	{
		var systems = pref_IncludeChildren ? Selection.activeGameObject.GetComponentsInChildren<ParticleSystem>() : Selection.activeGameObject.GetComponents<ParticleSystem>();
		var so = new SerializedObject(systems);

		for (int i = 0; i < modulesToCopy.Length; i++)
		{
			if (!selectedModulesMutate[i])
			{
				continue;
			}

			var sp = so.FindProperty(modulesToCopy[i].serializedPropertyPath);
			if (sp != null)
			{
				if (sp.FindPropertyRelative("enabled").boolValue)
				{
					GenericMutateProperty(sp);
					sp.serializedObject.ApplyModifiedProperties();
				}
			}
		}
	}

	private void GenericMutateProperty(SerializedProperty sp, bool recursive = true)
	{
		System.Func<int, int> randomizeInt = intValue =>
		{
			return intValue + Mathf.RoundToInt(intValue * Random.Range(-mutatePercentage, +mutatePercentage));
		};
		System.Func<float, float> randomizeFloat = floatValue =>
		{
			return floatValue + (floatValue * Random.Range(-mutatePercentage, +mutatePercentage));
		};
		System.Func<float, float> randomizeHue = hueValue =>
		{
			hueValue = randomizeFloat(hueValue);
			while (hueValue > 1) hueValue -= 1;
			while (hueValue < 0) hueValue += 1;
			return hueValue;
		};

		//Debug.Log(new string('\t', sp.depth) + sp.name + ": " + sp.type + ", " + sp.propertyType);

		switch (sp.type)
		{
			case "MinMaxCurve":
				GenericMutateProperty(sp.FindPropertyRelative("scalar"), false);
				GenericMutateProperty(sp.FindPropertyRelative("minScalar"), false);
				return;

			case "MinMaxGradient":
				GenericEditColorProperty(sp, color =>
				{
					float h, s, v;
					Color.RGBToHSV(color, out h, out s, out v);
					h = randomizeHue(h);
					return Color.HSVToRGB(h, s, v);
				});
				return;
		}

		switch (sp.propertyType)
		{
			case SerializedPropertyType.Float: sp.floatValue = randomizeFloat(sp.floatValue); break;
			case SerializedPropertyType.Integer: sp.intValue = randomizeInt(sp.intValue); break;
			case SerializedPropertyType.Vector2: sp.vector2Value += new Vector2(randomizeFloat(sp.vector2Value.x), randomizeFloat(sp.vector2Value.y)); break;
			case SerializedPropertyType.Vector3: sp.vector3Value += new Vector3(randomizeFloat(sp.vector3Value.x), randomizeFloat(sp.vector3Value.y), randomizeFloat(sp.vector3Value.z)); break;
			case SerializedPropertyType.Vector4: sp.vector4Value += new Vector4(randomizeFloat(sp.vector4Value.x), randomizeFloat(sp.vector4Value.y), randomizeFloat(sp.vector4Value.z), randomizeFloat(sp.vector4Value.w)); break;
		}

		if (recursive && sp.hasVisibleChildren)
		{
			var endProp = sp.GetEndProperty();
			while (!SerializedProperty.EqualContents(endProp, sp) && sp.NextVisible(true))
			{
				GenericMutateProperty(sp, false);
			}
		}
	}
	*/

	//Scale System
	private void ScaleParticleValues(ParticleSystem ps, GameObject parent)
	{
		//Particle System
		if (ps.gameObject != parent)
		{
			ps.transform.localPosition *= ScalingValue;
		}

		SerializedObject psSerial = new SerializedObject(ps);

		foreach(var path in PropertiesToScale)
		{
			var prop = psSerial.FindProperty(path);
			if(prop != null)
			{
				if(prop.propertyType == SerializedPropertyType.Float)
				{
					prop.floatValue *= ScalingValue;
				}
				else
				{
					Debug.LogWarning("Property in ParticleSystem is not a float: " + path + "\n");
				}
			}
			else
			{
				Debug.LogWarning("Property doesn't exist in ParticleSystem: " + path + "\n");
			}
		}

		//Shape Module special case
		/*
		if(psSerial.FindProperty("ShapeModule.enabled").boolValue)
		{
			//(ShapeModule.type 6 == Mesh)
			if(psSerial.FindProperty("ShapeModule.type").intValue == 6)
			{
				//Unity 4+ : changing the Transform scale will affect the shape Mesh
				ps.transform.localScale = ps.transform.localScale * ScalingValue;
				EditorUtility.SetDirty(ps.transform);
			}
		}
		*/
		
		//Apply Modified Properties
		psSerial.ApplyModifiedPropertiesWithoutUndo();
	}

	//Properties to scale, with per-version differences
	private string[] PropertiesToScale = new string[]
	{
		//Initial
		"InitialModule.startSize.scalar",
		"InitialModule.startSpeed.scalar",
#if UNITY_2017_1_OR_NEWER
		"InitialModule.startSize.minScalar",
		"InitialModule.startSpeed.minScalar",

		"InitialModule.startSizeY.scalar",
		"InitialModule.startSizeY.minScalar",
		"InitialModule.startSizeZ.scalar",
		"InitialModule.startSizeZ.minScalar",
#endif
		//Size by Speed
		"SizeBySpeedModule.range.x",
		"SizeBySpeedModule.range.y",
		//Velocity over Lifetime
		"VelocityModule.x.scalar",
		"VelocityModule.y.scalar",
		"VelocityModule.z.scalar",
#if UNITY_2017_1_OR_NEWER
		"VelocityModule.x.minScalar",
		"VelocityModule.y.minScalar",
		"VelocityModule.z.minScalar",

		"VelocityModule.orbitalX.scalar",
		"VelocityModule.orbitalX.minScalar",
		"VelocityModule.orbitalY.scalar",
		"VelocityModule.orbitalY.minScalar",
		"VelocityModule.orbitalZ.scalar",
		"VelocityModule.orbitalZ.minScalar",

		"VelocityModule.orbitalOffsetX.scalar",
		"VelocityModule.orbitalOffsetX.minScalar",
		"VelocityModule.orbitalOffsetY.scalar",
		"VelocityModule.orbitalOffsetY.minScalar",
		"VelocityModule.orbitalOffsetZ.scalar",
		"VelocityModule.orbitalOffsetZ.minScalar",

		"VelocityModule.radial.scalar",
		"VelocityModule.radial.minScalar",

		"VelocityModule.speedModifier.scalar",
		"VelocityModule.speedModifier.minScalar",

		"InheritVelocityModule.m_Curve.scalar",
		"InheritVelocityModule.m_Curve.minScalar",

		"ExternalForcesModule.multiplier",

		"NoiseModule.strength.scalar",
		"NoiseModule.strength.minScalar",
		"NoiseModule.strengthY.scalar",
		"NoiseModule.strengthY.minScalar",
		"NoiseModule.strengthZ.scalar",
		"NoiseModule.strengthZ.minScalar",
#endif
		//Limit Velocity over Lifetime
		"ClampVelocityModule.x.scalar",
		"ClampVelocityModule.y.scalar",
		"ClampVelocityModule.z.scalar",
#if UNITY_2017_1_OR_NEWER
		"ClampVelocityModule.x.minScalar",
		"ClampVelocityModule.y.minScalar",
		"ClampVelocityModule.z.minScalar",
#endif
		"ClampVelocityModule.magnitude.scalar",
		//Force over Lifetime
		"ForceModule.x.scalar",
		"ForceModule.y.scalar",
		"ForceModule.z.scalar",
#if UNITY_2017_1_OR_NEWER
		"ForceModule.x.minScalar",
		"ForceModule.y.minScalar",
		"ForceModule.z.minScalar",
#endif
		//Special cases per Unity version
#if UNITY_2017_1_OR_NEWER
		"ShapeModule.m_Scale.x",
		"ShapeModule.m_Scale.y",
		"ShapeModule.m_Scale.z",
#else
		"ShapeModule.boxX",
		"ShapeModule.boxY",
		"ShapeModule.boxZ",
#if UNITY_5_6_OR_NEWER
		"ShapeModule.radius.value",
#else
		"ShapeModule.radius",
#endif
#endif
#if UNITY_5_5_OR_NEWER
		"EmissionModule.rateOverDistance.scalar",
		"InitialModule.gravityModifier.scalar",
	#if UNITY_2017_1_OR_NEWER
		"EmissionModule.rateOverDistance.minScalar",
		"InitialModule.gravityModifier.minScalar",
	#endif
#else
		"InitialModule.gravityModifier",
		"EmissionModule.rate.scalar",
#endif
	};

	void GUISeparator()
	{
		GUILayout.Space(4);
		if(EditorGUIUtility.isProSkin)
		{
			GUILine(new Color(.15f, .15f, .15f), 1);
			GUILine(new Color(.4f, .4f, .4f), 1);
		}
		else
		{
			GUILine(new Color(.3f, .3f, .3f), 1);
			GUILine(new Color(.9f, .9f, .9f), 1);
		}
		GUILayout.Space(4);
	}

	static public void GUILine(Color color, float height = 2f)
	{
		Rect position = GUILayoutUtility.GetRect(0f, float.MaxValue, height, height, LineStyle);

		if(Event.current.type == EventType.Repaint)
		{
			Color orgColor = GUI.color;
			GUI.color = orgColor * color;
			LineStyle.Draw(position, false, false, false, false);
			GUI.color = orgColor;
		}
	}

	static public GUIStyle _LineStyle;
	static public GUIStyle LineStyle
	{
		get
		{
			if(_LineStyle == null)
			{
				_LineStyle = new GUIStyle();
				_LineStyle.normal.background = EditorGUIUtility.whiteTexture;
				_LineStyle.stretchWidth = true;
			}

			return _LineStyle;
		}
	}
}