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

namespace FLY.Modbus
{
    /// <summary>
    /// 命名数据到Modbus数据的映射
    /// </summary>
    public abstract class ModbusMapper : INotifyPropertyChanged
    {
        /// <summary>
        /// 一次读取的数据量
        /// </summary>
        const int MaxRegsOfOneRead = 100;
        const int MaxCoilsOfOneRead = 900;

        #region 数据变化通知
        /// <summary>
        /// 命名数据改变事件
        /// </summary>
        public event NameDataChangedEventHandler NameDataChanged;
        protected void NotifyNameDataChanged(DataToRegs dr)// object dataclass, string dataname)
        {
            NameDataChanged?.Invoke(this, dr);// new NameDataChangedEventArgs(dataclass, dataname));
        }
        
        public event PropertyChangedEventHandler PropertyChanged;


        #endregion

        /// <summary>
        /// 数据映射表，根据这个表，以后自动更新PLC寄存器，或更新property
        /// </summary>
        public List<DataToRegs> DRmap = new List<DataToRegs>();

        /// <summary>
        /// 
        /// </summary>
        public ModbusMapper()
        {
            coilData = new CoilData(PLCAddressArea.Coil, MaxCoilsOfOneRead);
            registerData = new RegisterData(PLCAddressArea.Register, MaxRegsOfOneRead);
            mAreaManager = new List<AreaManager>
            {
                coilData,
                registerData
            };
        }
        #region 数据映射

        /// <summary>
        /// 查找数据-寄存器对应表
        /// </summary>
        public DataToRegs FindDataMap(object owner, string propertyName)
        {
            return DRmap.Find((dr) => { return (dr.propertyName == propertyName && dr.owner == owner); });
        }

        /// <summary>
        /// 数据映射到寄存器
        /// </summary>
        /// <param name="dataArea">modbus 寄存器类型</param>    
        /// <param name="addr">modbus 寄存器首地址</param> 
        /// <param name="type">plc 数据对应类型</param>
        /// <param name="scale">plc 数据放大倍数</param>
        /// <param name="obj">Property 所属对象</param>    
        /// <param name="propertyname">Property Name</param>   
        /// <returns></returns>
        public DataToRegs MapDataToRegs(PLCAddressArea dataArea, int addr, REG_TYPE type, double scale, object obj, string propertyname, string regAddr, string ownerName)
        {
            DataToRegs dr = FindDataMap(obj, propertyname);
            if (dr == null)
            {
                dr = new DataToRegs(this, dataArea, addr, type, scale, obj, propertyname, regAddr, ownerName);
                DRmap.Add(dr);
                DRmap.Sort(delegate (DataToRegs X, DataToRegs Y)
                {
                    int ct = X.dataArea.CompareTo(Y.dataArea);
                    if (ct != 0)
                        return ct;
                    
                    return X.addr.CompareTo(Y.addr);
                });
                return dr;
            }

            return null;
        }




        #endregion

        #region Modbus数据
        /// <summary>
        /// 就是 coils 与 registers 的组合
        /// </summary>
        protected List<AreaManager> mAreaManager;
        protected CoilData coilData;
        protected RegisterData registerData;
        #endregion

        #region 数据读写
        /// <summary>
        /// 为命名数据赋值，会改变Modbus的寄存器值，并产生通知
        /// </summary>
        /// <param name="obj">属性所属的对象, 可为null</param>
        /// <param name="propertyName">属性名，不能为空</param>
        /// <param name="val">数据值，不能为空</param>
        /// <returns>赋值的结果</returns>
        public virtual bool SetNameData(object obj, string propertyName, object val)
        {

            DataToRegs dr = FindDataMap(obj, propertyName);
            if (dr == null) return false;
            SetNameData(dr, val);
            return true;
        }

        /// <summary>
        /// PC想向PLC写入数据；
        /// 为命名数据赋值，会改变Modbus的寄存器值，并产生通知
        /// </summary>
        /// <param name="dr">数据映射</param>
        /// <param name="val">数据值，不能为空</param>
        /// <returns></returns>
        public abstract void SetNameData(DataToRegs dr, object val);
        

        /// <summary>
        /// 取命名数据的值
        /// </summary>
        /// <param name="obj">属性所属 的对象</param>
        /// <param name="propertyName">属性名</param>
        /// <returns>属性的值</returns>
        public virtual object GetNameData(object obj, string propertyName)
        {
            DataToRegs dr = FindDataMap(obj, propertyName);
            if (dr == null) return null;
            return dr.value;
        }

        #region IModbusRegister实现


