using FLY.OBJComponents.IService;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using FLY.Modbus;
using System.Windows.Threading;
using System.IO;
using Newtonsoft.Json;
using FObjBase;

namespace FLY.OBJComponents.Server
{
    /// <summary>
    /// 属性更新计划
    /// </summary>
    public class PLCProxySystem : IPLCProxySystemService, IPropertyOpt
    {
        NLog.ILogger logger = NLog.LogManager.GetCurrentClassLogger();
        /// <summary>
        /// 用于调试， 在这个表内的属性不输出调试
        /// </summary>
        public List<SenderProperty> IgnoreLogProperties = new List<SenderProperty>();
        class Plan
        {
            /// <summary>
            /// 唯一ID
            /// </summary>
            public long ID;
            /// <summary>
            /// 上一次喂的时间
            /// </summary>
            public DateTime FeedTime;
            /// <summary>
            /// plc
            /// </summary>
            public List<ModbusMapper> mappers = new List<ModbusMapper>();
        }

        List<Plan> planIDs = new List<Plan>();
        public List<DataToRegs> DRMap = new List<DataToRegs>();
        public List<Modbus.WithThread.ModbusMapper_Client> PLCs = new List<Modbus.WithThread.ModbusMapper_Client>();
        public Dictionary<string, INotifyPropertyChanged> ObjNames { get; private set; } = new Dictionary<string, INotifyPropertyChanged>();

        /// <summary>
        /// 与PLC连接成功
        /// </summary>
        public bool IsConnectedWithPLC { get; private set; }

        public event PropertyChangedEventHandler PropertyChanged;

        public PLCProxySystem()
        {


        }
        public void AddConfigFile(
            string configfile, 
            Func<PLCGroup,Dictionary<string,INotifyPropertyChanged>> addObjNames)
        {
            PLCGroup plcgroup = new PLCGroup();
            string json = File.ReadAllText(configfile);
            JsonConvert.PopulateObject(json, plcgroup);

            foreach (PLCGroup.PLCDevice device in plcgroup.Devices)
            {
                var plc = new Modbus.WithThread.ModbusMapper_Client(device.EP);
                this.PLCs.Add(plc);
            }

            var objnames = addObjNames(plcgroup);
            this.ObjNames = objnames;


            foreach (PLCGroup.PLCVariable var in plcgroup.Variables)
            {
                if (var.DeviceIndex < 0 || var.DeviceIndex >= this.PLCs.Count)
                    continue;

                List<DataToRegs> drs = this.DRMap;
                var plc = this.PLCs[var.DeviceIndex];

                var reg_type = ModbusMapper.TranslateToREG_TYPE(var.Type);
                if (reg_type == REG_TYPE.Unknown)
                {
                    throw new Exception($"DeviceIndex = {var.DeviceIndex} var.Addr={var.Addr} var.Type={var.Type} 不能转换为 C#类型");
                }
                try
                {
                    DataToRegs dr = plc.MapDataToRegs(
                        ModbusMapper.TranslateToPLCAddressArea(var.Mode),
                        var.Addr,
                        reg_type,
                        var.Scale,
                        this.ObjNames[var.OwnerName],
                        var.PropertyName,
                        var.RegAddr,
                        var.OwnerName);
                    if (dr != null)
                        drs.Add(dr);
                }
                catch (Exception e) {
                    throw e;
                }
            }

            foreach (var plc in this.PLCs)
                plc.Build();
        }

