//******************************************************************************************************
//
//文件名（FileName）：                      Pro_OPCUA.cs
//
//功能描述（Description）：                 OPCUA通讯协议库，包含OPCUA Client 和 Server
//
//数据表（Tables）：                        Nothing
//
//作者（Author）：                          wudehang
//
//日期（Create Date）：                     2021-03-09
//
//CLR版本（CLR Version）：                  net Core 5.0
//
//版本（Version）：                         1.0.0
//
//公司（Company）：                         南京埃斯顿自动化股份有限公司
//
//******************************************************************************************************
using Newtonsoft.Json.Linq;
using Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using Treasure.DataModel;

namespace Treasure.通讯协议
{
    /// <summary>
    /// OPCUA 通讯协议
    /// </summary>
    public partial class Pro_OPCUA : ProtocolBase
    {
        /// <summary>
        /// 应用程序对象
        /// </summary>
        private ApplicationInstance m_application = new ApplicationInstance();
        /// <summary>
        /// 连接地址
        /// </summary>
        private string m_ConnectStr { get; set; }
        /// <summary>
        /// OPC连接状态
        /// </summary>
        private bool isCconnect { get; set; }
        /// <summary>
        /// 构造函数
        /// </summary>
        public Pro_OPCUA(string ConnectStr)
        {
            m_ConnectStr = ConnectStr;
            m_application.ApplicationType = ApplicationType.Client;
            m_application.ConfigSectionName = "Treasure.OPCUAClient";
            try
            {
                Console.WriteLine("加载客户端配置");
                // 加载opcua客户端配置
                m_application.LoadApplicationConfiguration(false).Wait();
                m_configuration = m_application.ApplicationConfiguration;
                // 核对客户端证书
                m_application.CheckApplicationInstanceCertificate(true, 0).Wait();
                //建立会话
                Connect();
            }
            catch (Exception ex)
            {
                throw new Exception("OPUAConfiguration:" + ex.ToString());
            }
        }
        /// <summary>
        /// 通讯协议参数初始化
        /// </summary>
        public override void ProtocolInit(string IP, short Port, string ConnectStr)
        {

        }
        /// <summary>
        /// 开始采集
        /// </summary>
        public override void ReadItems(List<ItemBase> itemBases)
        {
            int j = 20;
            for (int i = 0; i < itemBases.Count; i += 20)
            {

                //读取数据
                ReadNodes(itemBases.Take(j).Skip(i).ToList());
                j += 20;
            }
        }
        public void readAndWrite()
        {
            try
            {
                if (isCconnect)
                {
                    //Console.WriteLine("数据节点转换");
                    //节点格式转换
                    ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
                    nodesToRead.Add(new ReadValueId()
                    {
                        NodeId = new NodeId("ns=2;s=WRITEVAR"),
                        AttributeId = Attributes.Value
                    });
                    DataValueCollection resultsValues = null;
                    DiagnosticInfoCollection diagnosticInfos = null;
                    m_session.Read(
                        null,
                        0,
                        TimestampsToReturn.Both,
                        nodesToRead,
                        out resultsValues,
                        out diagnosticInfos);
                    //验证结果
                    ClientBase.ValidateResponse(resultsValues, nodesToRead);

                    WriteValueCollection nodesToWrite = new WriteValueCollection();
                    byte[] itt = (resultsValues[0].Value as ExtensionObject[])[0].Body as byte[];
                    ExtensionObject[] AA = new ExtensionObject[100];
                    for (int i = 0; i < AA.Length; i++)
                    {
                        AA[i] = new ExtensionObject()
                        {
                            Body = itt
                        };
                    }
                    var aa = resultsValues[0].Value;
                    nodesToWrite.Add(new WriteValue()
                    {
                        NodeId = new NodeId("ns=2;s=WRITEVAR"),
                        AttributeId = Attributes.Value,
                        Value = new DataValue()
                        {
                            Value = AA
                            // resultsValues[0].Value

                        }
                    });
                    StatusCodeCollection resultsValues1 = null;
                    DiagnosticInfoCollection diagnosticInfos1 = null;

                    //Console.WriteLine("数据读取开始");
                    // Call Read Service
                    m_session.Write(
                        null,
                        nodesToWrite,
                        out resultsValues1,
                        out diagnosticInfos1);
                    //验证结果
                    ClientBase.ValidateResponse(resultsValues1, nodesToWrite);
                }
                else
                {
                    Connect();
                }

            }
            catch (Exception ex)
            {
                throw new Exception("OPUAReadNodes:" + ex.Message + ex.StackTrace);
            }
        }
        /// <summary>
        /// 开始采集
        /// </summary>
        public override void ReadItem(ItemBase itemBases)
        {
            //读取数据
            //itemBases.ItemValue = ReadNode(itemBases.ItemAdress).Value.ToString();
        }
        /// <summary>
        /// 连接初始化
        /// </summary>
        public override async void Connect()
        {
            try
            {
                //待修改 sessionTimeout 设置长一些
                await ConnectServer(m_ConnectStr, false);
            }
            catch (Exception)
            {

                throw;
            }

        }
        /// <summary>
        /// 连接校验（在每次读取时，进行连接校验），连接校验失败，则进行一次重连
        /// </summary>
        public override void CheckConnect()
        {

        }
        /// <summary>
        /// 连接断开
        /// </summary>
        public override void Close()
        {
            if (m_session != null)
            {
                m_session.Close();
                m_session.Dispose();
                m_session = null;
            }
        }
        /// <summary>
        /// OPCUA单个Tag读取
        /// </summary>
        /// <param name="Tag"></param>
        /// <returns></returns>
        public Node ReadNode(string Tag)
        {
            try
            {
                return m_session.ReadNode(new NodeId(Tag));
            }
            catch (Exception ex)
            {
                throw new Exception("OPUAReadNode:" + ex.Message + ex.StackTrace);
            }
        }
        Subscription m_subscription;
        /// <summary>
        /// 订阅
        /// </summary>
        /// <param name="itemBases"></param>
        public void subscription(List<ItemBase> itemBases)
        {
            if (m_session == null || m_session.Connected == false)
            {
                Console.WriteLine("Session not connected!");
                return;
            }

            try
            {
                // Create a subscription for receiving data change notifications

                // Define Subscription parameters
                m_subscription = new Subscription(m_session.DefaultSubscription);

                m_subscription.DisplayName = "Console ReferenceClient Subscription";
                m_subscription.PublishingEnabled = true;
                m_subscription.PublishingInterval = 200;
                for (int i = 0; i < itemBases.Count; i++)
                {
                    // Create MonitoredItems for data changes

                    MonitoredItem intMonitoredItem = new MonitoredItem(m_subscription.DefaultItem);
                    // Int32 Node - Objects\CTT\Scalar\Simulation\Int32
                    intMonitoredItem.StartNodeId = new NodeId(itemBases[i].ItemAdress);
                    intMonitoredItem.AttributeId = Attributes.Value;
                    intMonitoredItem.DisplayName = itemBases[i].ItemID + itemBases[i].ItemName;
                    intMonitoredItem.SamplingInterval = 200;
                    intMonitoredItem.Notification += OnMonitoredItemNotification;

                    m_subscription.AddItem(intMonitoredItem);
                }
                var aa = m_session.AddSubscription(m_subscription);
                // Create the subscription on Server side
                m_subscription.Create();
            }
            catch (Exception ex)
            {
                Console.WriteLine("Subscribe error: {0}", ex.Message);
            }
        }
        /// <summary>
        /// 停止订阅
        /// </summary>
        public void EndSubscription()
        {

        }
        /// <summary>
        /// Handle DataChange notifications from Server
        /// </summary>
        private void OnMonitoredItemNotification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
        {
            try
            {
                // Log MonitoredItem Notification event
                MonitoredItemNotification notification = e.NotificationValue as MonitoredItemNotification;
                Console.WriteLine("Notification Received for Variable \"{0}\" and Value = {1}.", monitoredItem.DisplayName, notification.Value);
            }
            catch (Exception ex)
            {
                Console.WriteLine("OnMonitoredItemNotification error: {0}", ex.Message);
            }
        }
        public string structItem(string ItemAdress)
        {
            DataValue item = m_session.ReadValue(new NodeId(ItemAdress));
            string aaa = ReadStruct(ItemAdress, item);
            return aaa;
        }
        /// <summary>
        /// 读取结构体数据
        /// </summary>
        public string ReadStruct(string ItemAdress, DataValue value)
        {
            NodeId node = new NodeId(ItemAdress);
            //获取Node 信息 验证Node的数据类型
            VariableNode nodeInfo = m_session.ReadNode(node) as VariableNode;
            string aa1 = Newtonsoft.Json.JsonConvert.SerializeObject(nodeInfo);
            //DataValue value = m_session.ReadValue(node);
            var datatypeNode = (m_session.ReadNode(nodeInfo.DataType)) as DataTypeNode;
            var typeDefine = datatypeNode.DataTypeDefinition.Body as StructureDefinition;
            //var datatypeNode1 = (m_session.ReadNode(new NodeId("ns=2;i=3073"))) as DataTypeNode;
            string aa = Newtonsoft.Json.JsonConvert.SerializeObject(datatypeNode);
            //string aaa = Newtonsoft.Json.JsonConvert.SerializeObject(datatypeNode1);
            int index = 0;
            if (value.Value is ExtensionObject[])//数组
            {
                JArray res = new JArray();
                foreach (var item in value.Value as ExtensionObject[])
                {

                    res.Add(GetJsonFromExtensionObject(ItemAdress, item, typeDefine, ref index));
                }
                return res.ToString();
            }
            else //非数组
            {
                return GetJsonFromExtensionObject(ItemAdress, value.Value as ExtensionObject, typeDefine, ref index).ToString();
            }

        }
        private JObject GetJsonFromExtensionObject(string address, ExtensionObject value, StructureDefinition structure, ref int index)
        {
            JObject res = new JObject();
            var data = value.Body as byte[];
            foreach (var field in structure.Fields)
            {
                if (field.ValueRank == 1)
                {
                    //是数组，需要读取数组长度
                    string fieldNodeId = $"{address}.{field.Name}"; // 例如："StationStatus1.AlarmA1"
                    VariableNode fieldNode = m_session.ReadNode(new NodeId(fieldNodeId)) as VariableNode;
                    if (fieldNode.ArrayDimensions != null && fieldNode.ArrayDimensions.Count > 0)
                    {
                        int count = (int)BitConverter.ToUInt32(data.Skip(index).Take(4).ToArray(), 0);
                        index += 4;
                        for (int i = 0; i < count; i++)
                        {
                            res[$"{field.Name}[{i}]"] = BitConverter.ToBoolean(data.Skip(index + i).Take(1).ToArray(), 0);
                        }
                        index += count;
                    }

                }
                else if (field.DataType.NamespaceIndex == 2)
                {
                    //结构体嵌套
                    var count = BitConverter.ToUInt32(data.Skip(index).Take(4).ToArray(), 0);
                    index += 4;
                    var datatypeNode1 = (m_session.ReadNode(field.DataType)) as DataTypeNode;
                    var typeDefine = datatypeNode1.DataTypeDefinition.Body as StructureDefinition;
                    for (int i = 0; i < count; i++)
                    {
                        res[field.Name + i] = GetJsonFromExtensionObject(address, value, typeDefine, ref index);
                    }
                }
                else
                {
                    string name = field.Name;
                    if (field.DataType == DataTypeIds.String)
                    {
                        int length = (int)BitConverter.ToUInt32(data.Skip(index).Take(4).ToArray(), 0);
                        index += 4;
                        string re = Encoding.Default.GetString(data.Skip(index).Take(length).ToArray());
                        res[name] = re;
                        index += length;
                    }
                    if (field.DataType == DataTypeIds.UInt32)
                    {
                        UInt32 re = BitConverter.ToUInt32(data.Skip(index).Take(4).ToArray(), 0);
                        index += 4;
                        res[name] = re;
                    }
                    if (field.DataType == DataTypeIds.Float)
                    {
                        float re = BitConverter.ToSingle(data.Skip(index).Take(4).ToArray(), 0);
                        index += 4;
                        res[name] = re;
                    }
                    if (field.DataType == DataTypeIds.Boolean)
                    {
                        bool re = BitConverter.ToBoolean(data.Skip(index).Take(1).ToArray());
                        res[name] = re;
                        index += 1;
                    }
                    if (field.DataType == DataTypeIds.Double)
                    {
                        double re = BitConverter.ToDouble(data.Skip(index).Take(8).ToArray());
                        res[name] = re;
                        index += 8;
                    }
                }


            }
            return res;
        }

