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

namespace NaughtyAttributes.Editor
{
	[CustomPropertyDrawer(typeof(DropdownAttribute))]
	public class DropdownPropertyDrawer : PropertyDrawerBase
	{
		protected override float GetPropertyHeight_Internal(SerializedProperty property, GUIContent label)
		{
			DropdownAttribute dropdownAttribute = (DropdownAttribute)attribute;
			object values = GetValues(property, dropdownAttribute.ValuesName);
			FieldInfo fieldInfo = ReflectionUtility.GetField(PropertyUtility.GetTargetObjectWithProperty(property), property.name);

			float propertyHeight = AreValuesValid(values, fieldInfo)
				? GetPropertyHeight(property)
				: GetPropertyHeight(property) + GetHelpBoxHeight();

			return propertyHeight;
		}

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

			DropdownAttribute dropdownAttribute = (DropdownAttribute)attribute;
			object target = PropertyUtility.GetTargetObjectWithProperty(property);

			object valuesObject = GetValues(property, dropdownAttribute.ValuesName);
			FieldInfo dropdownField = ReflectionUtility.GetField(target, property.name);

			if (AreValuesValid(valuesObject, dropdownField))
			{
				if (valuesObject is IList && dropdownField.FieldType == GetElementType(valuesObject))
				{
					// Selected value
					object selectedValue = dropdownField.GetValue(target);

					// Values and display options
					IList valuesList = (IList)valuesObject;
					object[] values = new object[valuesList.Count];
					string[] displayOptions = new string[valuesList.Count];

					for (int i = 0; i < values.Length; i++)
					{
						object value = valuesList[i];
						values[i] = value;
						displayOptions[i] = value == null ? "<null>" : value.ToString();
					}

					// Selected value index
					int selectedValueIndex = Array.IndexOf(values, selectedValue);
					if (selectedValueIndex < 0)
					{
						selectedValueIndex = 0;
					}

					NaughtyEditorGUI.Dropdown(
						rect, property.serializedObject, target, dropdownField, label.text, selectedValueIndex, values, displayOptions);
				}
				else if (valuesObject is IDropdownList)
				{
					// Current value
					object selectedValue = dropdownField.GetValue(target);

					// Current value index, values and display options
					int index = -1;
					int selectedValueIndex = -1;
					List<object> values = new List<object>();
					List<string> displayOptions = new List<string>();
					IDropdownList dropdown = (IDropdownList)valuesObject;

					using (IEnumerator<KeyValuePair<string, object>> dropdownEnumerator = dropdown.GetEnumerator())
					{
						while (dropdownEnumerator.MoveNext())
						{
							index++;

							KeyValuePair<string, object> current = dropdownEnumerator.Current;
							if (current.Value?.Equals(selectedValue) == true)
							{
								selectedValueIndex = index;
							}

							values.Add(current.Value);

							if (current.Key == null)
							{
								displayOptions.Add("<null>");
							}
							else if (string.IsNullOrWhiteSpace(current.Key))
							{
								displayOptions.Add("<empty>");
							}
							else
							{
								displayOptions.Add(current.Key);
							}
						}
					}

					if (selectedValueIndex < 0)
					{
						selectedValueIndex = 0;
					}

					NaughtyEditorGUI.Dropdown(
						rect, property.serializedObject, target, dropdownField, label.text, selectedValueIndex, values.ToArray(), displayOptions.ToArray());
				}
			}
			else
			{
				string message = string.Format("Invalid values with name '{0}' provided to '{1}'. Either the values name is incorrect or the types of the target field and the values field/property/method don't match",
					dropdownAttribute.ValuesName, dropdownAttribute.GetType().Name);

				DrawDefaultPropertyAndHelpBox(rect, property, message, MessageType.Warning);
			}

			EditorGUI.EndProperty();
		}

		private object GetValues(SerializedProperty property, string valuesName)
		{
			object target = PropertyUtility.GetTargetObjectWithProperty(property);

			FieldInfo valuesFieldInfo = ReflectionUtility.GetField(target, valuesName);
			if (valuesFieldInfo != null)
			{
				return valuesFieldInfo.GetValue(target);
			}

			PropertyInfo valuesPropertyInfo = ReflectionUtility.GetProperty(target, valuesName);
			if (valuesPropertyInfo != null)
			{
				return valuesPropertyInfo.GetValue(target);
			}

			MethodInfo methodValuesInfo = ReflectionUtility.GetMethod(target, valuesName);
			if (methodValuesInfo != null &&
				methodValuesInfo.ReturnType != typeof(void) &&
				methodValuesInfo.GetParameters().Length == 0)
			{
				return methodValuesInfo.Invoke(target, null);
			}

			return null;
		}

		private bool AreValuesValid(object values, FieldInfo dropdownField)
		{
			if (values == null || dropdownField == null)
			{
				return false;
			}

			if ((values is IList && dropdownField.FieldType == GetElementType(values)) ||
				(values is IDropdownList))
			{
				return true;
			}

			return false;
		}

		private Type GetElementType(object values)
		{
			Type valuesType = values.GetType();
			Type elementType = ReflectionUtility.GetListElementType(valuesType);

			return elementType;
		}
	}
}