/******************************************************************************
 * Spine Runtimes License Agreement
 * Last updated April 5, 2025. Replaces all prior versions.
 *
 * Copyright (c) 2013-2025, Esoteric Software LLC
 *
 * Integration of the Spine Runtimes into software or otherwise creating
 * derivative works of the Spine Runtimes is permitted under the terms and
 * conditions of Section 2 of the Spine Editor License Agreement:
 * http://esotericsoftware.com/spine-editor-license
 *
 * Otherwise, it is permitted to integrate the Spine Runtimes into software
 * or otherwise create derivative works of the Spine Runtimes (collectively,
 * "Products"), provided that each user of the Products must obtain their own
 * Spine Editor license and redistribution of the Products in any form must
 * include this license and copyright notice.
 *
 * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES,
 * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THE SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

// Contributed by: Mitch Thompson

using UnityEditor;
using UnityEngine;

namespace Spine.Unity.Editor {
	[CustomEditor(typeof(SkeletonMecanim))]
	[CanEditMultipleObjects]
	public class SkeletonMecanimInspector : SkeletonRendererInspector {
		public static bool mecanimSettingsFoldout;
		public static bool enableScenePreview;

		protected SerializedProperty autoReset;
		protected SerializedProperty useCustomMixMode;
		protected SerializedProperty layerMixModes;
		protected SerializedProperty layerBlendModes;

		protected override void OnEnable () {
			base.OnEnable();
			SerializedProperty mecanimTranslator = serializedObject.FindProperty("translator");
			autoReset = mecanimTranslator.FindPropertyRelative("autoReset");
			useCustomMixMode = mecanimTranslator.FindPropertyRelative("useCustomMixMode");
			layerMixModes = mecanimTranslator.FindPropertyRelative("layerMixModes");
			layerBlendModes = mecanimTranslator.FindPropertyRelative("layerBlendModes");
		}

		protected override void DrawInspectorGUI (bool multi) {
			AddRootMotionComponentIfEnabled();

			base.DrawInspectorGUI(multi);

			using (new SpineInspectorUtility.BoxScope()) {
				mecanimSettingsFoldout = EditorGUILayout.Foldout(mecanimSettingsFoldout, "Mecanim Translator");
				if (mecanimSettingsFoldout) {
					EditorGUILayout.PropertyField(autoReset, new GUIContent("Auto Reset",
						"When set to true, the skeleton state is mixed out to setup-" +
						"pose when an animation finishes, according to the " +
						"animation's keyed items."));

					EditorGUILayout.PropertyField(useCustomMixMode, new GUIContent("Custom MixMode",
						"When disabled, the recommended MixMode is used according to the layer blend mode. Enable to specify a custom MixMode for each Mecanim layer."));

					if (useCustomMixMode.hasMultipleDifferentValues || useCustomMixMode.boolValue == true) {
						DrawLayerSettings();
						EditorGUILayout.Space();
					}
				}
			}

			EditorGUI.BeginChangeCheck();
			enableScenePreview = EditorGUILayout.Toggle(new GUIContent("Scene Preview",
				"Preview the Animation Clip selected in the Animation window. Lock this SkeletonMecanim Inspector " +
				"window, open the Animation window and select the Animation Clip. Then in the Animation window " +
				"scrub through the timeline."),
				enableScenePreview, GUILayout.MaxWidth(150f));
			bool wasScenePreviewChanged = EditorGUI.EndChangeCheck();
			if (enableScenePreview)
				HandleAnimationPreview();
			else if (wasScenePreviewChanged) // just disabled, back to setup pose
				PreviewAnimationInScene(null, 0.0f);
		}

		protected void AddRootMotionComponentIfEnabled () {
			foreach (UnityEngine.Object t in targets) {
				Component component = t as Component;
				Animator animator = component.GetComponent<Animator>();
				if (animator != null && animator.applyRootMotion) {
					if (component.GetComponent<SkeletonMecanimRootMotion>() == null) {
						component.gameObject.AddComponent<SkeletonMecanimRootMotion>();
					}
				}
			}
		}

		protected void HandleAnimationPreview () {
			UnityEngine.Object animationWindow = AnimationWindowPreview.GetOpenAnimationWindow();

			AnimationClip selectedClip = null;
			if (animationWindow != null) {
				selectedClip = AnimationWindowPreview.GetAnimationClip(animationWindow);
			}

			if (selectedClip != null) {
				float time = AnimationWindowPreview.GetAnimationTime(animationWindow);
				PreviewAnimationInScene(selectedClip, time);
			} else // back to setup pose
				PreviewAnimationInScene(null, 0.0f);
		}

		protected void PreviewAnimationInScene (AnimationClip clip, float time) {
			foreach (UnityEngine.Object c in targets) {
				SkeletonRenderer skeletonRenderer = c as SkeletonRenderer;
				if (skeletonRenderer == null) continue;
				Skeleton skeleton = skeletonRenderer.Skeleton;
				SkeletonData skeletonData = skeleton.Data;

				skeleton.SetToSetupPose();
				if (clip != null) {
					Spine.Animation animation = skeletonData.FindAnimation(clip.name);
					animation.Apply(skeleton, 0, time, false, null, 1.0f, MixBlend.First, MixDirection.In);
				}
				skeletonRenderer.LateUpdate();
			}
			SceneView.RepaintAll();
		}

		protected void DrawLayerSettings () {
			string[] layerNames = GetLayerNames();
			float widthLayerColumn = 140;
			float widthMixColumn = 84;

			using (new GUILayout.HorizontalScope()) {
				Rect rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight);
				rect.width = widthLayerColumn;
				EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mecanim Layer"), EditorStyles.boldLabel);

				int savedIndent = EditorGUI.indentLevel;
				EditorGUI.indentLevel = 0;

				rect.position += new Vector2(rect.width, 0);
				rect.width = widthMixColumn;
				EditorGUI.LabelField(rect, SpineInspectorUtility.TempContent("Mix Mode"), EditorStyles.boldLabel);

				EditorGUI.indentLevel = savedIndent;
			}

			using (new SpineInspectorUtility.IndentScope()) {
				int layerCount = layerMixModes.arraySize;
				for (int i = 0; i < layerCount; ++i) {
					using (new GUILayout.HorizontalScope()) {
						string layerName = i < layerNames.Length ? layerNames[i] : ("Layer " + i);

						Rect rect = GUILayoutUtility.GetRect(EditorGUIUtility.currentViewWidth, EditorGUIUtility.singleLineHeight);
						rect.width = widthLayerColumn;
						EditorGUI.PrefixLabel(rect, SpineInspectorUtility.TempContent(layerName));

						int savedIndent = EditorGUI.indentLevel;
						EditorGUI.indentLevel = 0;

						SerializedProperty mixMode = layerMixModes.GetArrayElementAtIndex(i);
						rect.position += new Vector2(rect.width, 0);
						rect.width = widthMixColumn;
						EditorGUI.PropertyField(rect, mixMode, GUIContent.none);

						EditorGUI.indentLevel = savedIndent;
					}
				}
			}
		}

		protected string[] GetLayerNames () {
			int maxLayerCount = 0;
			int maxIndex = 0;
			for (int i = 0; i < targets.Length; ++i) {
				SkeletonMecanim skeletonMecanim = ((SkeletonMecanim)targets[i]);
				int count = skeletonMecanim.Translator.MecanimLayerCount;
				if (count > maxLayerCount) {
					maxLayerCount = count;
					maxIndex = i;
				}
			}
			if (maxLayerCount == 0)
				return new string[0];
			SkeletonMecanim skeletonMecanimMaxLayers = ((SkeletonMecanim)targets[maxIndex]);
			return skeletonMecanimMaxLayers.Translator.MecanimLayerNames;
		}
	}
}
