using HTCoreServiceApp.Common;
using HTCoreServiceApp.DataHandle;
using HTCoreServiceApp.DriverInterface;
using HTCoreServiceApp.Models;
using Microsoft.Identity.Client;
using Newtonsoft.Json.Linq;
using Opc.Ua;
using OpcUaClient.OpcUaHelper;
using Pipelines.Sockets.Unofficial.Arenas;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Numerics;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using static Microsoft.EntityFrameworkCore.DbLoggerCategory.Database;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace HTCoreServiceApp.Communicate.OpcUaClient
{
	public class OpcUaClientExample : IDriverEntity
	{

		public string DeviceId { get; set; }

		private string DriverName { get; set; }
        private List<MetaSystem> Systems { get; set; }
        public bool IsConnected { get; set; }

		public int Timeout { get; set; }

		public bool isScan { get; set; }

		public System.Timers.Timer ScanTimer { get; set; }

		System.Timers.Timer timerCheck = new System.Timers.Timer();

		public string Address = "127.0.0.1";
		private int Port = 0;
		private string ServerUri = "";

		private string PLCType = "";

		public OpcUaClientHelper Client;
		private byte[] Buffer = new byte[65536];
		private byte[] DB_A = new byte[1024];
		private byte[] DB_B = new byte[1024];
		private byte[] DB_C = new byte[1024];
		private readonly object _lock = new();


		private List<MetaGroup> MetaGroups;
		private List<MetaTag> MetaTags;
		//List<Argument> argument, List<MetaGroup> metaGroups,List<MetaTag> metaTags
		public OpcUaClientExample(TagGroup tagGroup)
		{
			Client = new OpcUaClientHelper();

			Address = tagGroup.Arguments.Where(x => x.PropertyName == "ServerName").First().PropertyValue;
			Port = int.Parse(tagGroup.Arguments.Where(x => x.PropertyName == "Port").First().PropertyValue);
			ServerUri = tagGroup.Arguments.Where(x => x.PropertyName == "ServerUri").First().PropertyValue;
			PLCType = tagGroup.Arguments.Where(x => x.PropertyName == "PLCType").First().PropertyValue;
			Timeout = int.Parse(tagGroup.Arguments.Where(x => x.PropertyName == "TimeOut").First().PropertyValue);
			MetaGroups = tagGroup.Groups;
			MetaTags = tagGroup.Tags;//只有一组
			DriverName = tagGroup.DriverName;
            Systems = tagGroup.Systems;

            //Client.SetConnectionType(0x3);
            //Client.Timeout = Timeout;
            //Client.RecvTimeout = 30;



            //Connect();
            timerCheck.Interval = 2000;
			StartConnectCheck();

			StartScan();
		}

		public void StartScan()
		{
			var groups = MetaGroups.GroupBy(x => new { x.GroupId });

			var scanTagsParas = new List<ScanTagsParaOpc>();

			foreach (var metaGroup in groups)
			{
				ScanTagsParaOpc scanTagsPara = new ScanTagsParaOpc();
				scanTagsPara.GroupName = MetaGroups.Where(x => x.GroupId == metaGroup.Key.GroupId).ToList().First().GroupName;
				scanTagsPara.UpdateRate = MetaGroups.Where(x => x.GroupId == metaGroup.Key.GroupId).ToList().First().UpdateRate;
				scanTagsPara.IsConsecutive = (bool)MetaGroups.Where(x => x.GroupId == metaGroup.Key.GroupId).ToList().First().IsActive;
				scanTagsPara.IsActive = MetaGroups.Where(x => x.GroupId == metaGroup.Key.GroupId).ToList().First().IsActive;
				scanTagsPara.ScanTags = MetaTags.Where(x => x.GroupId == metaGroup.Key.GroupId).ToList();
                if (scanTagsPara.ScanTags.Count < 1)
                {
                    continue;
                }
                short driverID = (short)MetaGroups.Where(x => x.GroupId == metaGroup.Key.GroupId).ToList().First().DriverId;
				scanTagsPara.DriverName = DriverName;
                var groupSysId = MetaGroups.Where(x => x.GroupId == metaGroup.Key.GroupId).ToList().First().SystemId;
                scanTagsPara.SystemName = Systems.Where(x => x.SystemId == groupSysId).ToList().First().SystemName;
                //scanTagsPara.AddressArg = PLCHelper.GetAddressArg_Modbus_Word(scanTagsPara.ScanTags);
                scanTagsPara.Nodes = PLCHelper.GetAddressArg_OpcUaClient_Arraay(scanTagsPara.ScanTags);
				scanTagsParas.Add(scanTagsPara);
			}
			//if 添加连续或单变量读取标记
			//if (scanTagsPara.IsConsecutive == true)
			//{
			//    if (scanTagsPara.IsActive == true && scanTagsPara.ScanTags.Count > 0)
			//        Task.Run(() =>
			//        {
			//            ScanConsecutive(scanTagsPara);
			//        }) ;
			//}
			//else
			//{
			//    if (scanTagsPara.IsActive == true && scanTagsPara.ScanTags.Count > 0)
			//        Task.Run(() =>
			//        {
			//            Scan(scanTagsPara);
			//        });

			//}
			ScanConsecutive(scanTagsParas);
		}

		/// <summary>
		/// 启动连续读取
		/// </summary>
		/// <param name="scanTagsParas"></param>
		public void ScanConsecutive(List<ScanTagsParaOpc> scanTagsParas)
		{
			ScanTimerOpc timerScan = new ScanTimerOpc();
			if (scanTagsParas[0].UpdateRate > 0)
			{
				timerScan.Interval = (double)scanTagsParas[0].UpdateRate;
			}
			else
			{
				timerScan.Interval = 1000;
			}
			timerScan.ScanTagsParas = scanTagsParas;

			//timerScan.AddressArg = PLCHelper.GetAddressArg(scanTagsParas.ScanTags);

			timerScan.Elapsed += new System.Timers.ElapsedEventHandler(TimerScanConsecutive_Elapsed);
			timerScan.Enabled = true;
		}
		/// <summary>
		/// 连续读取timer事件
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void TimerScanConsecutive_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
		{
			ScanTimerOpc timerScan = (ScanTimerOpc)sender;
			List<ScanTagsParaOpc> scanTagsParas = timerScan.ScanTagsParas;
			//AddressArg addressArg = timerScan.AddressArg;
			foreach (var scanTagsPara in scanTagsParas)
			{
				if (scanTagsPara.IsActive == false)
					break;
				if (Client.Connected)
				{

					var addressArg = scanTagsPara.AddressArg;

					//object _lock = new();

					Task task = Task.Factory.StartNew(() =>
					{
						lock (_lock)
						{
							try
							{
								var tcollstart = DateTime.Now;

								string Result = "";
								NodeId[] nodes = scanTagsPara.Nodes;
								List<DataValue> values = new List<DataValue>();
								Result = ReadServerNodesValue(nodes, ref values);

								var tcollend = DateTime.Now;
								var timecolldiff = tcollend - tcollstart;
								Console.WriteLine($"OpcUaClient读取耗时：{timecolldiff},设备:{DriverName}变量组:{scanTagsPara.GroupName}");

								var valueArray = new string[values.Count];
								int ii = 0;
								foreach (var item in values)
								{
									//补全读取状态码的确认
									//item.StatusCode

									/*
                                     * 一
                                     * 1、编写opcclient连接所需求信息至采集服务，应设至数据库中的设备参数表。
                                     * 2、构建opcclient采集线程实例，实现线程的连接、检测、读取和写入接口功能。
                                     * 二
                                     * 1、组合opc节点数组，读取opc数据，根据变量标签索引将读取结果数组分离为标签数据。
                                     * 2、
                                     * 三
                                     * 1、配置opcclient客户端读取到的数据数组与sqlserver存储语句组合，调用采集服务的存储和实时数据接口，存储至sqlserver历史数据库和实时数据服务。
                                     */
									valueArray[ii] = item.Value.ToString();
									ii++;
								}
								if (Result == "")
								{
									var t1 = DateTime.Now.Millisecond;
									Console.WriteLine(string.Join(",", valueArray) + "OpcUaClient读取完成：" + " 字节数：" + values.Count);
									//Task<int> saveResult = DataExtract.DataExtractCAsync(scanTagsPara.ScanTags, scanTagsPara.GroupName, scanTagsPara.DriverName, buffer, addressArg);
									DataExtractOpc.DataExtractCAsync(scanTagsPara.ScanTags, scanTagsPara.SystemName, scanTagsPara.GroupName, scanTagsPara.DriverName, scanTagsPara.Nodes, values);
									var t2 = DateTime.Now.Millisecond;
									var timediff = t2 - t1;
									Console.WriteLine($"OpcUaClient缓存和储存耗时：{timediff}");
									{
										//if (saveResult.Result == 0)
										//{
										//    //logger
										//}
										//else
										//{
										//    //logger
										//}

									}
								}
								else if (Result != "")
								{
									Console.WriteLine("OpcUaClient读取失败：" + " ERROR：" + Result.ToString());
									LogHelper.LogWrite("OpcUaClient读取失败：" + " ERROR：" + Result.ToString());
									//Close();
									//Thread.Sleep(50);
									//Connect();
								}

							}
							catch (Exception ex)
							{
								LogHelper.LogWrite($"{DriverName}读取失败，MB错误信息{ex}");
							}
						}

					});
				}
				else
				{
					LogHelper.LogWrite($"{DriverName}读取失败，MB未连接");
					//Connect();
				}
				Thread.Sleep(50);
			}
		}
		/*
        /// <summary>
        /// 启动单个变量读取
        /// </summary>
        /// <param name="scanTagsPara"></param>
        public void Scan(ScanTagsPara scanTagsPara)
        {
            ScanTimer timerScan = new ScanTimer();
            if (scanTagsPara.UpdateRate > 0)
            {
                timerScan.Interval = (double)scanTagsPara.UpdateRate;
            }
            else
            {
                timerScan.Interval = 1000;
            }
            timerScan.ScanTagsPara = scanTagsPara;
            timerScan.Elapsed += new System.Timers.ElapsedEventHandler(TimerScan_Elapsed);
            timerScan.Enabled = true;
        }

        /// <summary>
        /// 单个变量读取timer事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TimerScan_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
        {
            ScanTimer timerScan = (ScanTimer)sender;
            ScanTagsPara scanTagsPara = timerScan.ScanTagsPara;
            AddressArg addressArg = timerScan.AddressArg;
            if (IsConnected = true)
            {
                object _lock = new();

                Task task = Task.Run(() =>
                {
                    lock (_lock)
                    {
                        try
                        {
                            //CodingTools.StartTime = DateTime.Now.Millisecond;//de
                            //scanTagsPara.ScanTags.Min(x => x.Address);
                            //scanTagsPara.ScanTags.Min(x => x.Address);提高速度应放在timer事件前，测试后更改
                            int startAdress = addressArg.Start;
                            int Area = 132;
                            int DBNumber = 0;
                            if (scanTagsPara.GroupName.Contains("DB"))
                            {
                                DBNumber = int.Parse(Regex.Replace(scanTagsPara.GroupName, "[a-z]", "", RegexOptions.IgnoreCase));
                            }
                            else
                            {
                                DBNumber = int.Parse(scanTagsPara.GroupName);
                            }
                            int amount = addressArg.Length;
                            int WordLen = 2;
                            byte[] buffer = new byte[amount];
                            int Result = ReadArea(Area, DBNumber, startAdress, amount, WordLen, ref buffer);
                            //CodingTools.EndTime = DateTime.Now.Millisecond;//de
                            //var timess = (CodingTools.EndTime - CodingTools.StartTime).ToString();
                            //Console.WriteLine($"读取耗时：{timess}");
                            if (Result == 0)
                            {
                                //Console.WriteLine(string.Join(",", buffer) + "读取完成：DB" + DBNumber.ToString() + ",字节数：" + amount.ToString());
                                int saveResult = DataExtract.DataExtractConsecutive(timerScan.ScanTagsPara.ScanTags, scanTagsPara.GroupName, timerScan.ScanTagsPara.DriverName, buffer, addressArg);
                                {
                                    if (saveResult == 0)
                                    {
                                        //logger
                                    }
                                    else
                                    {
                                        //logger
                                    }

                                }
                            }
                            else if (Result != 0)
                            {
                                Console.WriteLine("读取失败：DB" + DBNumber.ToString() + ",ERRORCODE：" + Result.ToString());
                                Close();
                                Thread.Sleep(200);
                                Connect();
                            }

                        }
                        catch (Exception ex)
                        {

                        }
                    }

                });
            }
            else
            {
                Connect();
            }
        }
        */
		public string ReadServerNodesValue(NodeId[] nodes, ref List<DataValue> values)
		{
			object _lock = new();
			var Result = "";
			lock (_lock)
			{
				try
				{
					int reval = 0;
					int SizeRead = 0;

					//buffer = new byte[65536];
					values = Client.ReadNodes(nodes);
					return Result;
				}
				catch (Exception ex)
				{
					return Result = ex.ToString();
				}
			}
		}

		public void StartConnectCheck()
		{

			timerCheck.Elapsed += new System.Timers.ElapsedEventHandler(TimerCheckConnect_Elapsed);
			timerCheck.Enabled = true;
		}

		private void TimerCheckConnect_Elapsed(object? sender, System.Timers.ElapsedEventArgs e)
		{
			timerCheck.Enabled = false;
			if (Client.Connected)
			{
				timerCheck.Enabled = true;
				return;
			}
			else
			{
				Connect();
			}
		}


		public bool Connect()
		{
			var Result = "";
			try
			{

				Client.ConnectServer(serverUrl: ServerUri).Wait(Timeout);
			}
			catch (Exception ex)
			{
				Result = ex.ToString();
			}
			if (Result == "" && Client.Connected)
			{
				IsConnected = true;
				//log
				LogHelper.LogWrite($"OpcUaClient连接成功，返回值:{Result}");
				timerCheck.Enabled = true;
				return true;
			}
			else
			{
				IsConnected = false;
				//log
				LogHelper.LogWrite($"OpcUaClient连接失败，返回值:{Result}");
				timerCheck.Enabled = true;
				return false;
			}

		}

		public bool Close()
		{
			var Result = "";
			try
			{
				Client.Disconnect();
			}
			catch (Exception ex)
			{
				Result = ex.ToString();
			}
			if (Result == "")
			{
				IsConnected = false;
				//log
				LogHelper.LogWrite($"OpcUaClient关闭连接成功，返回值:{Result}");
				return true;
			}
			else
			{
				IsConnected = false;
				LogHelper.LogWrite($"OpcUaClient关闭连接失败，返回值:{Result}");
				//log
				return false;
			}
		}

		public void Dispose()
		{

			try
			{
				Client.Disconnect();
				//log
			}
			catch (Exception ex)
			{
				//log
			}

		}

		public int Write(string writePara)
		{
			throw new NotImplementedException();
		}
		/*
        /// <summary>
        /// 写入方法
        /// </summary>
        /// <param name="writePara"></param>
        /// <returns></returns>
        public int Write(string writePara)
        {
           int reCode = 0;
           JObject valueJson = JObject.Parse(writePara);
           var srcValue = valueJson[propertyName: "Value"].ToString();
           var writeAddress = valueJson[propertyName: "WriteAddress"].ToString();
           var dataType =short.Parse(valueJson[propertyName: "DataType"].ToString());
           var groupName = valueJson[propertyName: "GroupName"].ToString();

           var array = writeAddress.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
           var DBNum_string = array[0].ToString();
           var DBNum = int.Parse(Regex.Replace(DBNum_string, "[a-z]", "", RegexOptions.IgnoreCase));

           var address = array[1].ToString();
           string startaddress = Regex.Replace(address, "[a-z]", "", RegexOptions.IgnoreCase);
           int address_dem = 0;
           int address_int = 0;
           if (startaddress.Contains(".") && startaddress.Length > 2)
           {
               try
               {
                   string[] address_arr = startaddress.Split('.');
                   address_int = int.Parse(address_arr[0]);
                   address_dem = int.Parse(address_arr[1]);
               }
               catch (Exception exp)
               {
                   //logger
               }
           }
           else
           {
               address_int = int.Parse(startaddress);
           }

           byte[] revbuffer_w;// = new byte[maxreg + 2];
           decimal value = decimal.Parse(srcValue);
           lock (_lock)
           {
               if (dataType == (short)DataType.BOOL)
               {
                   var boolbyte = new byte[1];
                   int rebooltempread = ReadHoldingRegisters(132, DBNum, address_int, 1, 2, ref boolbyte);
                   Thread.Sleep(50);
                   Console.WriteLine(rebooltempread);
                   var temval = (float)value;
                   bool bl = temval > 0.0f ? true : false;
                   //var rtbyte = revbuffer_w[address_int];
                   //var rtbuff = Sharp7.S7. rtbyte
                   Sharp7.S7.SetBitAt(ref boolbyte, 0, address_dem, bl);
                   reCode = Client.WriteArea(132, DBNum, address_int, 1, 2, boolbyte);
               }
               else if (dataType == (short)DataType.FLOAT)
               {
                   revbuffer_w = new byte[4];
                   Sharp7.S7.SetRealAt(revbuffer_w, 0, (float)value);
                   reCode = Client.WriteArea(132, DBNum, address_int, 4, 2, revbuffer_w);
               }
               else if (dataType == (short)DataType.WORD)
               {
                   revbuffer_w = new byte[2];
                   Sharp7.S7.SetWordAt(revbuffer_w, 0, (ushort)value);
                   reCode = Client.WriteArea(132, DBNum, address_int, 2, 2, revbuffer_w);
               }
               else if (dataType == (short)DataType.BYTE)
               {
                   revbuffer_w = new byte[1];
                   Sharp7.S7.SetByteAt(revbuffer_w, 0, (byte)value);
                   reCode = Client.WriteArea(132, DBNum, address_int, 1, 2, revbuffer_w);
               }
               else
               {

               }
           }
           Console.WriteLine(writePara);
           return reCode;
        }
        */
	}
}
