using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

namespace FlexalonCopilot
{
    internal static class Serialization
    {
        public static string Serialize(object data, bool pretty = false)
        {
            var stream = FastJson.Serialize(data, pretty);
            if (stream == null)
            {
                return null;
            }

            return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length);
        }

        private static bool TrySplitArray(string arrayString, out List<string> elements)
        {
            elements = null;

            if (arrayString.Length < 2)
            {
                return false;
            }

            if (arrayString[0] != '[')
            {
                return false;
            }

            if (arrayString[arrayString.Length - 1] != ']')
            {
                return false;
            }

            elements = new List<string>();
            int b = 0;
            bool q = false;
            bool bs = false;
            var sb = new StringBuilder();
            for (int i = 1; i < arrayString.Length - 1; i++)
            {
                var c = arrayString[i];
                if (!q && b == 0 && c == ',') {
                    elements.Add(sb.ToString());
                    sb.Clear();
                    continue;
                }

                sb.Append(c);

                if (bs)
                {
                    bs = false;
                }
                else if (c == '\\')
                {
                    bs = true;
                }
                else if (c == '"')
                {
                    q = !q;
                }
                else if (c == '[')
                {
                    b++;
                }
                else if (c == ']')
                {
                    b--;
                }
            }

            if (sb.Length > 0)
            {
                elements.Add(sb.ToString());
            }

            return true;
        }

