using System;
using System.Runtime.CompilerServices;
using UnityEngine;

namespace UnityEditor.Rendering
{
    /// <summary>
    /// Extensions for <see cref="SerializedProperty"/>
    /// </summary>
    public static class SerializedPropertyExtension
    {
        /// <summary>
        /// Checks if the property target is alive
        /// </summary>
        /// <param name="property">The <see cref="SerializedProperty"/> to check </param>
        /// <returns>true, if the property is not null</returns>
        public static bool IsTargetAlive(this SerializedProperty property)
            => property != null && property.serializedObject.targetObject != null &&
            !property.serializedObject.targetObject.Equals(null);

        /// <summary>
        /// Helper to get an enum value from a SerializedProperty.
        /// This handle case where index do not correspond to enum value.
        /// <example>
        /// <code>
        /// enum MyEnum
        /// {
        ///     A = 2,
        ///     B = 4,
        /// }
        /// public class MyObject : MonoBehavior
        /// {
        ///     public MyEnum theEnum = MyEnum.A;
        /// }
        /// #if UNITY_EDITOR
        /// [CustomEditor(typeof(MyObject))]
        /// class MyObjectEditor : Editor
        /// {
        ///     public override void OnInspectorGUI()
        ///     {
        ///         Debug.Log($"By enumValueIndex: {(MyEnum)serializedObject.FindProperty("theEnum").enumValueIndex}");         //write the value (MyEnum)(0)
        ///         Debug.Log($"By GetEnumValue: {(MyEnum)serializedObject.FindProperty("theEnum").GetEnumValue<MyEnum>()}");   //write the value MyEnum.A
        ///     }
        /// }
        /// #endif
        /// </code>
        /// </example>
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static T GetEnumValue<T>(this SerializedProperty property)
            where T : Enum
            => GetEnumValue_Internal<T>(property);

        /// <summary>
        /// Helper to get an enum name from a SerializedProperty
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static string GetEnumName<T>(this SerializedProperty property)
            where T : Enum
            => property.hasMultipleDifferentValues
            ? "MultipleDifferentValues"
            : property.enumNames[property.enumValueIndex];

        /// <summary>
        /// Helper to set an enum value to a SerializedProperty
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void SetEnumValue<T>(this SerializedProperty property, T value)
            where T : Enum
            // intValue actually is the value underlying beside the enum
            => SetEnumValue_Internal(property, value);

        /// <summary>
        /// Get the value of a <see cref="SerializedProperty"/>.
        ///
        /// This function will be inlined by the compiler.
        /// Caution: The case of Enum is not handled here.
        /// </summary>
        /// <typeparam name="T">
        /// The type of the value to get.
        ///
        /// It is expected to be a supported type by the <see cref="SerializedProperty"/>.
        /// </typeparam>
        /// <param name="serializedProperty">The property to get.</param>
        /// <returns>The value of the property.</returns>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static T GetInline<T>(this SerializedProperty serializedProperty)
            where T : struct
        {
            if (typeof(T) == typeof(Color))
                return (T)(object)serializedProperty.colorValue;
            if (typeof(T) == typeof(string))
                return (T)(object)serializedProperty.stringValue;
            if (typeof(T) == typeof(double))
                return (T)(object)serializedProperty.doubleValue;
            if (typeof(T) == typeof(float))
                return (T)(object)serializedProperty.floatValue;
            if (typeof(T) == typeof(long))
                return (T)(object)serializedProperty.longValue;
            if (typeof(T) == typeof(int))
                return (T)(object)serializedProperty.intValue;
            if (typeof(T) == typeof(bool))
                return (T)(object)serializedProperty.boolValue;
            if (typeof(T) == typeof(BoundsInt))
                return (T)(object)serializedProperty.boundsIntValue;
            if (typeof(T) == typeof(Bounds))
                return (T)(object)serializedProperty.boundsValue;
            if (typeof(T) == typeof(RectInt))
                return (T)(object)serializedProperty.rectIntValue;
            if (typeof(T) == typeof(Rect))
                return (T)(object)serializedProperty.rectValue;
            if (typeof(T) == typeof(Quaternion))
                return (T)(object)serializedProperty.quaternionValue;
            if (typeof(T) == typeof(Vector2Int))
                return (T)(object)serializedProperty.vector2IntValue;
            if (typeof(T) == typeof(Vector4))
                return (T)(object)serializedProperty.vector4Value;
            if (typeof(T) == typeof(Vector3))
                return (T)(object)serializedProperty.vector3Value;
            if (typeof(T) == typeof(Vector2))
                return (T)(object)serializedProperty.vector2Value;
            if (typeof(T).IsEnum)
                return GetEnumValue_Internal<T>(serializedProperty);
            throw new ArgumentOutOfRangeException($"<{typeof(T)}> is not a valid type for a serialized property.");
        }

