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;

namespace FLY.OBJComponents.Server
{
    /// <summary>
    /// 属性更新计划
    /// </summary>
    public class PLCProxySystem : IPLCProxySystemService, IPropertyOpt
    {
        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; } = new Dictionary<string, INotifyPropertyChanged>();

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

        public event PropertyChangedEventHandler PropertyChanged;

        public PLCProxySystem()
        {


        }
        public void Init()
        {
            foreach (var plc in PLCs)
            {
                plc.NameDataChanged += plc_NameDataChanged;
            }

            
            Misc.BindingOperations.SetBinding(PLCs[0].Client, "IsConnected", () =>
            {
                IsConnectedWithPLC = PLCs[0].Client.IsConnected;
                //if (!IsConnectedWithPLC)
                //    ClearPlan();
            });

            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();
        }

        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

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

        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(dr.owner, dr.propertyName, dr.value);
                isShield = false;

            }), dr.owner, dr.propertyName, dr.value);
            
        }
        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, SetPlanReponseHandler reponseHandler, object context)
        {
            long planID = GetFreePlanID();
            SetPlan(objname, propertynames, planID);
            reponseHandler(planID, context);
        }
        /// <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[] { "IsConnectedWithPLC" };
        }

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