using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
using Nito.AsyncEx;

namespace Modbus.Net.Siemens
{
    /// <summary>
    ///     西门子Tcp协议
    /// </summary>
    public class SiemensTcpProtocol : SiemensProtocol
    {
        private readonly string _ip;
        private readonly ushort _maxCalled;
        private readonly ushort _maxCalling;
        private readonly ushort _maxPdu;
        private readonly int _port;
        private readonly ushort _taspSrc;
        private readonly byte _tdpuSize;
        private readonly ushort _tsapDst;
        private int _connectTryCount;
        private readonly AsyncLock _lock = new AsyncLock();

        /// <summary>
        ///     构造函数
        /// </summary>
        /// <param name="tdpuSize"></param>
        /// <param name="tsapSrc"></param>
        /// <param name="tsapDst"></param>
        /// <param name="maxCalling"></param>
        /// <param name="maxCalled"></param>
        /// <param name="maxPdu"></param>
        public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled,
            ushort maxPdu)
            : this(tdpuSize, tsapSrc, tsapDst, maxCalling, maxCalled, maxPdu, new ConfigurationBuilder().AddJsonFile($"appsettings.json").Build().GetSection("Config")["IP"])
        {
        }

        /// <summary>
        ///     构造函数
        /// </summary>
        /// <param name="tdpuSize"></param>
        /// <param name="tsapSrc"></param>
        /// <param name="tsapDst"></param>
        /// <param name="maxCalling"></param>
        /// <param name="maxCalled"></param>
        /// <param name="maxPdu"></param>
        /// <param name="ip">IP地址</param>
        public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled,
            ushort maxPdu, string ip)
            : this(
                tdpuSize, tsapSrc, tsapDst, maxCalling, maxCalled, maxPdu, ip,
                int.Parse(new ConfigurationBuilder().AddJsonFile($"appsettings.json").Build().GetSection("Config")["SiemensPort"] ?? "102"))
        {
        }

        /// <summary>
        ///     构造函数
        /// </summary>
        /// <param name="tdpuSize"></param>
        /// <param name="tsapSrc"></param>
        /// <param name="tsapDst"></param>
        /// <param name="maxCalling"></param>
        /// <param name="maxCalled"></param>
        /// <param name="maxPdu"></param>
        /// <param name="ip">IP地址</param>
        /// <param name="port">端口</param>
        public SiemensTcpProtocol(byte tdpuSize, ushort tsapSrc, ushort tsapDst, ushort maxCalling, ushort maxCalled,
            ushort maxPdu, string ip, int port) : base(0, 0)
        {
            _taspSrc = tsapSrc;
            _tsapDst = tsapDst;
            _maxCalling = maxCalling;
            _maxCalled = maxCalled;
            _maxPdu = maxPdu;
            _tdpuSize = tdpuSize;
            _ip = ip;
            _port = port;
            _connectTryCount = 0;
            ProtocolLinker = new SiemensTcpProtocolLinker(_ip, _port);
        }

        /// <summary>
        ///     发送数据并接收
        /// </summary>
        /// <param name="content">发送的数据</param>
        /// <returns>返回的数据</returns>
        public override PipeUnit SendReceive(params object[] content)
        {
            return AsyncHelper.RunSync(() => SendReceiveAsync(Endian, content));
        }

        /// <summary>
        ///     发送数据并接收
        /// </summary>
        /// <param name="content">发送的数据</param>
        /// <returns>返回的数据</returns>
        public override async Task<PipeUnit> SendReceiveAsync(params object[] content)
        {
            if (ProtocolLinker == null || !ProtocolLinker.IsConnected)
                await ConnectAsync();
            return await base.SendReceiveAsync(Endian, content);
        }

        /// <summary>
        ///     发送数据并接收
        /// </summary>
        /// <param name="unit">协议的核心</param>
        /// <param name="content">协议的参数</param>
        /// <returns>返回的数据</returns>
        public override PipeUnit SendReceive(ProtocolUnit unit, IInputStruct content)
        {
            return AsyncHelper.RunSync(() => SendReceiveAsync(unit, content));
        }

        /// <summary>
        ///     发送数据并接收
        /// </summary>
        /// <param name="unit">发送的数据</param>
        /// <param name="content">协议的参数</param>
        /// <returns>返回的数据</returns>
        public override async Task<PipeUnit> SendReceiveAsync(ProtocolUnit unit, IInputStruct content)
        {
            if (ProtocolLinker != null && ProtocolLinker.IsConnected) return await base.SendReceiveAsync(unit, content);
            if (_connectTryCount > 10) return null;
            return
                await
                    ConnectAsync()
                        .ContinueWith(answer => answer.Result ? base.SendReceiveAsync(unit, content) : null).Unwrap();
        }

        /// <summary>
        ///     强制发送数据并接收
        /// </summary>
        /// <param name="unit">发送的数据</param>
        /// <param name="content">协议的参数</param>
        /// <returns>返回的数据</returns>
        private async Task<PipeUnit> ForceSendReceiveAsync(ProtocolUnit unit, IInputStruct content)
        {
            return await base.SendReceiveAsync(unit, content);
        }

        /// <summary>
        ///     连接设备
        /// </summary>
        /// <returns>设备是否连接成功</returns>
        public override async Task<bool> ConnectAsync()
        {
            IOutputStruct outputStruct;
            using (await _lock.LockAsync())
            {
                _connectTryCount++;
                if (ProtocolLinker.IsConnected) return true;
                if (!await ProtocolLinker.ConnectAsync()) return false;
                _connectTryCount = 0;
                var inputStruct = new CreateReferenceSiemensInputStruct(_tdpuSize, _taspSrc, _tsapDst);
                outputStruct =
                    //先建立连接，然后建立设备的引用
                    (await (await
                            ForceSendReceiveAsync(this[typeof(CreateReferenceSiemensProtocol)], inputStruct))
                        .SendReceiveAsync(
                            this[typeof(EstablishAssociationSiemensProtocol)], answer =>
                                answer != null
                                    ? new EstablishAssociationSiemensInputStruct(0x0101, _maxCalling,
                                        _maxCalled,
                                        _maxPdu)
                                    : null)).Unwrap<EstablishAssociationSiemensOutputStruct>();
                if (outputStruct == null && ProtocolLinker.IsConnected)
                {
                    ProtocolLinker.Disconnect();
                }
            }
            return outputStruct != null;
        }
    }
}