        /// <summary>
        /// Set the value of a <see cref="SerializedProperty"/>.
        ///
        /// This function will be inlined by the compiler.
        /// Caution: The case of Enum is not handled here.
        /// </summary>
        /// <typeparam name="T">
        /// The type of the value to set.
        ///
        /// It is expected to be a supported type by the <see cref="SerializedProperty"/>.
        /// </typeparam>
        /// <param name="serializedProperty">The property to set.</param>
        /// <param name="value">The value to set.</param>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void SetInline<T>(this SerializedProperty serializedProperty, T value)
            where T : struct
        {
            if (typeof(T) == typeof(Color))
            {
                serializedProperty.colorValue = (Color)(object)value;
                return;
            }
            if (typeof(T) == typeof(string))
            {
                serializedProperty.stringValue = (string)(object)value;
                return;
            }
            if (typeof(T) == typeof(double))
            {
                serializedProperty.doubleValue = (double)(object)value;
                return;
            }
            if (typeof(T) == typeof(float))
            {
                serializedProperty.floatValue = (float)(object)value;
                return;
            }
            if (typeof(T) == typeof(long))
            {
                serializedProperty.longValue = (long)(object)value;
                return;
            }
            if (typeof(T) == typeof(int))
            {
                serializedProperty.intValue = (int)(object)value;
                return;
            }
            if (typeof(T) == typeof(bool))
            {
                serializedProperty.boolValue = (bool)(object)value;
                return;
            }
            if (typeof(T) == typeof(BoundsInt))
            {
                serializedProperty.boundsIntValue = (BoundsInt)(object)value;
                return;
            }
            if (typeof(T) == typeof(Bounds))
            {
                serializedProperty.boundsValue = (Bounds)(object)value;
                return;
            }
            if (typeof(T) == typeof(RectInt))
            {
                serializedProperty.rectIntValue = (RectInt)(object)value;
                return;
            }
            if (typeof(T) == typeof(Rect))
            {
                serializedProperty.rectValue = (Rect)(object)value;
                return;
            }
            if (typeof(T) == typeof(Quaternion))
            {
                serializedProperty.quaternionValue = (Quaternion)(object)value;
                return;
            }
            if (typeof(T) == typeof(Vector2Int))
            {
                serializedProperty.vector2IntValue = (Vector2Int)(object)value;
                return;
            }
            if (typeof(T) == typeof(Vector4))
            {
                serializedProperty.vector4Value = (Vector4)(object)value;
                return;
            }
            if (typeof(T) == typeof(Vector3))
            {
                serializedProperty.vector3Value = (Vector3)(object)value;
                return;
            }
            if (typeof(T) == typeof(Vector2))
            {
                serializedProperty.vector2Value = (Vector2)(object)value;
                return;
            }
            if (typeof(T).IsEnum)
            {
                SetEnumValue_Internal(serializedProperty, value);
                return;
            }
            throw new ArgumentOutOfRangeException($"<{typeof(T)}> is not a valid type for a serialized property.");
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static T GetEnumValue_Internal<T>(SerializedProperty property)
        // intValue actually is the value underlying beside the enum
            => (T)(object)property.intValue;


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static void SetEnumValue_Internal<T>(SerializedProperty property, T value)
        // intValue actually is the value underlying beside the enum
            => property.intValue = (int)(object)value;
    }
}