        /// <summary>
        /// OPCUA批量Tag读取
        /// </summary>
        /// <param name="itemBases">需要读取的节点</param>
        /// <returns></returns>
        public void ReadNodes(List<ItemBase> itemBases)
        {
            try
            {
                if (isCconnect)
                {
                    //Console.WriteLine("数据节点转换");
                    //节点格式转换
                    ReadValueIdCollection nodesToRead = new ReadValueIdCollection();
                    for (int i = 0; i < itemBases.Count; i++)
                    {
                        nodesToRead.Add(new ReadValueId()
                        {
                            NodeId = new NodeId(itemBases[i].ItemAdress),
                            AttributeId = Attributes.Value
                        });
                    }
                    NodeId ss = itemBases[0].ItemAdress;
                    //EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(QTI_Data));
                    // Read the node attributes
                    DataValueCollection resultsValues = null;
                    DiagnosticInfoCollection diagnosticInfos = null;
                    //Console.WriteLine("数据读取开始");
                    // Call Read Service
                    m_session.Read(
                        null,
                        0,
                        TimestampsToReturn.Both,
                        nodesToRead,
                        out resultsValues,
                        out diagnosticInfos);
                    //验证结果
                    ClientBase.ValidateResponse(resultsValues, nodesToRead);
                    for (int i = 0; i < resultsValues.Count; i++)
                    {
                        if (resultsValues[i].Value == null)
                        {
                            itemBases[i].ItemValue = "";
                        }
                        else
                        {
                            //若是字符串数据的话，则进行直接赋值
                            if (resultsValues[i].WrappedValue.TypeInfo.BuiltInType == BuiltInType.String)
                            {
                                itemBases[i].ItemValue = resultsValues[i].Value.ToString();
                            }
                            else
                            {
                                itemBases[i].ItemValue = Newtonsoft.Json.JsonConvert.SerializeObject(resultsValues[i].Value);
                            }
                        }
                        //在这里进行结构体类型数据处理
                        if (itemBases[i].ItemType == DataType.Struct)
                        {
                            // itemBases[i].ItemValue = ReadStruct(itemBases[i].ItemAdress, resultsValues[i]);
                        }
                    }
                }
                else
                {
                    Connect();
                }

            }
            catch (Exception ex)
            {
                throw new Exception("OPUAReadNodes:" + ex.Message + ex.StackTrace);
            }
        }
        /// <summary>
        /// OPCUATag写入
        /// </summary>
        /// <param name="adress">需要写入节点的地址</param>
        /// <param name="value">需要写入节点的值</param>
        /// <returns></returns>
        public bool WriteNode(WriteValueCollection value)
        {
            try
            {
                if (isCconnect)
                {
                    StatusCodeCollection resultsValues = null;
                    DiagnosticInfoCollection diagnosticInfos = null;

                    //Console.WriteLine("数据读取开始");
                    // Call Read Service
                    var result = m_session.Write(
                          null,
                          value,
                          out resultsValues,
                          out diagnosticInfos);
                    //写入返回值为bad，则写入失败
                    if (StatusCode.IsBad(result.ServiceResult))
                    {
                        return false;
                    }
                    //验证结果
                    ClientBase.ValidateResponse(resultsValues, value);
                    return true;
                }
                return false;
            }
            catch (Exception ex)
            {
                throw new Exception("OPUAReadNodes:" + ex.Message + ex.StackTrace);
            }
        }
        /// <summary>
        /// 从对象中获取对象所有的值，并转化为byte[]
        /// </summary>
        /// <param name="value">需要写入节点的值组成的byte数组列表</param>
        /// <returns></returns>
        public ExtensionObject GetValueByteFromObj(WriteVar value)
        {
            List<byte> result = new List<byte>();
            //前4位为字符串数据长度
            var length = BitConverter.GetBytes(value.Name.Length);
            result.AddRange(length);
            //数据项名称
            var Name = Encoding.Default.GetBytes(value.Name);
            result.AddRange(Name);
            //数据类型
            var DataType = BitConverter.GetBytes(value.DataType);
            result.AddRange(DataType);
            //字符串数据转换
            if (value.StringValue.Length == 0)
            {
                result.AddRange(new byte[4] { 0, 0, 0, 0 });
            }
            else
            {
                //前4位为字符串数据长度
                length = BitConverter.GetBytes(value.StringValue.Length);
                result.AddRange(length);
                //字符串数据
                var StringValue = Encoding.Default.GetBytes(value.StringValue);
                result.AddRange(StringValue);
            }
            //整数数据
            var IntValue = BitConverter.GetBytes(value.IntValue);
            result.AddRange(IntValue);
            //浮点数数据
            var FloatValue = BitConverter.GetBytes(value.FloatValue);
            result.AddRange(FloatValue);

            return new ExtensionObject { Body = result.ToArray() };
        }
        /// <summary>
        /// OPCUATag写入
        /// </summary>
        /// <param name="value">需要写入节点的值组成的byte数组列表</param>
        /// <returns></returns>
        public void WriteVarStruct(ExtensionObject[] value)
        {
            WriteValueCollection nodesToWrite = new WriteValueCollection();
            nodesToWrite.Add(new WriteValue()
            {
                NodeId = new NodeId("ns=2;s=WRITEVAR"),
                AttributeId = Attributes.Value,
                Value = new DataValue()
                {
                    Value = value
                }
            });
            WriteNode(nodesToWrite);
        }

