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

namespace NaughtyAttributes.Editor
{
	[CustomPropertyDrawer(typeof(AnimatorParamAttribute))]
	public class AnimatorParamPropertyDrawer : PropertyDrawerBase
	{
		private const string InvalidAnimatorControllerWarningMessage = "Target animator controller is null";
		private const string InvalidTypeWarningMessage = "{0} must be an int or a string";

		protected override float GetPropertyHeight_Internal(SerializedProperty property, GUIContent label)
		{
			AnimatorParamAttribute animatorParamAttribute = PropertyUtility.GetAttribute<AnimatorParamAttribute>(property);
			bool validAnimatorController = GetAnimatorController(property, animatorParamAttribute.AnimatorName) != null;
			bool validPropertyType = property.propertyType == SerializedPropertyType.Integer || property.propertyType == SerializedPropertyType.String;

			return (validAnimatorController && validPropertyType)
				? GetPropertyHeight(property)
				: GetPropertyHeight(property) + GetHelpBoxHeight();
		}

		protected override void OnGUI_Internal(Rect rect, SerializedProperty property, GUIContent label)
		{
			EditorGUI.BeginProperty(rect, label, property);

			AnimatorParamAttribute animatorParamAttribute = PropertyUtility.GetAttribute<AnimatorParamAttribute>(property);

			AnimatorController animatorController = GetAnimatorController(property, animatorParamAttribute.AnimatorName);
			if (animatorController == null)
			{
				DrawDefaultPropertyAndHelpBox(rect, property, InvalidAnimatorControllerWarningMessage, MessageType.Warning);
				return;
			}

			int parametersCount = animatorController.parameters.Length;
			List<AnimatorControllerParameter> animatorParameters = new List<AnimatorControllerParameter>(parametersCount);
			for (int i = 0; i < parametersCount; i++)
			{
				AnimatorControllerParameter parameter = animatorController.parameters[i];
				if (animatorParamAttribute.AnimatorParamType == null || parameter.type == animatorParamAttribute.AnimatorParamType)
				{
					animatorParameters.Add(parameter);
				}
			}

			switch (property.propertyType)
			{
				case SerializedPropertyType.Integer:
					DrawPropertyForInt(rect, property, label, animatorParameters);
					break;
				case SerializedPropertyType.String:
					DrawPropertyForString(rect, property, label, animatorParameters);
					break;
				default:
					DrawDefaultPropertyAndHelpBox(rect, property, string.Format(InvalidTypeWarningMessage, property.name), MessageType.Warning);
					break;
			}

			EditorGUI.EndProperty();
		}

		private static void DrawPropertyForInt(Rect rect, SerializedProperty property, GUIContent label, List<AnimatorControllerParameter> animatorParameters)
		{
			int paramNameHash = property.intValue;
			int index = 0;

			for (int i = 0; i < animatorParameters.Count; i++)
			{
				if (paramNameHash == animatorParameters[i].nameHash)
				{
					index = i + 1; // +1 because the first option is reserved for (None)
					break;
				}
			}

			string[] displayOptions = GetDisplayOptions(animatorParameters);

			int newIndex = EditorGUI.Popup(rect, label.text, index, displayOptions);
			int newValue = newIndex == 0 ? 0 : animatorParameters[newIndex - 1].nameHash;

			if (property.intValue != newValue)
			{
				property.intValue = newValue;
			}
		}

		private static void DrawPropertyForString(Rect rect, SerializedProperty property, GUIContent label, List<AnimatorControllerParameter> animatorParameters)
		{
			string paramName = property.stringValue;
			int index = 0;

			for (int i = 0; i < animatorParameters.Count; i++)
			{
				if (paramName.Equals(animatorParameters[i].name, System.StringComparison.Ordinal))
				{
					index = i + 1; // +1 because the first option is reserved for (None)
					break;
				}
			}

			string[] displayOptions = GetDisplayOptions(animatorParameters);

			int newIndex = EditorGUI.Popup(rect, label.text, index, displayOptions);
			string newValue = newIndex == 0 ? null : animatorParameters[newIndex - 1].name;

			if (!property.stringValue.Equals(newValue, System.StringComparison.Ordinal))
			{
				property.stringValue = newValue;
			}
		}

		private static string[] GetDisplayOptions(List<AnimatorControllerParameter> animatorParams)
		{
			string[] displayOptions = new string[animatorParams.Count + 1];
			displayOptions[0] = "(None)";

			for (int i = 0; i < animatorParams.Count; i++)
			{
				displayOptions[i + 1] = animatorParams[i].name;
			}

			return displayOptions;
		}

		private static AnimatorController GetAnimatorController(SerializedProperty property, string animatorName)
		{
			object target = PropertyUtility.GetTargetObjectWithProperty(property);

			FieldInfo animatorFieldInfo = ReflectionUtility.GetField(target, animatorName);
			if (animatorFieldInfo != null &&
				animatorFieldInfo.FieldType == typeof(Animator))
			{
				Animator animator = animatorFieldInfo.GetValue(target) as Animator;
				if (animator != null)
				{
					AnimatorController animatorController = animator.runtimeAnimatorController as AnimatorController;
					return animatorController;
				}
			}

			PropertyInfo animatorPropertyInfo = ReflectionUtility.GetProperty(target, animatorName);
			if (animatorPropertyInfo != null &&
				animatorPropertyInfo.PropertyType == typeof(Animator))
			{
				Animator animator = animatorPropertyInfo.GetValue(target) as Animator;
				if (animator != null)
				{
					AnimatorController animatorController = animator.runtimeAnimatorController as AnimatorController;
					return animatorController;
				}
			}

			MethodInfo animatorGetterMethodInfo = ReflectionUtility.GetMethod(target, animatorName);
			if (animatorGetterMethodInfo != null &&
				animatorGetterMethodInfo.ReturnType == typeof(Animator) &&
				animatorGetterMethodInfo.GetParameters().Length == 0)
			{
				Animator animator = animatorGetterMethodInfo.Invoke(target, null) as Animator;
				if (animator != null)
				{
					AnimatorController animatorController = animator.runtimeAnimatorController as AnimatorController;
					return animatorController;
				}
			}

			return null;
		}
	}
}
