using System;
using System.Reflection;
using System.Collections.Generic;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace SmartBowSDK
{
    public class CustomJson_SDK
    {
        public Dictionary<Type, Func<object, JToken>> importerDict = new Dictionary<Type, Func<object, JToken>>();
        public Dictionary<Type, Func<JToken, object>> exporterDict = new Dictionary<Type, Func<JToken, object>>();

        public CustomJson_SDK()
        {
            //add importer
            importerDict.Add(typeof(short), (o) => JValue.FromObject(o));
            importerDict.Add(typeof(int), (o) => JValue.FromObject(o));
            importerDict.Add(typeof(long), (o) => JValue.FromObject(o));
            importerDict.Add(typeof(float), (o) => JValue.FromObject(o));
            importerDict.Add(typeof(double), (o) => JValue.FromObject(o));
            importerDict.Add(typeof(bool), (o) => JValue.FromObject(o));
            importerDict.Add(typeof(double[][]), (o) => JArray.FromObject(o));
            importerDict.Add(typeof(Nullable<bool>), (o) => JValue.FromObject(o));
            //add exporter
            exporterDict.Add(typeof(short), (jt) => jt.ToObject<short>());
            exporterDict.Add(typeof(int), (jt) => jt.ToObject<int>());
            exporterDict.Add(typeof(long), (jt) => jt.ToObject<long>());
            exporterDict.Add(typeof(float), (jt) => jt.ToObject<float>());
            exporterDict.Add(typeof(double), (jt) => jt.ToObject<double>());
            exporterDict.Add(typeof(bool), (jt) => jt.ToObject<bool>());
            exporterDict.Add(typeof(double[][]), (jt) => ((JArray)jt).ToObject<double[][]>());
            exporterDict.Add(typeof(Nullable<bool>), (jt) => jt.ToObject<Nullable<bool>>());
        }

        public void InitJsonClass<T>(Func<object, JToken> importer, Func<JToken, object> exporter)
        {
            Type type = typeof(T);
            importerDict.Add(type, importer);
            exporterDict.Add(type, exporter);
        }

        public void InitJsonClass<T>(Func<object> ctor, params string[] props)
        {
            Type type = typeof(T);
            exporterDict.Add(type, (jt) =>
            {
                var o = ctor.Invoke();
                FetchFieldValues(o, jt, props);
                return o;
            });
            importerDict.Add(type, (o) =>
            {
                JToken jt = new JObject();
                FetchJsonProperties(jt, o, props);
                return jt;
            });
        }

        public T Parse<T>(string text)
        {
            var jt = JToken.Parse(text);
            return (T)ParseByExporter(jt, typeof(T));
        }

        public string Stringify(object o)
        {
            return JsonConvert.SerializeObject(ToJTokenByImporter(o));
        }

        public void FetchJsonProperties(JToken target, object fromObject, params string[] propertyNames)
        {
            Type fromObjectType = fromObject.GetType();
            foreach (var propertyName in propertyNames)
            {
                FieldInfo targetField = fromObjectType.GetField(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if (targetField == null)
                {
                    PropertyInfo propertyInfo = fromObjectType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                    if (propertyInfo == null)
                    {
                        throw new Exception($"获取PropertyInfo[{propertyName}]失败");
                    }
                    else
                    {
                        JToken propertyValue = ToJTokenByImporter(propertyInfo.GetValue(fromObject));
                        target[propertyName] = propertyValue;
                    }
                }
                else
                {
                    JToken propertyValue = ToJTokenByImporter(targetField.GetValue(fromObject));
                    target[propertyName] = propertyValue;
                }
            }
        }

        public JToken ToJTokenByImporter(object o)
        {
            if (o == null) return null;
            Type type = o.GetType();
            Func<object, JToken> importer = null;
            importerDict.TryGetValue(type, out importer);
            if (importer == null)
            {
                if (IsArray(type) && type.GetArrayRank() == 1)
                { //是一维数组才会触发
                    Type elemType = type.GetElementType();
                    importerDict.TryGetValue(elemType, out importer);
                    if (importer == null) throw new Exception($"缺少Type<{elemType.Name}>的Importer");
                    JArray ja = new JArray();
                    Array arr = (Array)o;
                    for (int i = 0; i < arr.Length; i++) ja.Add(importer.Invoke(arr.GetValue(i)));
                    return ja;
                }
                else
                {
                    throw new Exception($"缺少Type<{type.Name}>的Importer");
                }
            }
            return importer.Invoke(o);
        }

        public T ParseByExporter<T>(JToken jt)
        {
            return (T)ParseByExporter(jt, typeof(T));
        }

        public object ParseByExporter(JToken jt, Type type)
        {
            if (jt.Type == JTokenType.Null) return null;
            Func<JToken, object> exporter = null;
            exporterDict.TryGetValue(type, out exporter);
            if (exporter == null)
            {
                if (IsArray(type) && type.GetArrayRank() == 1)
                { //是一维数组才会触发
                    Type elemType = type.GetElementType();
                    exporterDict.TryGetValue(elemType, out exporter);
                    if (exporter == null) throw new Exception($"缺少Type<{elemType.Name}>的Exporter");
                    var ja = (JArray)jt;
                    var arr = Array.CreateInstance(elemType, ja.Count);
                    for (int i = 0; i < arr.Length; i++) arr.SetValue(exporter.Invoke(ja[i]), i);
                    return arr;
                }
                else
                {
                    throw new Exception($"缺少Type<{type.Name}>的Exporter");
                }
            }
            return exporter.Invoke(jt);
        }

        public void FetchFieldValues(object target, JToken fromJson, params string[] fieldNames)
        {
            Type targetType = target.GetType();
            foreach (var fieldName in fieldNames)
            {
                FieldInfo targetField = targetType.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                if (targetField == null)
                {
                    PropertyInfo propertyInfo = targetType.GetProperty(fieldName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                    if (propertyInfo == null)
                    {
                        throw new Exception($"获取PropertyInfo[{fieldName}]失败");
                    }
                    else
                    {
                        object fieldValue = ParseByExporter(fromJson[fieldName], propertyInfo.PropertyType);
                        propertyInfo.SetValue(target, fieldValue);
                    }
                }
                else
                {
                    object fieldValue = ParseByExporter(fromJson[fieldName], targetField.FieldType);
                    targetField.SetValue(target, fieldValue);
                }
            }
        }

        public bool IsArray(Type type)
        {
            return type.BaseType == typeof(Array);
        }
    }
}
