using NModbus;
using NModbus.Data;
using NModbus.Device;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Net.NetworkInformation;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;

namespace DataCollection
{
    internal class Service
    {
        NModbus.IModbusSlave _Slave;
        ServiceConfig _ServiceConfig;
        NIDAQmx.DAQmx _DAQmx = new NIDAQmx.DAQmx();
        CancellationTokenSource tokenSource = new CancellationTokenSource();
        ulong _SampleRate = 2000;
        NIDAQmx.AIChannel _AIChannel;
        public void Init()
        {
            /*
             * 此处为处理在部署到RT中并开机启动时，
             * 由于程序缸启动时NIDAQmx驱动还未全初始化完成，
             * 此处用于实时检测驱动是否完成
             * 但如果系统中没有任何任何daqmx设备会导致程序无法继续
             */
#if DEBUG
            while (true)
            {
                var testdaq = new NIDAQmx.DAQmx();
                testdaq.Task.CreateTask("Test");
                if (testdaq.Devices.Count > 0 && testdaq.Devices.Count(x => x.IO.AI.PhysicalChans.Length > 0) > 0)
                {
                    testdaq.Task.ClearTask();
                    break;
                }
                Task.Delay(100).Wait();
                testdaq.Task.ClearTask();
            }
            #endif
            _DAQmx = new NIDAQmx.DAQmx();
            _DAQmx.Task.CreateTask("AI");
            var device = _DAQmx.Devices.First(x => x.IO.AI.PhysicalChans.Length > 0);
            string phchname = string.Join(',', device.IO.AI.PhysicalChans.Take(ServiceConstant.ChannelCount));
            _AIChannel = _DAQmx.AIChannelFactory.CreateVoltage(phchname, "", -10, 10, NIDAQmxConst.TerminalConfig.RES, NIDAQmxConst.AIVoltageUnits.Volts);
            if (_AIChannel == null)
            {
                Tools.LogTool.WriteLog($"创建通道失败,{_DAQmx.Message}");
            }
            _DAQmx.Timing.CfgSampClkTiming(_DAQmx.SystemInfo.SampleClocks[0], _SampleRate, NIDAQmxConst.AnalogActiveEdge.Rising, device.IsSimulated ? NIDAQmxConst.SampleModes.ContSamps : NIDAQmxConst.SampleModes.HWTimedSinglePoint, _SampleRate);
            _DAQmx.Task.StopTask();
            _ServiceConfig = Tools.ServiceConfigTools.ReadConfig();

            IPAddress address = new IPAddress(new byte[] { 0, 0, 0, 0 });

            // create and start the TCP slave
            TcpListener slaveTcpListener = new TcpListener(address, ServiceConstant.ModBusPort);
            //slaveTcpListener.Start();

            IModbusFactory factory = new ModbusFactory();

            IModbusSlaveNetwork network = factory.CreateSlaveNetwork(slaveTcpListener);

            _Slave = factory.CreateSlave(1);
            var dataStore = _Slave.DataStore as DefaultSlaveDataStore;
            if (dataStore == null) return;
            var coefficient = _ServiceConfig.Values;
            var vv = new ushort[coefficient.Length * 2];

            Unsafe.CopyBlock(ref Unsafe.As<ushort, byte>(ref vv[0]), ref Unsafe.As<double, byte>(ref coefficient[0]), (uint)vv.Length * 2);
            Tools.BitChange.Changed(ref Unsafe.As<ushort, byte>(ref vv[0]), (uint)Unsafe.SizeOf<double>(), (uint)coefficient.Length);

            if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
            {
                var interfaces = NetworkInterface.GetAllNetworkInterfaces();
                var ipproperties = interfaces.First(x => x.Name == ServiceConstant.NetcatName).GetIPProperties();
                var val = ipproperties.UnicastAddresses.First(x => x.Address.AddressFamily == AddressFamily.InterNetwork);
                byte[] ip = val.Address.GetAddressBytes();
                byte[] mask = val.IPv4Mask.GetAddressBytes();
                byte[] dns = new byte[4];
                if (ipproperties.DnsAddresses.Count > 0)
                {
                    dns = ipproperties.DnsAddresses[0].GetAddressBytes();
                }
                dataStore.HoldingRegisters.WritePoints(ServiceConstant.IPStartAddr, new ushort[] { Unsafe.As<byte, ushort>(ref ip[0]), Unsafe.As<byte, ushort>(ref ip[2]) });
                dataStore.HoldingRegisters.WritePoints(ServiceConstant.IPStartAddr + 2, new ushort[] { Unsafe.As<byte, ushort>(ref mask[0]), Unsafe.As<byte, ushort>(ref mask[2]) });
                dataStore.HoldingRegisters.WritePoints(ServiceConstant.IPStartAddr + 4, new ushort[] { Unsafe.As<byte, ushort>(ref dns[0]), Unsafe.As<byte, ushort>(ref dns[2]) });
            }
            dataStore.HoldingRegisters.WritePoints(ServiceConstant.ConfigStartAddr, vv);
            (dataStore.HoldingRegisters as DefaultPointSource<ushort>)!.WriteEvent += HoldingRegisters_AfterWrite;
            network.AddSlave(_Slave);
            Task.Run(() => network.ListenAsync().GetAwaiter().GetResult());
        }

