using System;
using System.Collections;
using System.Diagnostics;
using System.Text.RegularExpressions;
using UnityEngine;
using Debug = UnityEngine.Debug;
using Object = UnityEngine.Object;
using Type = System.Type;
#if UNITY_EDITOR
using UnityEditor;
using System.Reflection;
#endif

namespace Jovian.Utilities {

    public static class SerializedObjectUtility {
        [Conditional("UNITY_EDITOR")]
        public static void SaveObjectProperties(Object targetObject, params object[] args) {
#if UNITY_EDITOR
            EditorSerializedObjectUtility.SaveObjectProperties(targetObject, args);
#endif
        }
    }
#if UNITY_EDITOR
    public static class EditorSerializedObjectUtility {
        
        //https://answers.unity.com/questions/929293/get-field-type-of-serializedproperty.html
        public static Type GetTypeFromProperty(SerializedProperty property) {
            //gets parent type info
            string[] slices = property.propertyPath.Split('.');
            System.Type type = property.serializedObject.targetObject.GetType();
 
            for(int i = 0; i < slices.Length; i++)                
                if (slices[i] == "Array")
                {
                    i++; //skips "data[x]"
                    type = type.GetElementType(); //gets info on array elements
                }                   
                 
                //gets info on field and its type
                else type = type.GetField(slices[i], BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance).FieldType;  
 
            //type is now the type of the property
            return type;
        }

        private static readonly Regex isArrayElementRegex = new Regex("Array.data\\[\\d+\\]$");
        
        public static bool IsPropertyAnArrayElement(SerializedProperty property) {
            return isArrayElementRegex.IsMatch(property.propertyPath);
        }

        public static SerializedProperty GetArrayPropertyWithElementProperty(SerializedProperty property) {
            SerializedObject serializedObject = property.serializedObject;
            string propertyPath = property.propertyPath;
            int arrayDataIndex = propertyPath.LastIndexOf(".Array.data", StringComparison.Ordinal);
            string propertyPathWithoutArray = propertyPath.Substring(0, arrayDataIndex);
            int pathDividerIndex = propertyPathWithoutArray.LastIndexOf(".", StringComparison.Ordinal);

            string parentPropertyName = propertyPathWithoutArray;
            if(pathDividerIndex != -1) {
                parentPropertyName = propertyPathWithoutArray.Substring(pathDividerIndex);
            }
            return serializedObject.FindProperty(parentPropertyName);;
        }
        
        public static void SaveObjectProperties(Object targetObject, params object[] args) {
            SerializedObject serializedObject = new SerializedObject(targetObject);

            for(int i = 0; i < args.Length; i += 2) {
                var keyArg = args[i];
                var keyType = keyArg.GetType();

                if((keyType == typeof(string)) == false) {
                    throw new System.NotSupportedException(string.Format("Key must be string. {0} is {1}", args[i], keyType));
                }
                else {
                    var property = serializedObject.FindProperty((string)keyArg);
                    object argValue = args[i + 1];

                    if(property == null) {
                        throw new System.Exception(string.Format("No property found for key {0}", keyArg));
                    }

                    if(argValue == null) {
                        property.objectReferenceValue = null;
                    }
                    else {
                        if(property.isArray) {
                            property.arraySize = 0;
                            IEnumerable argArray = (IEnumerable)argValue;
                            IEnumerator enumerator = argArray.GetEnumerator();

                            int index = 0;
                            while(enumerator.MoveNext()) {
                                property.InsertArrayElementAtIndex(index);
                                var arrayProperty = property.GetArrayElementAtIndex(index);
                                SetSerializedPropertyValue(arrayProperty, enumerator.Current);
                                index++;
                            }

                        }
                        else {
                            SetSerializedPropertyValue(property, argValue);
                        }

                    }
                }
            }

            serializedObject.ApplyModifiedProperties();
        }

        private static void SetSerializedPropertyValue(SerializedProperty property, object value) {
            if(property == null) {
                UnityEngine.Debug.LogError("SetSerializedPropertyValue failed, property is null");
                return;
            }
            switch(property.propertyType) {
                case SerializedPropertyType.AnimationCurve:
                    property.animationCurveValue = (AnimationCurve)value;
                    break;
                case SerializedPropertyType.Boolean:
                    property.boolValue = (bool)value;
                    break;
                case SerializedPropertyType.BoundsInt:
                    property.boundsIntValue = (BoundsInt)value;
                    break;
                case SerializedPropertyType.Character:
                    property.intValue = (int)(char)value;
                    break;
                case SerializedPropertyType.Color: {
                        if(value is Color32) {
                            property.colorValue = (Color)(Color32)value;
                        }
                        else {
                            property.colorValue = (Color)value;
                        }
                        break;
                    }
                case SerializedPropertyType.ExposedReference:
                case SerializedPropertyType.ObjectReference:
                    property.objectReferenceValue = (Object)value;
                    break;
                case SerializedPropertyType.Float:
                    property.floatValue = (float)value;
                    break;
                case SerializedPropertyType.Integer:
                    property.intValue = (int)value;
                    break;
                case SerializedPropertyType.LayerMask:
                    property.intValue = ((LayerMask)value).value;
                    break;
                case SerializedPropertyType.Quaternion:
                    property.quaternionValue = (Quaternion)value;
                    break;
                case SerializedPropertyType.Rect:
                    property.rectValue = (Rect)value;
                    break;
                case SerializedPropertyType.RectInt:
                    property.rectIntValue = (RectInt)value;
                    break;
                case SerializedPropertyType.String:
                    property.stringValue = (string)value;
                    break;
                case SerializedPropertyType.Vector2:
                    property.vector2Value = (Vector2)value;
                    break;
                case SerializedPropertyType.Vector2Int:
                    property.vector2IntValue = (Vector2Int)value;
                    break;
                case SerializedPropertyType.Vector3:
                    property.vector3Value = (Vector3)value;
                    break;
                case SerializedPropertyType.Vector3Int:
                    property.vector3IntValue = (Vector3Int)value;
                    break;
                case SerializedPropertyType.Vector4:
                    property.vector4Value = (Vector4)value;
                    break;
                case SerializedPropertyType.Enum:
                    property.enumValueIndex = (int)value; // need to test this
                    // flags???
                    break;
                case SerializedPropertyType.Generic:
                    SaveGenericProperty(property, value);
                    break;
                default:
                    throw new System.NotSupportedException($"PropertyType {property.propertyType} is not supported - yet. Array? {property.isArray} Path: {property.propertyPath}");
            }
        }

        private static void SaveGenericProperty(SerializedProperty property, object instance) {
            Type type = instance.GetType();
            var fields = type.GetRuntimeFields();
            foreach(FieldInfo field in fields) {
                if(field.IsNotSerialized || field.IsStatic) {
                    continue;
                }
                SerializedProperty fieldProperty = property.FindPropertyRelative(field.Name);
                if(fieldProperty != null) {
                    SetSerializedPropertyValue(property.FindPropertyRelative(field.Name), field.GetValue(instance));
                }
                else {
                    UnityEngine.Debug.Log($"SaveGenericProperty cannot field serializedProperty named '{field.Name}'");
                }
            }
        }
    }
#endif
}