        internal static IList MakeListOf(Type elementType)
        {
            var list = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elementType));
            return list;
        }

        internal static IList MakeList(Type listType)
        {
            return MakeListOf(listType.GetGenericArguments()[0]);
        }

        internal static IList CopyList(IList list)
        {
            var newList = MakeList(list.GetType());
            foreach (var item in list)
            {
                newList.Add(item);
            }

            return newList;
        }

        internal static Array MakeArrayOf(Type elementType, int length)
        {
            return Array.CreateInstance(elementType, length);
        }

        internal static Array MakeArray(Type arrayType, int length)
        {
            return MakeArrayOf(arrayType.GetElementType(), length);
        }

        internal static Array CopyArray(Array array)
        {
            var newArray = MakeArray(array.GetType(), array.Length);
            Array.Copy(array, newArray, array.Length);
            return newArray;
        }

        internal static Array ResizeArray(Array array, int size)
        {
            var newArray = MakeArray(array.GetType(), size);
            Array.Copy(array, newArray, Math.Min(array.Length, size));
            return newArray;
        }

        private static bool TryDeserializeList(Type listType, string arrayString, out IList values)
        {
            if (!TrySplitArray(arrayString, out var elements))
            {
                values = null;
                return false;
            }

            var elementType = listType.GetGenericArguments()[0];
            values = MakeListOf(elementType);
            for (int i = 0; i < elements.Count; i++)
            {
                if (!TryDeserialize(elementType, elements[i].Trim('"'), out var obj))
                {
                    values = null;
                    return false;
                }

                values.Add(obj);
            }

            return true;
        }

        private static bool TryDeserializeArray(Type elementType, string arrayString, out Array values)
        {
            if (!TrySplitArray(arrayString, out var elements))
            {
                values = null;
                return false;
            }

            values = MakeArrayOf(elementType, elements.Count);
            for (int i = 0; i < elements.Count; i++)
            {
                if (!TryDeserialize(elementType, elements[i].Trim('"'), out var obj))
                {
                    values = null;
                    return false;
                }

                values.SetValue(obj, i);
            }

            return true;
        }

        private static bool TryDeserializeArray<T>(string arrayString, out T[] values)
        {
            if (TryDeserializeArray(typeof(T), arrayString, out var array))
            {
                values = (T[])array;
                return true;
            }

            values = null;
            return false;
        }

        private static bool TryDeserializeArray<T>(string arrayString, int count, out T[] values)
        {
            if (!TryDeserializeArray<T>(arrayString, out values))
            {
                return false;
            }

            if (values.Length != count)
            {
                values = null;
                return false;
            }

            return true;
        }

        internal static bool TryDeserialize<T>(string value, out T result)
        {
            if (TryDeserialize(typeof(T), value, out var obj))
            {
                result = (T)obj;
                return true;
            }

            result = default(T);
            return false;
        }

        // This does NOT do full JSON deserialization. It just deserializes single values
        // or arrays of values, which is all the inspector needs.
        internal static bool TryDeserialize(Type type, string value, out object result)
        {
            if (type == typeof(string))
            {
                result = value;
                return true;
            }
            else if (type == typeof(float))
            {
                if (float.TryParse(value, out var floatValue))
                {
                    result = floatValue;
                    return true;
                }
            }
            else if (type == typeof(double))
            {
                if (double.TryParse(value, out var doubleValue))
                {
                    result = doubleValue;
                    return true;
                }
            }
            else if (type == typeof(byte))
            {
                if (byte.TryParse(value, out var byteValue))
                {
                    result = byteValue;
                    return true;
                }
            }
            else if (type == typeof(sbyte))
            {
                if (sbyte.TryParse(value, out var sbyteValue))
                {
                    result = sbyteValue;
                    return true;
                }
            }
            else if (type == typeof(short))
            {
                if (short.TryParse(value, out var shortValue))
                {
                    result = shortValue;
                    return true;
                }
            }
            else if (type == typeof(ushort))
            {
                if (ushort.TryParse(value, out var ushortValue))
                {
                    result = ushortValue;
                    return true;
                }
            }
            else if (type == typeof(int))
            {
                if (int.TryParse(value, out var intValue))
                {
                    result = intValue;
                    return true;
                }
            }
            else if (type == typeof(uint))
            {
                if (uint.TryParse(value, out var uintValue))
                {
                    result = uintValue;
                    return true;
                }
            }
            else if (type == typeof(long))
            {
                if (long.TryParse(value, out var longValue))
                {
                    result = longValue;
                    return true;
                }
            }
            else if (type == typeof(ulong))
            {
                if (ulong.TryParse(value, out var ulongValue))
                {
                    result = ulongValue;
                    return true;
                }
            }
            else if (type == typeof(bool))
            {
                result = (value == "true");
                return true;
            }
            else if (type == typeof(Vector2))
            {
                if (TryDeserializeArray<float>(value, 2, out var v))
                {
                    result = new Vector2(v[0], v[1]);
                    return true;
                }
            }
            else if (type == typeof(Vector3))
            {
                if (TryDeserializeArray<float>(value, 3, out var v))
                {
                    result = new Vector3(v[0], v[1], v[2]);
                    return true;
                }
            }
            else if (type == typeof(Vector4))
            {
                if (TryDeserializeArray<float>(value, 4, out var v))
                {
                    result = new Vector4(v[0], v[1], v[2], v[3]);
                    return true;
                }
            }
            else if (type == typeof(Vector2Int))
            {
                if (TryDeserializeArray<int>(value, 2, out var v))
                {
                    result = new Vector2Int(v[0], v[1]);
                    return true;
                }
            }
            else if (type == typeof(Vector3Int))
            {
                if (TryDeserializeArray<int>(value, 3, out var v))
                {
                    result = new Vector3Int(v[0], v[1], v[2]);
                    return true;
                }
            }
            else if (type == typeof(Quaternion))
            {
                if (TryDeserializeArray<float>(value, 3, out var v))
                {
                    result = Quaternion.Euler(v[0], v[1], v[2]);
                    return true;
                }
            }
            else if (type == typeof(Rect))
            {
                if (TryDeserializeArray<float>(value, 4, out var v))
                {
                    result = new Rect(v[0], v[1], v[2], v[3]);
                    return true;
                }
            }
            else if (type == typeof(RectInt))
            {
                if (TryDeserializeArray<int>(value, 4, out var v))
                {
                    result = new RectInt(v[0], v[1], v[2], v[3]);
                    return true;
                }
            }
            else if (type == typeof(Bounds))
            {
                if (TryDeserializeArray<float>(value, 6, out var v))
                {
                    result = new Bounds(new Vector3(v[0], v[1], v[2]), new Vector3(v[3], v[4], v[5]));
                    return true;
                }
            }
            else if (type == typeof(BoundsInt))
            {
                if (TryDeserializeArray<int>(value, 6, out var v))
                {
                    result = new BoundsInt(new Vector3Int(v[0], v[1], v[2]), new Vector3Int(v[3], v[4], v[5]));
                    return true;
                }
            }
            else if (type == typeof(Color))
            {
                if (ColorUtility.TryParseHtmlString(value, out var color))
                {
                    result = color;
                    return true;
                }
            }
            else if (type == typeof(LayerMask))
            {
                if (int.TryParse(value, out var intValue))
                {
                    result = new LayerMask() { value = intValue };
                    return true;
                }
            }
            #if UNITY_TMPRO
                else if (type == typeof(TMPro.TMP_Dropdown.OptionData))
                {
                    result = new TMPro.TMP_Dropdown.OptionData { text = value };
                    return true;
                }
            #endif
            else if (type.IsEnum)
            {
                if (int.TryParse(value, out var enumValue))
                {
                    result = enumValue;
                    return true;
                }
            }
            else if (type.IsArray)
            {
                if (TryDeserializeArray(type.GetElementType(), value, out var array))
                {
                    result = array;
                    return true;
                }
            }
            else if (typeof(IList).IsAssignableFrom(type))
            {
                if (TryDeserializeList(type, value, out var list))
                {
                    result = list;
                    return true;
                }
            }
            else if (type.IsSubclassOf(typeof(UnityEngine.Object)))
            {
                #if UNITY_EDITOR
                    if (value == "")
                    {
                        result = null;
                        return true;
                    }
                    else if (int.TryParse(value, out var id))
                    {
                        result = UnityEditor.EditorUtility.InstanceIDToObject(id);
                        return true;
                    }
                    else if (value.Length == 32)
                    {
                        var assetPath = UnityEditor.AssetDatabase.GUIDToAssetPath(value);
                        if (!string.IsNullOrEmpty(assetPath))
                        {
                            result = UnityEditor.AssetDatabase.LoadAssetAtPath(assetPath, type);
                            return true;
                        }
                    }
                #endif
            }
            else if (type.IsSerializable)
            {
                result = JsonUtility.FromJson(value, type);
                return true;
            }

            result = null;
            return false;
        }

        internal static object DeserializeOrDefault(Type t, string value)
        {
            if (TryDeserialize(t, value, out var result))
            {
                return result;
            }

            if (t.IsValueType)
            {
                return Activator.CreateInstance(t);
            }
            else
            {
                return null;
            }
        }

        internal static T DeserializeOrDefault<T>(string value)
        {
            return (T)DeserializeOrDefault(typeof(T), value);
        }

        internal static object Deserialize(Type t, string value)
        {
            if (TryDeserialize(t, value, out var result))
            {
                return result;
            }

            throw new Exception("Failed to deserialize value: " + value + " as type: " + t.Name);
        }

        internal static T Deserialize<T>(string value)
        {
            return (T)Deserialize(typeof(T), value);
        }

        private static List<Type> _nativeFlags = new List<Type>() {
#if UNITY_PHYSICS
            typeof(RigidbodyConstraints)
#endif
        };
    }
}