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;

        TimeSpan actUpdateInterval;
        /// <summary>
        /// 实际更新间隔
        /// </summary>
        public TimeSpan ActUpdateInterval { get; protected set; }
        //{
        //    get { return actUpdateInterval; }
        //    protected set {
        //        if (actUpdateInterval != value)
        //        {
        //            actUpdateInterval = value;
        //            NotifyPropertyChanged("ActUpdateInterval");
        //        }
        //    }
        //}
        private Stopwatch mStopwatch = new Stopwatch();
        class RegWrite
        {
            public PLCAddressArea dataArea;
            public int addr;
            public object val;

            public RegWrite(PLCAddressArea dataArea, int addr, object val)
            {
                this.dataArea = dataArea;
                this.addr = addr;
                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)
                {
                    coils.ClearPlanState();
                    registers.ClearPlanState();
                    //复位全部状态
                    isInCommunication = false;
                    p_doing = null;
                    isUpdating = false;
                    curr_area_idx = 0;
                    // DRMap 的数据 与 下端 属性的数据一致
                    foreach (RegWrite rw in rws)
                    {
                        //更新 本地的 PLC 数据
                        SetModbusData(rw.dataArea, rw.addr, rw.val);
                    }
                    rws.Clear();

                    mStopwatch.Stop();
                }
            }
        }

        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();
                }
            }
            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)
        {
            ModbusMapper.Plan p = obj as ModbusMapper.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)
        {
            
            ModbusMapper.Plan p = obj as ModbusMapper.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>
        ModbusMapper.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 bool SetNameData(DataToRegs dr, object val)
        {
            RegWrite rw;
            switch (dr.dataArea)
            {
                case PLCAddressArea.Coil:
                    rw = rws.Find((c) => { return (c.addr == dr.addr) && (c.dataArea == PLCAddressArea.Coil); });
                    if (rw != null) rws.Remove(rw);
                    rws.Add(new RegWrite(dr.dataArea, dr.addr, dr.ToBools(val)));
                    return true;
                case PLCAddressArea.Register:
                    rw = rws.Find((c) => { return (c.addr == dr.addr) && (c.dataArea == PLCAddressArea.Register); });
                    if (rw != null) rws.Remove(rw);
                    rws.Add(new RegWrite(dr.dataArea, dr.addr, dr.ToRegs(val)));
                    return true;
            }
            return false;
        }
        /// <summary>
        /// 更新写命令缓冲区
        /// </summary>
        void UpdateWriteData()
        {
            RegWrite rw = rws.First();
            switch (rw.dataArea)
            {
                case PLCAddressArea.Coil:
                    if (mclient.Do_0F((UInt16)rw.addr, (bool[])(rw.val), Do_0F_callback, null) != ModbusClient_Errno.OK)
                        return;
                    SetModbusData(rw.dataArea, rw.addr, rw.val);
                    break;
                case PLCAddressArea.Register:
                    if (mclient.Do_10((UInt16)rw.addr, (UInt16[])rw.val, Do_10_callback, null) != ModbusClient_Errno.OK)
                        return;
                    SetModbusData(rw.dataArea, rw.addr, rw.val);
                    break;
            }

            //更新 本地的 PLC 数据
            SetModbusData(rw.dataArea, rw.addr , rw.val);
            rws.Remove(rw);
            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;
        }
    }

}