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

namespace FObjBase.Reflect
{
    public class Reflect_Proxy : FObj
    {
        Type interfaceType;
        object obj;
        public bool ignoreSet = false;
        List<AnyEvent> anyEvents = new List<AnyEvent>();
        Dictionary<object, string> subProperties = new Dictionary<object, string>();
        List<string> properties = new List<string>();

        public Reflect_Proxy(int objsys_idx, UInt32 id, Type interfaceType, object obj) : base(objsys_idx)
        {
            ID = id;

            this.interfaceType = interfaceType;
            this.obj = obj;

            InitPropertyChanged();

            //处理[PropertyPush]
            InitPropertyPush();

            //处理[Push]
            InitEventPush();
        }
        void InitPropertyChanged() 
        {
            if (!typeof(INotifyPropertyChanged).IsAssignableFrom(interfaceType))
                return;

            //继承了INotifyPropertyChanged
            ((INotifyPropertyChanged)(this.obj)).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 InitEventPush() 
        {
            var interfaceTypes = new List<Type>();

            interfaceTypes.Add(this.interfaceType);
            interfaceTypes.AddRange(this.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.eventName == eventInfo.Name))
                    continue;//已经添加了

                var anyEvent = new AnyEvent()
                {
                    eventName = eventInfo.Name,
                    PushObjInfoEx = (msg) => {
                        var buf = Misc.Converter.StringToBytes(msg);
                        CurrObjSys.PushObjInfoEx(
                            this, Reflect_OBJ_INTERFACE.PUSH_Event,
                            buf);
                    }
                };
                //这个事件需要推送
                eventInfo.AddEventHandler(obj, anyEvent.eventHandler);
                anyEvents.Add(anyEvent);
                
            }
        }
        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;

                if (!properties.Contains(propertyInfo.Name)) {
                    properties.Add(propertyInfo.Name);
                }

                InitSubPropertyPush(this.obj.GetType().GetProperty(propertyInfo.Name), this.obj, 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);
            }
        }

        class AnyEvent
        {
            public string eventName;
            public Action<string> PushObjInfoEx;
            public EventHandler eventHandler;
            public AnyEvent()
            {
                eventHandler = new EventHandler(Obj_AnyEvent);
            }
            void Obj_AnyEvent(object sender, EventArgs e)
            {

                var rData = new Reflect_OBJ_INTERFACE.ReflectData()
                {
                    name = eventName,
                    data = JObject.FromObject(e)
                };
                string reponse = JsonConvert.SerializeObject(rData);
                PushObjInfoEx?.Invoke(reponse);
            }
        }


        
        private void Obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (ignoreSet)//从服务器接收的数据，不用再推送给服务器
                return;

            if (interfaceType == null)
                return;

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

            var v = this.obj.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);
            var buf = Misc.Converter.StringToBytes(json);
            CurrObjSys.PushObjInfoEx(
                this, Reflect_OBJ_INTERFACE.PUSH_PropertyChanged,
                buf);
        }

        private void Sub_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            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.PushObjInfoEx( this, 
                Reflect_OBJ_INTERFACE.PUSH_PropertyChanged,
                Misc.Converter.StringToBytes(json));
        }

        public override void CallFunction(IFConn from, uint srcid, uint magic, ushort funcid, byte[] infodata)
        {
            switch (funcid)
            {
                case Reflect_OBJ_INTERFACE.CALL_GetAllProperties:
                    {
                        var jObject = new JObject();

                        var type = obj.GetType();

                        foreach (var propertyName in properties)
                        {
                            var propertyInfo = type.GetProperty(propertyName);
                            var v = propertyInfo.GetValue(obj);
                            if(v == null)
                                jObject.Add(propertyInfo.Name, null);
                            else
                                jObject.Add(propertyInfo.Name, JToken.FromObject(v));
                        }
                        string json = jObject.ToString(Formatting.None);

                        CurrObjSys.PushCallFunctionEx(from, srcid, ID, magic, funcid, Misc.Converter.StringToBytes(json));
                    }
                    break;
                
                case Reflect_OBJ_INTERFACE.CALL_SetProperty:
                    {
                        string json = Misc.Converter.BytesToString(infodata);
                        ignoreSet = true;
                        JsonConvert.PopulateObject(json, obj);
                        ignoreSet = false;
                    }
                    break;

                case Reflect_OBJ_INTERFACE.CALL_MethodInvoke:
                    {
                        string json = Misc.Converter.BytesToString(infodata);
                        var rData = JsonConvert.DeserializeObject<Reflect_OBJ_INTERFACE.ReflectData>(json);
                        var type = obj.GetType();
                        var methodInfo = type.GetMethod(rData.name);
                        var parameterInfos = methodInfo.GetParameters();
                        object[] parameters = new object[parameterInfos.Count()];
                        for (int i = 0; i < parameters.Count(); i++)
                        {
                            var ptype = parameterInfos[i].ParameterType;
                            var pname = parameterInfos[i].Name;
                            if (string.Compare(pname,"asyncDelegate",true)==0)
                            {
                                parameters[i] = new AsyncCBHandler(asyncDelegate);
                            }
                            else if (string.Compare(pname, "asyncContext", true) == 0)
                            {
                                parameters[i] = new CC { from = from, srcid = srcid, magic = magic, methodName = rData.name };
                            }
                            else
                            {
                                parameters[i] = rData.data[pname].ToObject(ptype);
                            }
                        }
                        methodInfo.Invoke(obj, parameters);
                    }
                    break;
            }
        }
        void asyncDelegate(object asyncContext, object retData)
        {
            var cc = (CC)asyncContext;
            
            var rData = new Reflect_OBJ_INTERFACE.ReflectData()
            {
                name = cc.methodName,
                data = retData==null?null:JToken.FromObject(retData)
            };

            string json = JsonConvert.SerializeObject(rData);
            CurrObjSys.PushCallFunctionEx(
                cc.from,
                cc.srcid,
                ID,
                cc.magic,
                Reflect_OBJ_INTERFACE.CALL_MethodInvoke,
                Misc.Converter.StringToBytes(json));
        }
        class CC
        {
            public IFConn from;
            public UInt32 srcid;
            public UInt32 magic;
            public string methodName;
        }
    }
}