        /// <summary>
        /// PLC 更新计划服务初始化
        /// </summary>
        public void Init()
        {
            foreach (var plc in PLCs)
            {
                plc.NameDataChanged += Plc_NameDataChanged;
            }



            //任何一个PLC断开，也会显示连接断开
            IsConnectedWithPLC = PLCs.All(plc => plc.Client.IsConnected);
            foreach (var plc in PLCs)
            {
                plc.Client.PropertyChanged += (s, e) =>
                {
                    if (e.PropertyName == nameof(Modbus.WithThread.ClientTCP.IsConnected)) {
                        IsConnectedWithPLC = PLCs.All(p => p.Client.IsConnected);
                    }
                };
            }

            foreach (var obj in ObjNames.Values)
            {
                obj.PropertyChanged += Obj_PropertyChanged;
            }


            FObjBase.PollModule.Current.Poll_Config(FObjBase.PollModule.POLL_CONFIG.ADD,
                OnPoll_plans, TimeSpan.FromSeconds(1));

            //启动！！！！
            foreach (var plc in PLCs)
                plc.Start();
            //PLCs[0].Start();
        }


        void LogProperty(object sender, string propertyName, DataToRegs dr) 
        {

            //注解
            var property = sender.GetType().GetProperty(propertyName);
            var attrs = property.GetCustomAttributes(typeof(DescriptionAttribute), false);
            string desp = "";
            if (attrs.Count() > 0)
            {
                desp = (attrs.First() as DescriptionAttribute).Description;
            }

            //新值
            var new_value = property.GetValue(sender, null);

            //旧值
            var old_value = dr.mapper.GetNameData(sender, propertyName);

            //PLC地址
            string plcAddr = $"{(dr.dataArea == PLCAddressArea.Coil ? "M" : "D")}{dr.addr}";

            //对象名称
            string objName = "";
            var names = from kv in ObjNames where kv.Value == sender select kv.Key;
            if (names.Count() > 0)
                objName = names.First();

            logger.Info($"{objName}.{propertyName} [{desp}] [{plcAddr}] ({old_value})->({new_value})");
        }
        private void Obj_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (isShield)
                return;
            List<DataToRegs> drs = DRMap;

            DataToRegs dr = drs.Find((_dr) => { return (_dr.propertyName == e.PropertyName) && (_dr.owner == sender); });
            if (dr == null)
                return;//不会刷新PLC


            if (logger.IsInfoEnabled) 
            {
                if (!IgnoreLogProperties.Exists(sp => sp.sender == sender && sp.propertyName == e.PropertyName))
                {
                    LogProperty(sender, e.PropertyName, dr);
                }
            }

            //向PLC写入数据，mRegs每次只能写入一个，它有列表保存功能。
            //SetNameData可以执行很多次，不过它只能一个个发。
            dr.mapper.SetNameData(dr, Misc.PropertiesManager.GetValue(sender, e.PropertyName));
        }

        /// <summary>
        /// 屏蔽 "属性变化 写入PLC"
        /// </summary>
        bool isShield = false;



        void Plc_NameDataChanged(object sender, DataToRegs dr)// NameDataChangedEventArgs e)
        {
            //var plc = sender as Modbus.WithThread.ModbusMapper_Client;
            //object value = plc.GetNameData(e.Owener, e.PropertyName);
            //这是从PLC 更新过来的，不需要向PLC写入

            //FObjBase.PollModule.Current.Dispatcher.Invoke(new SetValueHandler((owner, propertyName, value) =>
            //{
            //    isShield = true;
            //    Misc.PropertiesManager.SetValue(owner, propertyName, value);
            //    isShield = false;

            //}), dr.owner, dr.propertyName, dr.value);

            FObjBase.PollModule.Current.Dispatcher.Invoke(() =>
            {
                isShield = true;
                Misc.PropertiesManager.SetValue(dr.owner, dr.propertyName, dr.value);
                isShield = false;
            });
        }
        //delegate void SetValueHandler(object owner, string propertyname, object value);
        /// <summary>
        /// 设置更新计划
        /// </summary>
        /// <param name="objname">对象名称</param>
        /// <param name="propertynames"></param>
        /// <param name="planID">计划的编号，应该全局唯一,建议使用时间ticks</param>
        public void SetPlan(string objname, IEnumerable<string> propertynames, long planID)
        {
            Plan plan = planIDs.Find(p => p.ID == planID);
            if (plan == null)
            {
                plan = new Plan() { ID = planID, FeedTime = DateTime.Now };
                planIDs.Add(plan);
            }
            else
            {
                plan.FeedTime = DateTime.Now;
            }
            

            List<DataToRegs> drs = DRMap;
            foreach (string propertyname in propertynames)
            {
                if (!ObjNames.ContainsKey(objname))
                    continue;
                DataToRegs dr = drs.Find((_dr) => { return (_dr.propertyName == propertyname && _dr.owner == ObjNames[objname]); });
                if (dr == null)
                {
                    continue;
                    //throw new Exception($"PLCProxySystem.SetPlan 不能找到 {objname}.{propertyname}");
                }

                dr.mapper.PlanAdd(planID, dr);
                if (!plan.mappers.Contains(dr.mapper))
                    plan.mappers.Add(dr.mapper);
            }
            foreach (var mapper in plan.mappers)
            {
                mapper.PlanMake();
            }

        }