        /// <summary>
        /// 是从PLC读取数据后；
        /// 设置Modbus的值，会改变相应命名数据的值，并产生通知
        /// </summary>
        /// <param name="dataarea">coil or register</param>
        /// <param name="addr">地址</param>
        /// <param name="value">值</param>
        /// <returns></returns>
        public virtual void SetModbusData(PLCAddressArea dataarea, int addr, object value)
        {
            switch (dataarea)
            {
                case PLCAddressArea.Register:
                    {
                        IEnumerable<UInt16> vals = (IEnumerable<UInt16>)value;
                        //找寄存器
                        int endAddr = addr + vals.Count() - 1;

                        RegisterData area_regs = registerData;

                        for (int i = 0; i < area_regs.regs.Count(); i++)
                        {
                            DataToRegs dr = area_regs.regs[i];
                            if (endAddr < dr.addr)
                            {
                                //当前输入的全部PLC寄存器 在 dr的前面

                                break;
                            }
                            else if (addr <= dr.endAddr)//有交集
                            {
                                //计算交集
                                int addr_act = Math.Max(addr, dr.addr);
                                int endAddr_act = Math.Min(endAddr, dr.endAddr);
                                int num_act = endAddr_act - addr_act + 1;//不需要检测<=0, 当为0，下面的循环跑不动

                                for (int j = 0; j < num_act; j++)
                                {
                                    int idx1 = addr_act - dr.addr + j;
                                    int idx2 = addr_act - addr + j;
                                    UInt16[] plc_value = (UInt16[])dr.plcValue;
                                    if (plc_value[idx1] != vals.ElementAt(idx2))
                                    {
                                        plc_value[idx1] = vals.ElementAt(idx2);
                                        dr.isPlcValueChanged = true;//其中 一个PLC寄存器 被改变了
                                    }
                                }
                                if (endAddr_act == dr.endAddr)//枚举到 dr 的最后一个寄存器了，触发动作
                                {
                                    if (dr.isPlcValueChanged)
                                    {
                                        dr.isPlcValueChanged = false;
                                        dr.ToPropertyObj();
                                        //触发事件
                                        NotifyNameDataChanged(dr);
                                    }
                                }
                            }
                            else
                            {
                                //在后面
                            }
                        }
                    }break;
                case PLCAddressArea.Coil:
                    {
                        IEnumerable<bool> vals = (IEnumerable<bool>)value;
                        //找寄存器
                        int endAddr = addr + vals.Count() - 1;

                        CoilData area_coils = coilData; 

                        for (int i = 0; i < area_coils.regs.Count(); i++)
                        {
                            DataToRegs dr = area_coils.regs[i];
                            if (endAddr < dr.addr)
                            {
                                //在前面

                                break;
                            }
                            else if (addr <= dr.addr)//有交集
                            {
                                int addr_act = dr.addr;
                                
                                int idx2 = addr_act - addr;

                                if ((bool)dr.plcValue != vals.ElementAt(idx2))
                                {
                                    dr.plcValue = vals.ElementAt(idx2);
                                    dr.isPlcValueChanged = true;
                                }

                                //isPlcValueChanged 可能是上面值不一样 被设为true， 也可能是 客户端 发送数据到 PLC ，所以被强制设置为 true
                                if (dr.isPlcValueChanged) { 
                                    dr.ToPropertyObj();
                                    //触发事件
                                    NotifyNameDataChanged(dr);
                                }
                            }
                            else
                            {
                                //在后面
                            }
                        }
                    }break;
            }
        }


        #endregion
        #endregion

        #region 类的静态函数
        /// <summary>
        /// C#类型，对应PLC寄存器个数
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static int RegSize(REG_TYPE type)
        {
            switch (type)
            {
                case REG_TYPE.FLOAT:
                    return sizeof(float) / sizeof(Int16);
                case REG_TYPE.UINT16:
                    return sizeof(UInt16) / sizeof(Int16);
                case REG_TYPE.INT32:
                    return sizeof(Int32) / sizeof(Int16);
                case REG_TYPE.INT16:
                    return sizeof(Int16) / sizeof(Int16);
                case REG_TYPE.UINT32:
                    return sizeof(Int32) / sizeof(Int16);
                default:
                    {
                        throw new NotImplementedException("不支持类型 " + type);
                    }
            }
        }


