//******************************************************************************************************
//
//文件名（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 Opc.Ua;
using Opc.Ua.Client;
using Opc.Ua.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Treasure.通讯协议
{
    /// <summary>
    /// A tool bar used to connect to a server.
    /// </summary>
    public partial class Pro_OPCUA
    {
        #region Constructors
        /// <summary>
        /// Initializes the object.
        /// </summary>
        public Pro_OPCUA()
        {
            m_endpoints = new Dictionary<Uri, EndpointDescription>();
        }
        #endregion

        #region Private Fields
        private ApplicationConfiguration m_configuration;
        private Session m_session;
        private SessionReconnectHandler m_reconnectHandler;
        private EventHandler m_ReconnectComplete;
        private EventHandler m_ReconnectStarting;
        private EventHandler m_KeepAliveComplete;
        private EventHandler m_ConnectComplete;
        private Dictionary<Uri, EndpointDescription> m_endpoints;
        #endregion

        #region Public Members
        /// <summary>
        /// Default session values.
        /// </summary>
        public static readonly uint DefaultSessionTimeout = 60000;
        /// <summary>
        /// 默认发现超时
        /// </summary>
        public static readonly int DefaultDiscoverTimeout = 15000;
        /// <summary>
        /// 默认重新连接时间
        /// </summary>
        public static readonly int DefaultReconnectPeriod = 1000;

        /// <summary>
        /// The name of the session to create.
        /// </summary>
        public string SessionName { get; set; }

        /// <summary>
        /// Gets or sets a flag indicating that the domain checks should be ignored when connecting.
        /// </summary>
        public bool DisableDomainCheck { get; set; }

        /// <summary>
        /// Gets the cached EndpointDescription for a Url.
        /// </summary>
        public EndpointDescription GetEndpointDescription(Uri url)
        {
            EndpointDescription endpointDescription;
            if (m_endpoints.TryGetValue(url, out endpointDescription))
            {
                return endpointDescription;
            }
            return null;
        }

        /// <summary>
        /// The URL displayed in the control.
        /// </summary>
        public string ServerUrl
        {
            get;
            set;
        }

        /// <summary>
        /// Whether to use security when connecting.
        /// </summary>
        public bool UseSecurity
        {
            get;
            set;
        }
        /// <summary>
        /// Get the Client status.
        /// </summary>
        public string ConnectStatus
        {
            get { return m_ConnectStatus; }
        }
        private string m_ConnectStatus;
        /// <summary>
        /// The locales to use when creating the session.
        /// </summary>
        public string[] PreferredLocales { get; set; }

        /// <summary>
        /// The user identity to use when creating the session.
        /// </summary>
        public IUserIdentity UserIdentity { get; set; } = new UserIdentity();

        /// <summary>
        /// The client application configuration.
        /// </summary>
        public ApplicationConfiguration Configuration
        {
            get => m_configuration;

            set
            {
                if (!Object.ReferenceEquals(m_configuration, value))
                {
                    m_configuration = value;
                }
            }
        }

        /// <summary>
        /// The currently active session. 
        /// </summary>
        public Session Session => m_session;

        /// <summary>
        /// The number of seconds between reconnect attempts (0 means reconnect is disabled).
        /// </summary>
        public int ReconnectPeriod { get; set; } = DefaultReconnectPeriod;

        /// <summary>
        /// The discover timeout.
        /// </summary>
        public int DiscoverTimeout { get; set; } = DefaultDiscoverTimeout;

        /// <summary>
        /// The session timeout.
        /// </summary>
        public uint SessionTimeout { get; set; } = DefaultSessionTimeout;

        /// <summary>
        /// Raised when a good keep alive from the server arrives.
        /// </summary>
        public event EventHandler KeepAliveComplete
        {
            add { m_KeepAliveComplete += value; }
            remove { m_KeepAliveComplete -= value; }
        }

        /// <summary>
        /// Raised when a reconnect operation starts.
        /// </summary>
        public event EventHandler ReconnectStarting
        {
            add { m_ReconnectStarting += value; }
            remove { m_ReconnectStarting -= value; }
        }

        /// <summary>
        /// Raised when a reconnect operation completes.
        /// </summary>
        public event EventHandler ReconnectComplete
        {
            add { m_ReconnectComplete += value; }
            remove { m_ReconnectComplete -= value; }
        }

        /// <summary>
        /// Raised after successfully connecting to or disconnecing from a server.
        /// </summary>
        public event EventHandler ConnectComplete
        {
            add { m_ConnectComplete += value; }
            remove { m_ConnectComplete -= value; }
        }

        /// <summary>
        /// Creates a new session.
        /// </summary>
        /// <returns>The new session object.</returns>
        private async Task<Session> Connect(
            ITransportWaitingConnection connection,
            EndpointDescription endpointDescription,
            bool useSecurity,
            uint sessionTimeout = 0)
        {
            // disconnect from existing session.
            InternalDisconnect();
            // select the best endpoint.
            if (endpointDescription == null)
            {
                endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, connection, useSecurity, DiscoverTimeout);
            }

            EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(m_configuration);
            ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

            m_session = await Session.Create(
                m_configuration,
                connection,
                endpoint,
                false,
                !DisableDomainCheck,
                (String.IsNullOrEmpty(SessionName)) ? m_configuration.ApplicationName : SessionName,
                sessionTimeout == 0 ? DefaultSessionTimeout : sessionTimeout,
                UserIdentity,
                PreferredLocales);
            // set up keep alive callback.
            m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);

            // return the new session.
            return m_session;
        }

        /// <summary>
        /// Creates a new session.
        /// </summary>
        /// <returns>The new session object.</returns>
        private async Task<Session> ConnectServer(
            string serverUrl,
            bool useSecurity,
            uint sessionTimeout = 0)
        {
            try
            {
                // disconnect from existing session.
                InternalDisconnect();
                // select the best endpoint.
                Console.WriteLine("获取远程节点1");
                var endpointDescription = CoreClientUtils.SelectEndpoint(serverUrl, useSecurity, 600000);
                var endpointConfiguration = EndpointConfiguration.Create(m_configuration);
                var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);
               // Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(endpoint));
                Console.WriteLine("开始连接");
                m_session = await Session.Create(
                    m_configuration,
                    endpoint,
                    false,
                    !DisableDomainCheck,
                    (String.IsNullOrEmpty(SessionName)) ? m_configuration.ApplicationName : SessionName,
                    sessionTimeout == 0 ? DefaultSessionTimeout : sessionTimeout,
                    UserIdentity,
                    PreferredLocales);
                // set up keep alive callback.
                m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);
                Console.WriteLine("连接完成");
                Console.WriteLine(m_session.Connected);
                isCconnect = true;
            }
            catch (Exception ex)
            {
                if (m_session!=null)
                {
                    m_session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);
                }
                else
                {
                    isCconnect = false;
                }
            }
            
            try
            {
                UpdateStatus(false, DateTime.Now, "Connected, loading complex type system.");
            }
            catch (Exception e)
            {
                UpdateStatus(true, DateTime.Now, "Connected, failed to load complex type system.");
                Utils.Trace(e, "Failed to load complex type system.");
            }

            // return the new session.
            return m_session;
        }

        /// <summary>
        /// Creates a new session.
        /// </summary>
        /// <param name="serverUrl">The URL of a server endpoint.</param>
        /// <param name="useSecurity">Whether to use security.</param>
        /// <param name="sessionTimeout">会话超时时间.</param>
        /// <returns>The new session object.</returns>
        public async Task<Session> ConnectServerAsync(
            string serverUrl = null,
            bool useSecurity = false,
            uint sessionTimeout = 0
            )
        {
            return await Task.Run(() => ConnectServer(serverUrl, useSecurity, sessionTimeout));
        }

        /// <summary>
        /// Create a new reverse connection.
        /// </summary>
        /// <param name="connection"></param>
        /// <param name="useSecurity"></param>
        /// <param name="discoverTimeout"></param>
        /// <param name="sessionTimeout"></param>
        public async Task<Session> ConnectServerAsync(
            ITransportWaitingConnection connection,
            bool useSecurity,
            int discoverTimeout = -1,
            uint sessionTimeout = 0
            )
        {
            if (connection.EndpointUrl == null)
            {
                throw new ArgumentException("Endpoint URL is not valid.");
            }

            EndpointDescription endpointDescription = null;

            if (!m_endpoints.TryGetValue(connection.EndpointUrl, out endpointDescription))
            {
                // Discovery uses the reverse connection and closes it
                // return and wait for next reverse hello
                endpointDescription = CoreClientUtils.SelectEndpoint(m_configuration, connection, useSecurity, discoverTimeout);
                m_endpoints[connection.EndpointUrl] = endpointDescription;
                return null;
            }

            return await Connect(connection, endpointDescription, useSecurity, sessionTimeout);
        }

        /// <summary>
        /// Disconnects from the server.
        /// </summary>
        public Task DisconnectAsync()
        {
            UpdateStatus(false, DateTime.Now, "Disconnected");
            return Task.Run(() => InternalDisconnect());
        }

        /// <summary>
        /// Disconnects from the server.
        /// </summary>
        private void InternalDisconnect()
        {
            // stop any reconnect operation.
            if (m_reconnectHandler != null)
            {
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;
            }

            // disconnect any existing session.
            if (m_session != null)
            {
                m_session.KeepAlive -= Session_KeepAlive;
                m_session.Close(10000);
                m_session = null;
            }
        }

        /// <summary>
        /// Disconnects from the server.
        /// </summary>
        public void Disconnect()
        {
            UpdateStatus(false, DateTime.Now, "Disconnected");

            // stop any reconnect operation.
            InternalDisconnect();
        }

        /// <summary>
        /// Prompts the user to choose a server on another host.
        /// </summary>
        public void Discover(string hostName)
        {
            //待修改：添加服务器发现功能
           // string endpointUrl = new DiscoverServerDlg().ShowDialog(m_configuration, hostName);

            //if (endpointUrl != null)
            //{
            //    ServerUrl = endpointUrl;
            //}
        }
        #endregion

        #region Event Handlers
        /// <summary>
        /// Updates the status control.
        /// </summary>
        /// <param name="error">Whether the status represents an error.</param>
        /// <param name="time">The time associated with the status.</param>
        /// <param name="status">The status message.</param>
        /// <param name="args">Arguments used to format the status message.</param>
        private void UpdateStatus(bool error, DateTime time, string status, params object[] args)
        {
             m_ConnectStatus= String.Format(status, args);
        }

        /// <summary>
        /// Handles a keep alive event from a session.
        /// </summary>
        private void Session_KeepAlive(Session session, KeepAliveEventArgs e)
        {
            try
            {
                // check for events from discarded sessions.
                if (!Object.ReferenceEquals(session, m_session))
                {
                    return;
                }

                // start reconnect sequence on communication error.
                if (Opc.Ua.ServiceResult.IsBad(e.Status))
                {
                    if (ReconnectPeriod <= 0)
                    {
                        UpdateStatus(true, e.CurrentTime, "Communication Error ({0})", e.Status);
                        return;
                    }

                    UpdateStatus(true, e.CurrentTime, "Reconnecting in {0}s", ReconnectPeriod);

                    if (m_reconnectHandler == null)
                    {
                        if (m_ReconnectStarting != null)
                        {
                            m_ReconnectStarting(this, e);
                        }

                        m_reconnectHandler = new SessionReconnectHandler();
                        m_reconnectHandler.BeginReconnect(m_session, ReconnectPeriod * 1000, Server_ReconnectComplete);
                    }

                    return;
                }

                // update status.
                UpdateStatus(false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl);

                // raise any additional notifications.
                if (m_KeepAliveComplete != null)
                {
                    m_KeepAliveComplete(this, e);
                }
            }
            catch (Exception exception)
            {
                throw new Exception("Session_KeepAlive", exception);
            }
        }
        /// <summary>
        /// Handles a reconnect event complete from the reconnect handler.
        /// </summary>
        private void Server_ReconnectComplete(object sender, EventArgs e)
        {
            try
            {
                // ignore callbacks from discarded objects.
                if (!Object.ReferenceEquals(sender, m_reconnectHandler))
                {
                    return;
                }

                m_session = m_reconnectHandler.Session;
                m_reconnectHandler.Dispose();
                m_reconnectHandler = null;

                // raise any additional notifications.
                if (m_ReconnectComplete != null)
                {
                    m_ReconnectComplete(this, e);
                }
            }
            catch (Exception exception)
            {
                throw new Exception("Server_ReconnectComplete", exception);
            }
        }
        #endregion
    }
}