        long freeplanid = 1;
        long GetFreePlanID()
        {
            long planid = freeplanid;
            freeplanid++;
            if (freeplanid == 0)
                freeplanid = 1;
            return planid;
        }

        /// <summary>
        /// 设置更新计划
        /// </summary>
        /// <param name="objname">对象名称</param>
        /// <param name="propertynames"></param>
        /// <param name="planID">计划的编号，应该全局唯一,建议使用时间ticks</param>
        public void SetPlan(string objname, IEnumerable<string> propertynames, AsyncCBHandler asyncDelegate, object asyncContext)
        {
            long planID = GetFreePlanID();
            SetPlan(objname, propertynames, planID);
            asyncDelegate?.Invoke(asyncContext, planID);
        }
        /// <summary>
        /// 更新计划持续，如果不喂狗，20s后停止更新数据
        /// </summary>
        /// <param name="planID">计划的编号</param>
        public void FeedPlan(long planID)
        {
            Plan plan = planIDs.Find(p => p.ID == planID);
            if (plan != null)
            {
                plan.FeedTime = DateTime.Now;
            }
        }

        /// <summary>
        /// 主动删除某个计划
        /// </summary>
        /// <param name="planID"></param>
        public void RemovePlan(long planID)
        {
            ClearPlan((plan) => (plan.ID == planID));
        }

        /// <summary>
        /// 清除 非0 全部计划
        /// </summary>
        void ClearPlan()
        {
            //if (planIDs.Count() > 0)
            //{
            //    foreach (var plc in PLCs)
            //    {
            //        plc.PlanClear();
            //        plc.PlanMake();
            //    }
            //    planIDs.Clear();
            //}
            ClearPlan((plan) => (plan.ID != 0));
        }

        void ClearPlan(Func<Plan, bool> condition)
        {
            if (planIDs.Count() > 0)
            {
                var _remove =
                    from plan in planIDs
                    where condition(plan)
                    select plan;

                if (_remove.Count() > 0)
                {
                    List<Plan> remove = new List<Plan>(_remove);

                    List<ModbusMapper> mappers = new List<ModbusMapper>();
                    foreach (var plan in remove)
                    {
                        planIDs.Remove(plan);
                        foreach (var mapper in plan.mappers)
                        {
                            mapper.PlanRemove(plan.ID);
                            if (!mappers.Contains(mapper))
                                mappers.Add(mapper);
                        }
                    }
                    foreach (var mapper in mappers)
                    {
                        mapper.PlanMake();
                    }
                }
            }
        }
        
        /// <summary>
        /// 1s 一次，看出哪个令牌过期
        /// </summary>
        void OnPoll_plans()
        {
            DateTime now = DateTime.Now;
            ClearPlan((plan) => (plan.ID != 0) && (now - plan.FeedTime) > TimeSpan.FromMinutes(2));
        }

        public string[] GetSyncPropNames()
        {
            return new string[] { nameof(IsConnectedWithPLC) };
        }

        public string[] GetNoSyncPropNames()
        {
            return null;
        }
    }

    public class SenderProperty 
    {
        public object sender;
        public string propertyName;
    }
}