        /// <summary>
        /// string 转 C#类型
        /// </summary>
        /// <param name="typename"></param>
        /// <returns></returns>
        public static REG_TYPE TranslateToREG_TYPE(string typename)
        {
            typename = typename.ToLower();
            if (typename == "int32")
                return REG_TYPE.INT32;
            if (typename == "uint32")
                return REG_TYPE.UINT32;
            if (typename == "bool")
                return REG_TYPE.BOOL;
            if (typename == "int16")
                return REG_TYPE.INT16;
            if (typename == "uint16")
                return REG_TYPE.UINT16;
            if (typename == "float")
                return REG_TYPE.FLOAT;
            return REG_TYPE.Unknown;
        }
        /// <summary>
        /// string 转modbus类型
        /// </summary>
        /// <param name="dm"></param>
        /// <returns></returns>
        public static PLCAddressArea TranslateToPLCAddressArea(string dm)
        {
            if (dm.CompareTo("0") == 0) return PLCAddressArea.Coil;
            if (dm.CompareTo("1") == 0) return PLCAddressArea.Coil;
            if (dm.CompareTo("3") == 0) return PLCAddressArea.Register;
            if (dm.CompareTo("4") == 0) return PLCAddressArea.Register;
            else {
                throw new Exception("{0} 不是合法的 PLCAddressArea类型");
            }
        }
        #endregion

        #region 寄存器读取更新计划
        Dictionary<object, List<DataToRegs>> plans = new Dictionary<object, List<DataToRegs>>();
        /// <summary>
        /// 登记寄存器更新计划
        /// </summary>
        public void PlanAdd(object key, DataToRegs dr)
        {
            if (plans.ContainsKey(key))
            {
                if (!plans[key].Contains(dr))
                    plans[key].Add(dr);
            }
            else
            {
                plans.Add(key, new List<DataToRegs>());
                plans[key].Add(dr);
            }
        }
        /// <summary>
        /// 删除寄存器更新计划
        /// </summary>
        /// <param name="key"></param>
        public void PlanRemove(object key)
        {
            plans.Remove(key);
        }
        /// <summary>
        /// 删除全部寄存器更新计划
        /// </summary>
        public void PlanClear()
        {
            plans.Clear();
        }

        /// <summary>
        /// 需要被更新的 寄存器数量
        /// </summary>
        public int DRNeedUpdateCnt { get; private set; }

        /// <summary>
        /// 计划列表更新时间
        /// </summary>
        public DateTime PlanUpdateTime { get; private set; }
        /// <summary>
        /// 总寄存器数量
        /// </summary>
        public int DRCnt { get; private set; }

        /// <summary>
        /// DRmap分配到 每个寄存器区
        /// </summary>
        public void Build()
        {
            DRCnt = DRmap.Count;

            //TODO, 应该提前做！！！！
            foreach (var areaManager in mAreaManager)
                areaManager.BuildArray(DRmap);
        }
        /// <summary>
        /// 创建寄存器更新计划
        /// </summary>
        public void PlanMake()
        {
            foreach (DataToRegs dr in DRmap)
            {
                dr.isNeedUpdate = false;
            }

            foreach (KeyValuePair<object, List<DataToRegs>> kv in plans)
            {
                foreach (DataToRegs dr in kv.Value)
                {
                    dr.isNeedUpdate = true;
                }
            }

            DRNeedUpdateCnt = DRmap.Count((dr) => dr.isNeedUpdate);
            PlanUpdateTime = DateTime.Now;
            foreach (var areaManager in mAreaManager)
                areaManager.MakePlan();
        }
        #endregion
    }


    /// <summary>
    /// PLC 地址空间， 线圈 or 寄存器
    /// </summary>
    public enum PLCAddressArea
    {
        /// <summary>
        /// 线圈
        /// </summary>
        Coil,
        /// <summary>
        /// 寄存器
        /// </summary>
        Register
    }
    /// <summary>
    /// 通知命名数据改变是的参数
    /// </summary>
    public class NameDataChangedEventArgs : EventArgs
    {

        /// <summary>
        /// 初始化 NameDataChangedEventArgs 类的新实例。
        /// </summary>
        /// <param name="owner">数据类</param>
        /// <param name="propertyName">已更改的数据名。</param>
        public NameDataChangedEventArgs(object owner, string propertyName)
        {
            this.owner = owner;
            this.propertyName = propertyName;
        }

        object owner;
        string propertyName;

        /// <summary>
        ///  获取数据所属类
        /// </summary>
        public object Owener
        {
            get
            {
                return owner;
            }
        }

        /// <summary>
        /// 获取已更改的数据名名。
        /// </summary>
        public string PropertyName
        {
            get
            {
                return propertyName;
            }
        }
    }

    /// <summary>
    /// NameData 改变事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public delegate void NameDataChangedEventHandler(object sender, DataToRegs e);// NameDataChangedEventArgs e);

}