        private void HoldingRegisters_AfterWrite(object? sender, ushort e)
        {
            if (e >= ServiceConstant.ConfigStartAddr && e <= ServiceConstant.ConfigStartAddr + ServiceConstant.ConfigLength)
            {
                var vv = _Slave.DataStore.HoldingRegisters.ReadPoints(ServiceConstant.ConfigStartAddr, ServiceConstant.ConfigLength);
                var coefficient = new double[ServiceConstant.ChannelCount * 2];
                Unsafe.CopyBlock(ref Unsafe.As<double, byte>(ref coefficient[0]), ref Unsafe.As<ushort, byte>(ref vv[0]), (uint)(vv.Length * 2));
                Tools.BitChange.Changed(ref Unsafe.As<double, byte>(ref coefficient[0]), (uint)Unsafe.SizeOf<double>(), (uint)coefficient.Length);
                for (int i = 0; i < _ServiceConfig.Coefficients.Count; i++)
                {
                    _ServiceConfig.Coefficients[i].K = coefficient[i * 2];
                    _ServiceConfig.Coefficients[i].B = coefficient[i * 2 + 1];
                }
                Tools.ServiceConfigTools.SaveConfig(_ServiceConfig);
            }
            else if (e >= ServiceConstant.IPStartAddr && e <= ServiceConstant.IPStartAddr + 6)
            {
                var ips = _Slave.DataStore.HoldingRegisters.ReadPoints(ServiceConstant.IPStartAddr, 6);
                var ip = new IPAddress(Unsafe.As<ushort, uint>(ref ips[0]));
                var mask = new IPAddress(Unsafe.As<ushort, uint>(ref ips[2]));
                var dns = new IPAddress(Unsafe.As<ushort, uint>(ref ips[4]));
                Task.Run(() => SetIP(ip.ToString(), mask.ToString(), dns.ToString()));
            }
        }

        private void SetIP(string ip,string mask,string dns)
        {
            if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return;
            LinuxCommandHelper.RunCommand("bash", $"ifconfig {ServiceConstant.NetcatName} {ip} netmask {mask}");
            LinuxCommandHelper.RunCommand("bash", $"route add default gw {dns} {ServiceConstant.NetcatName}");

        }
        public void Start()
        {
            tokenSource = new CancellationTokenSource();
            Task.Run(() => GetData());
        }
        private void GetData()
        {
            Tools.LogTool.WriteLog("采集线程开始");
            try
            {
                _DAQmx.Task.StartTask();
                int per = Unsafe.SizeOf<double>() / Unsafe.SizeOf<ushort>();
                ushort[] result = new ushort[ServiceConstant.ChannelCount * per];
                int readnum = 0;
                while (!tokenSource.IsCancellationRequested)
                {
                    _AIChannel.ReadAnalog(1, -1, true, ref Unsafe.As<ushort, double>(ref result[0]), ServiceConstant.ChannelCount, ref readnum);
                    if (readnum == 1)
                    {
                        Tools.BitChange.Changed(ref Unsafe.As<ushort, byte>(ref result[0]), (uint)Unsafe.SizeOf<double>(), (uint)ServiceConstant.ChannelCount);
                        //lock (_ServiceConfig)
                        {
                            for (int i = 0; i < ServiceConstant.ChannelCount; i++)
                            {
                                ref var temp = ref Unsafe.As<ushort, double>(ref result[i * per]);
                                temp = _ServiceConfig.Coefficients[i].K * temp + _ServiceConfig.Coefficients[i].B;
                            } 
                        }
                        _Slave.DataStore.HoldingRegisters.WritePoints(ServiceConstant.DataStartAddr, result);
                    }
                }
                _DAQmx.Task.StopTask();
            }
            catch(Exception ex)
            {
                Tools.LogTool.WriteLog($"采集错误,{Newtonsoft.Json.JsonConvert.SerializeObject(ex)}");
            }
            Tools.LogTool.WriteLog("采集线程结束");
        }
        public void Stop()
        {
            tokenSource.Cancel();
        }
    }
}
