using System ;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Text;

namespace FLY.Modbus
{
    /// <summary>
    /// 学习HMI,  根据 property 与 PLC寄存器 映射表 自动更新
    /// 
    /// 使用说明:
    /// 1.注册 NameDataChanged 事件, 当从PLC读取的数据更新后，通知property修改
    /// NameDataChanged += ......
    /// 
    /// 2.填写 property 与 PLC寄存器 映射表 
    /// DRMap
    /// 
    /// 3.通过FindDataMap() 找到 映射 注册需要更新的属性 
    /// dr = FindDataMap(obj, propertyName)
    /// key = 页面对应obj
    /// PlanAdd(key, dr)
    /// 
    /// 4.创建更新计划
    /// PlanMake()
    /// 
    /// 5.页面关闭，不需要再更新 property， 注销更新计划
    /// key = 页面对应obj
    /// PlanRemove(key)
    /// 
    /// 6.当property 修改后，调用 SetNameData 设置PLC
    /// SetNameData(obj, propertyName, propertyValue)
    /// </summary>
    public class ModbusMapper_Client : ModbusMapper
    {
        /// <summary>
        /// 更新周期，单位ms，
        /// </summary>
        public int UpdateInterval = 200;

        /// <summary>
        /// 实际更新间隔
        /// </summary>
        public TimeSpan ActUpdateInterval { get; protected set; }


        private Stopwatch mStopwatch = new Stopwatch();
        class RegWrite
        {
            public DataToRegs dr;
            public object val;

            public RegWrite(DataToRegs dr, object val)
            {
                this.dr = dr;
                this.val = val;
            }
        }

        List<RegWrite> rws = new List<RegWrite>();

        /// <summary>
        /// tcp client
        /// </summary>
        public ClientTCP mclient;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="clienttcp"></param>
        public ModbusMapper_Client(ClientTCP clienttcp)
        {
            mclient = clienttcp;

            FObjBase.PollModule.Current.Poll_Config(OnPoll_update);

            mclient.PropertyChanged += Mclient_PropertyChanged;
        }



        public override string ToString()
        {
            return mclient.ToString();
        }
        private void Mclient_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsConnected")
            {
                if (!mclient.IsConnected)
                {
                    foreach (var areaManager in mAreaManager)
                        areaManager.ClearPlanState();

                    //复位全部状态
                    isInCommunication = false;
                    p_doing = null;
                    isUpdating = false;
                    curr_area_idx = 0;
                    // DRMap 的数据 与 下端 属性的数据一致
                    //foreach (var areaManager in mAreaManager)
                    //    areaManager.UpdatePLCObjFromProperty();

                    //rws.Clear();

                    mStopwatch.Stop();
                }
                else
                {
                    foreach (var areaManager in mAreaManager)
                        areaManager.SetAllIsPlcValueChanged();
                    rws.Clear();
                }
            }
        }

        void OnPoll_update()
        {
            if (!mclient.IsConnected)
                return;

            if (!mStopwatch.IsRunning)
                mStopwatch.Restart();
            
            if (mStopwatch.ElapsedMilliseconds >= UpdateInterval)
            {
                //检查上次更新周期是否完成
                if (!isUpdating)
                {
                    isUpdating = true;
                    ActUpdateInterval = mStopwatch.Elapsed;
                    mStopwatch.Restart();

                    foreach (var areaManager in mAreaManager)
                    {
                        areaManager.MakePlanReal();
                    }
                }
            }
            NextPlan();
        }

        int curr_area_idx = 0;
        void NextPlan()
        {
            if (isInCommunication)//已经在通讯中
                return;

            if (rws.Count() > 0)
                UpdateWriteData();


            if (isUpdating)
            {
                p_doing = mAreaManager[curr_area_idx].GetNextPlan();
                if (p_doing != null)
                {
                    p_doing.isDoing = false;
                    UpdateReadData();
                }
                else
                {
                    //读完
                    curr_area_idx++;
                    if (curr_area_idx >= mAreaManager.Count())
                    {
                        isUpdating = false;
                        //读完一轮了
                        curr_area_idx = 0;
                    }
                    else
                    {
                        NextPlan();
                    }
                }
            }
        }

        /// <summary>
        /// 读 多个线圈回调
        /// </summary>
        /// <param name="val"></param>
        /// <param name="obj"></param>
        void Do_01_callback(bool[] val, object obj)
        {
            Plan p = obj as Plan;

            p.isDoing = false;
            SetModbusData(PLCAddressArea.Coil, p.addr, val);
            isInCommunication = false;
            NextPlan();
        }
        /// <summary>
        /// 读多个寄存器回调
        /// </summary>
        /// <param name="val"></param>
        /// <param name="obj"></param>
        void Do_03_callback(UInt16[] val, object obj)
        {
            
            Plan p = obj as Plan;
            p.isDoing = false;
            SetModbusData(PLCAddressArea.Register, p.addr, val);
            isInCommunication = false;
            NextPlan();
        }

        void Do_0F_callback(object obj)
        {
            isInCommunication = false;
            NextPlan();
        }
        void Do_10_callback(object obj)
        {
            isInCommunication = false;
            NextPlan();
        }
        /// <summary>
        /// 正在通讯中
        /// </summary>
        bool isInCommunication = false;
        /// <summary>
        /// 读更新流程中
        /// </summary>
        bool isUpdating = false;
        /// <summary>
        /// 读更新 的计划
        /// </summary>
        Plan p_doing = null;
        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="propertyname"></param>
        //protected void NotifyPropertyChanged(string propertyname)
        //{
        //    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyname));
        //}
        ///// <summary>
        ///// 
        ///// </summary>
        //public event PropertyChangedEventHandler PropertyChanged;


        /// <summary>
        /// PC -> PLC
        /// 为命名数据赋值，会改变Modbus的寄存器值，并产生通知
        /// </summary>
        /// <param name="dr">数据映射</param>
        /// <param name="val">数据值，不能为空</param>
        /// <returns></returns>
        public override void SetNameData(DataToRegs dr, object val)
        {
            RegWrite rw;
            //rw = rws.Find((c) => { return (c.dr == dr); });
            //if (rw != null)
            //    rws.Remove(rw);

            rw = new RegWrite(dr, val);
            rws.Add(rw);
        }
        /// <summary>
        /// 更新写命令缓冲区
        /// </summary>
        void UpdateWriteData()
        {
            RegWrite rw = rws.First();
            rws.Remove(rw);

            //更新 本地的 PLC 数据
            rw.dr.isPlcValueChanged = true;

            switch (rw.dr.dataArea)
            {
                case PLCAddressArea.Coil:
                    if (mclient.Do_0F((UInt16)rw.dr.addr, new bool[] { (bool)rw.dr.plcValue }, Do_0F_callback, null) != ModbusClient_Errno.OK)
                        return;
                    break;
                case PLCAddressArea.Register:
                    if (mclient.Do_10((UInt16)rw.dr.addr, (UInt16[])rw.dr.plcValue, Do_10_callback, null) != ModbusClient_Errno.OK)
                        return;
                    break;
            }

            isInCommunication = true;
        }
        void UpdateReadData()
        {
            switch (p_doing.area)
            {
                case PLCAddressArea.Coil:
                    mclient.Do_01((UInt16)p_doing.addr, (UInt16)p_doing.num, Do_01_callback, p_doing);
                    break;
                case PLCAddressArea.Register:
                    mclient.Do_03((UInt16)p_doing.addr, (UInt16)p_doing.num, Do_03_callback, p_doing);
                    break;
            }
            p_doing.isDoing = true;
            isInCommunication = true;
        }


    }

}