        ///// <summary>
        ///// OPCUATag写入
        ///// </summary>
        ///// <param name="value">需要写入节点的值组成的byte数组列表</param>
        ///// <returns></returns>
        //public void WriteVarStruct(WriteVar writeVar)
        //{
        //    List<byte> result = new List<byte>();
        //    WriteValueCollection nodesToWrite = new WriteValueCollection();
        //    nodesToWrite.Add(new WriteValue()
        //    {
        //        NodeId = new NodeId("ns=2;s=WRITEVAR"),
        //        AttributeId = Attributes.Value,
        //        Value = new DataValue()
        //        {
        //            Value = value
        //        }
        //    });
        //    WriteNode(nodesToWrite);
        //}
    }
    /// <summary>
    /// writevar对象
    /// </summary>
    public class WriteVar
    {
        /// <summary>
        /// 数组索引
        /// </summary>
        public int index = 0;
        /// <summary>
        /// 数据项名称
        /// </summary>
        public string Name = "";
        /// <summary>
        /// 数据类型
        /// </summary>
        public int DataType = 0;
        /// <summary>
        /// 字符串值
        /// </summary>
        public string StringValue = "";
        /// <summary>
        /// 整数值
        /// </summary>
        public int IntValue = 0;
        /// <summary>
        /// 浮点数值
        /// </summary>
        public float FloatValue = 0.0F;
    }
}
