using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace FObjBase.Reflect
{
    public class Reflect_SeviceClient : FObjServiceClient
    {
        public bool ignoreSet;

        protected virtual Type InterfaceType { get; }

        List<AnyEvent> anyEvents = new List<AnyEvent>();
        List<AnyCall> anyCalls = new List<AnyCall>();
        class AnyEvent
        {
            public string name;
            public string triggerName;
            public Type retType;
        }
        class AnyCall 
        {
            public string name;
            public Type retType;
        }
        Dictionary<object,string> subProperties = new Dictionary<object, string>();

        List<string> properties = new List<string>();

        public Reflect_SeviceClient(UInt32 id) : base(id) {
            init();
        }

        public Reflect_SeviceClient(UInt32 serviceId, string connName) : base(serviceId, connName) {

            init();
        }
        void init() {

            InitPropertyChanged();
            InitPropertyPush();
            InitEventPush();
            InitCall();
        }
        void InitPropertyChanged()
        {
            if (!typeof(INotifyPropertyChanged).IsAssignableFrom(InterfaceType))
                return;

            //继承了INotifyPropertyChanged
            this.PropertyChanged += Obj_PropertyChanged;

            var interfaceTypes = new List<Type>();
            interfaceTypes.Add(this.InterfaceType);
            interfaceTypes.AddRange(this.InterfaceType.GetInterfaces());

            var propertyInfos = new List<PropertyInfo>();
            foreach (var ifaceType in interfaceTypes)
            {
                propertyInfos.AddRange(ifaceType.GetProperties());
            }

            foreach (var propertyInfo in propertyInfos)
            {

                if (propertyInfo.GetCustomAttribute<JsonIgnoreAttribute>() != null)
                    continue;//这个不需要推送

                if (properties.Contains(propertyInfo.Name))
                    continue;
                properties.Add(propertyInfo.Name);
            }
        }

        void InitPropertyPush()
        {
            //处理[PropertyPush]
            var interfaceTypes = new List<Type>();
            interfaceTypes.Add(this.InterfaceType);
            interfaceTypes.AddRange(this.InterfaceType.GetInterfaces());

            var propertyInfos = new List<PropertyInfo>();
            foreach (var ifaceType in interfaceTypes)
                propertyInfos.AddRange(ifaceType.GetProperties());


            foreach (var propertyInfo in propertyInfos)
            {
                if (!IsPropertyPush(propertyInfo))
                    continue;

                InitSubPropertyPush(this.GetType().GetProperty(propertyInfo.Name), this, null);
            }
        }
        bool IsPropertyPush(PropertyInfo propertyInfo)
        {
            if (propertyInfo.GetCustomAttribute<PropertyPushAttribute>() == null)
                return false;//必须是[PropertyPush]

            if (propertyInfo.CanWrite)
                return false;//它只能是get,不能有set

            if (!typeof(INotifyPropertyChanged).IsAssignableFrom(propertyInfo.PropertyType))
                return false;//必须是从INotifyPropertyChanged派生的

            return true;
        }
        void InitSubPropertyPush(PropertyInfo propertyInfo, object obj, string parentName)
        {
            //下级推送！！！！
            var propertyValue = propertyInfo.GetValue(obj);
            string path = parentName == null ? propertyInfo.Name : $"{parentName}.{propertyInfo.Name}";
            subProperties.Add(propertyValue, path);

            ((INotifyPropertyChanged)(propertyValue)).PropertyChanged += Sub_PropertyChanged;

            //继续向下找
            var subPropertyInfos = propertyInfo.PropertyType.GetProperties();
            foreach (var subPropertyInfo in subPropertyInfos)
            {
                if (!IsPropertyPush(subPropertyInfo))
                    continue;//必须是[PropertyPush]

                InitSubPropertyPush(subPropertyInfo, propertyValue, path);
            }
        }

        void InitEventPush()
        {
            var interfaceTypes = new List<Type>();

            interfaceTypes.Add(InterfaceType);
            interfaceTypes.AddRange(InterfaceType.GetInterfaces());


            var eventInfos = new List<EventInfo>();
            foreach (var ifaceType in interfaceTypes)
            {
                eventInfos.AddRange(ifaceType.GetEvents());
            }

            foreach (var eventInfo in eventInfos)
            {
                var pushAttribute = eventInfo.GetCustomAttribute<PushAttribute>();
                if (pushAttribute == null)
                    continue;

                if (anyEvents.Any(ae => ae.name == eventInfo.Name))
                    continue;//已经添加了
                string triggerName;
                if (string.IsNullOrEmpty(pushAttribute.TriggerName))
                {
                    triggerName = "Trigger_" + eventInfo.Name;
                }
                else {
                    triggerName = pushAttribute.TriggerName;
                }
                var anyEvent = new AnyEvent()
                {
                    name = eventInfo.Name,
                    triggerName = triggerName,
                    retType = pushAttribute.EventArgsType
                };
                anyEvents.Add(anyEvent);

            }
        }

        void InitCall()
        {
            var interfaceTypes = new List<Type>();

            interfaceTypes.Add(InterfaceType);
            interfaceTypes.AddRange(InterfaceType.GetInterfaces());


            var methodInfos = new List<MethodInfo>();
            foreach (var ifaceType in interfaceTypes)
            {
                methodInfos.AddRange(ifaceType.GetMethods());
            }

            foreach (var methodInfo in methodInfos)
            {
                var callAttribute = methodInfo.GetCustomAttribute<CallAttribute>();
                if (callAttribute == null)
                    continue;

                if (anyCalls.Any(ae => ae.name == methodInfo.Name))
                    continue;//已经添加了

                var anyCall = new AnyCall()
                {
                    name = methodInfo.Name,
                    retType = callAttribute.ReponseType
                };
                anyCalls.Add(anyCall);

            }
        }
        private void Obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!IsConnected)
                return;

            if (ignoreSet)//从服务器接收的数据，不用再推送给服务器
                return;

            if (InterfaceType == null)
                return;

            if (!properties.Contains(e.PropertyName))
                return;//这个不需要推送


            var v = GetType().GetProperty(e.PropertyName).GetValue(sender);
            var jObject = new JObject();
            if(v == null)
                jObject.Add(e.PropertyName, null);
            else
                jObject.Add(e.PropertyName, JToken.FromObject(v));

            string json = jObject.ToString(Formatting.None);

            CurrObjSys.CallFunctionEx(mConn, mServerID, ID, 
                Reflect_OBJ_INTERFACE.CALL_SetProperty, 
                Misc.Converter.StringToBytes(json));
        }

        private void Sub_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (!IsConnected)
                return;

            if (ignoreSet)//从服务器接收的数据，不用再推送给服务器
                return;
            if (!subProperties.ContainsKey(sender))
                return;//异常, 不是子property

            var type = sender.GetType();

            var propertyInfo = type.GetProperty(e.PropertyName);
            if (propertyInfo == null)
                return;//获取失败！！！

            if (propertyInfo.GetCustomAttribute<JsonIgnoreAttribute>() != null)
                return;//这个不需要推送

            string path = subProperties[sender];
            string[] parentNames = path.Split('.');
            if (parentNames.Count() == 0)
                return;


            var v = propertyInfo.GetValue(sender);
            var jObject = new JObject();
            if(v == null)
                jObject.Add(propertyInfo.Name, null);
            else
                jObject.Add(propertyInfo.Name, JToken.FromObject(v));

            JObject jObject_parent = null;
            for (int i = 0; i < parentNames.Count(); i++)
            {
                int idx = parentNames.Count() - 1 - i;
                jObject_parent = new JObject();
                jObject_parent.Add(parentNames[idx], jObject);
                jObject = jObject_parent;
            }

            string json = jObject_parent.ToString(Formatting.None);


            CurrObjSys.CallFunctionEx(mConn, mServerID, ID, 
                Reflect_OBJ_INTERFACE.CALL_SetProperty, 
                Misc.Converter.StringToBytes(json));
        }

        #region FObj
        public override void ConnectNotify(IFConn from)
        {
            base.ConnectNotify(from);

            if (from.IsConnected)
            {
                //获取所有数据，设置推送
                 CurrObjSys.CallFunctionEx(
                    mConn, mServerID, ID,
                    Reflect_OBJ_INTERFACE.CALL_GetAllProperties, null);

                CurrObjSys.SenseConfigEx(
                    mConn, mServerID, ID,
                    0xffffffff,
                    SENSE_CONFIG.ADD);
            }
        }



        public override void PushInfo(IFConn from, uint srcid, ushort infoid, byte[] infodata)
        {
            switch (infoid)
            {
                case Reflect_OBJ_INTERFACE.PUSH_PropertyChanged:
                    {
                        string json = Misc.Converter.BytesToString(infodata);
                        ignoreSet = true;
                        JsonConvert.PopulateObject(json, this);
                        ignoreSet = false;
                    }
                    break;
                case Reflect_OBJ_INTERFACE.PUSH_Event:
                    {
                        if (InterfaceType == null)
                            return;

                        string json = Misc.Converter.BytesToString(infodata);
                        var rData = JsonConvert.DeserializeObject<Reflect_OBJ_INTERFACE.ReflectData>(json);

                        var anyEvent = anyEvents.Find(ae => ae.name == rData.name);
                        if (anyEvent == null)
                            return;//异常！！！


                        //触发事件！！！
                        var methodInfo = GetType().GetMethod(anyEvent.triggerName);
                        if (methodInfo == null)
                        {
                            throw new Exception($"客户端 {GetType()} 忘记写 {anyEvent.triggerName}");
                        }
                        var obj = rData.data.ToObject(anyEvent.retType);
                        //出错，就提示，肯定是客户端忘记写 "Trigger_XXXX"
                        methodInfo.Invoke(this, new object[] { obj });
                    }
                    break;
            }

        }

        public override void PushCallFunction(IFConn from, uint srcid, uint magic, ushort funcid, byte[] retdata, object asyncDelegate, object asyncContext)
        {
            switch (funcid)
            {
                case Reflect_OBJ_INTERFACE.CALL_GetAllProperties:
                    {
                        string json = Misc.Converter.BytesToString(retdata);
                        ignoreSet = true;
                        JsonConvert.PopulateObject(json, this);
                        ignoreSet = false;
                    }
                    break;
                case Reflect_OBJ_INTERFACE.CALL_MethodInvoke:
                    {
                        if (InterfaceType == null)
                            return;

                        string json = Misc.Converter.BytesToString(retdata);
                        var reponse = JsonConvert.DeserializeObject<Reflect_OBJ_INTERFACE.ReflectData>(json);
                        var anyCall = anyCalls.Find(ac => ac.name == reponse.name);
                        if (anyCall == null)
                        {
                            //异常！！！应该有[Call]注明返回的类型
                            throw new Exception($"{reponse.name} 异常！！！应该有[Call]注明返回的类型");
                        }

                        var retData = reponse.data.ToObject(anyCall.retType);
                        ((AsyncCBHandler)asyncDelegate).Invoke(asyncContext, retData);
                    }
                    break;
            }
        }
        
        
        protected void Call(string methodName, object parameters, AsyncCBHandler asyncDelegate, object asyncContext)
        {
            var rData = new Reflect_OBJ_INTERFACE.ReflectData()
            {
                name = methodName,
                data = parameters==null?null:JObject.FromObject(parameters)
            };
            string json = JsonConvert.SerializeObject(rData);

            CurrObjSys.CallFunctionEx(mConn, mServerID, ID,
                Reflect_OBJ_INTERFACE.CALL_MethodInvoke,
                Misc.Converter.StringToBytes(json),
                asyncDelegate, asyncContext);
        }
        protected void Call(string methodName, object parameters) {
            Call(methodName, parameters, null, null);
        }
        protected void Call(string methodName)
        {
            Call(methodName, null, null, null);
        }
        #endregion
    }
}
