using System;
using System.Collections;
using System.Security.Cryptography.X509Certificates;

using Opc.Ua;
using Opc.Ua.Client;
using Siemens.OpcUA.Helper;

namespace Siemens.OpcUA
{
    /// <summary>
    /// Event handler for the event that the client received a value change.
    /// </summary>
    public delegate void valueChanged(object clientHandle, DataValue value);
    
    /// <summary>
    /// Event handler for the event that the client received a untrusted server certificate.
    /// </summary>
    public delegate void certificateValidation(CertificateValidator validator, CertificateValidationEventArgs e);

    /// <summary>
    /// This class encapsulates a connection to an OPC UA server and access to particular Services of it.
    /// </summary>
    public class Server
    {
        public object lockThis = new object();
        #region Construction
        public Server()
        { }
        #endregion

        #region Fields
        /// <summary> 
        /// Keeps a session with an UA server. 
        /// </summary>
        private Session m_Session = null;

        /// <summary> 
        /// Interface which encapsulates the use of the browse service of an UA server. 
        /// </summary>
        private Browser m_Browser = null;

        /// <summary> 
        /// Keeps a hash table for attribute names. 
        /// </summary>
        private Hashtable m_hashAttributeNames = null;
        #endregion
                
        #region Properties
        /// <summary>
        /// Use the certificateValidation delegate as event.
        /// </summary>
        public event certificateValidation CertificateEvent = null;

        /// <summary>
        /// Provides the session being established with an OPC UA server.
        /// </summary>
        public Session Session
        {
            get { return m_Session; }
        }
        #endregion
             
        #region Connect
        /// <summary>Establishes the connection to an OPC UA server.</summary>
        /// <param name="url">The Url of the endpoint.</param>
        /// <returns>Result code.</returns>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public void Connect(string Url)
        {
            try
            {
                // Create the configuration.     
                ApplicationConfiguration configuration = Helpers.CreateClientConfiguration();

                // Create the endpoint description.
                EndpointDescription endpointDescription = Helpers.CreateEndpointDescription(Url);

                // Create the endpoint configuration (use the application configuration to provide default values).
                EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration);

                // The default timeout for a requests sent using the channel.
                endpointConfiguration.OperationTimeout = 300000;

                // Use the pure binary encoding on the wire.
                endpointConfiguration.UseBinaryEncoding = true;

                // Create the endpoint.
                ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

                // Create the binding factory.
                BindingFactory bindingFactory = BindingFactory.Create(configuration);

                // Update endpoint description using the discovery endpoint.
                if (endpoint.UpdateBeforeConnect)
                {
                    endpoint.UpdateFromServer(bindingFactory); 
                    endpointDescription = endpoint.Description;
                    endpointConfiguration = endpoint.Configuration;
                }

                X509Certificate2 clientCertificate = configuration.SecurityConfiguration.ApplicationCertificate.Find();

                // Set up a callback to handle certificate validation errors.
                configuration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation);

                // Initialize the channel which will be created with the server.
                SessionChannel channel = SessionChannel.Create(
                    configuration,
                    endpointDescription,
                    endpointConfiguration,
                    bindingFactory,
                    clientCertificate,
                    null);

                // Wrap the channel with the session object.
                // This call will fail if the server does not trust the client certificate.
                m_Session = new Session(channel, configuration, endpoint);
                m_Session.ReturnDiagnostics = DiagnosticsMasks.All;

                // Register keep alive callback.
                m_Session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);

                UserIdentity identity = new UserIdentity();

                // Create the session. This actually connects to the server.
                // Passing null for the user identity will create an anonymous session.
                m_Session.Open("Siemens OPC UA sample client - main session", identity);
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>Establishes the connection to an OPC UA server.</summary>
        /// <param name="url">The Url of the endpoint.</param>
        /// <returns>Result code.</returns>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public void Connect(EndpointDescription endpointDescription)
        {
            try
            {
                // Create the configuration.     
                ApplicationConfiguration configuration = Helpers.CreateClientConfiguration();

                // Create the endpoint configuration (use the application configuration to provide default values).
                EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(configuration);

                // The default timeout for a requests sent using the channel.
                endpointConfiguration.OperationTimeout = 300000;

                // Use the pure binary encoding on the wire.
                endpointConfiguration.UseBinaryEncoding = true;

                // Create the endpoint.
                ConfiguredEndpoint endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration);

                // Create the binding factory.
                BindingFactory bindingFactory = BindingFactory.Create(configuration);

                X509Certificate2 clientCertificate = configuration.SecurityConfiguration.ApplicationCertificate.Find();

                // Set up a callback to handle certificate validation errors.
                configuration.CertificateValidator.CertificateValidation += new CertificateValidationEventHandler(CertificateValidator_CertificateValidation);

                // Initialize the channel which will be created with the server.
                SessionChannel channel = SessionChannel.Create(
                    configuration,
                    endpointDescription,
                    endpointConfiguration,
                    bindingFactory,
                    clientCertificate,
                    null);

                // Wrap the channel with the session object.
                // This call will fail if the server does not trust the client certificate.
                m_Session = new Session(channel, configuration, endpoint);
                m_Session.ReturnDiagnostics = DiagnosticsMasks.All;

                // Register keep alive callback.
                m_Session.KeepAlive += new KeepAliveEventHandler(Session_KeepAlive);

                UserIdentity identity = new UserIdentity();

                // Create the session. This actually connects to the server.
                // Passing null for the user identity will create an anonymous session.
                m_Session.Open("Siemens OPC UA sample client - main session", identity);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
                
        /// <summary>Releases the connection to an OPC UA server.</summary>
        /// <returns>Result code.</returns>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public int Disconnect()
        {
            int result = 0;
            
            // Close the session.
            try
            {
                m_Session.Close(10000);
            }
            catch (Exception e)
            {
                return -1;
                throw e;
            } 
           
            // Delete browser.
            m_Browser = null;
            
            return result;
        }
        #endregion 
        
        #region Browse
        /// <summary>
        /// Navigates through the address space of an OPC UA server.
        /// </summary>
        /// <param name="nodeId">The starting node.</param>
        /// <param name="browseResults">A list of results for the passed starting node and filters.</param>
        /// <returns>Result code.</returns>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public int Browse(NodeId nodeId, out ReferenceDescriptionCollection browseResults)
        {
            int result = 0;
            
            if (m_Browser == null)
            {
                // Instantiate browser.
                m_Browser = new Browser(m_Session);
            }

            // Browse filter: only follow hierarchical references.
            m_Browser.ReferenceTypeId = ReferenceTypes.HierarchicalReferences;

            browseResults = null;

            try
            {
                // The actual call to the server.
                browseResults = m_Browser.Browse(nodeId);
            }
            catch (Exception e)
            {
                result = -1;
                throw e;
            }

            return result;
        }
        #endregion 

        #region Read / Write
        /// <summary>
        /// Reads any attribute of one or more nodes.
        /// </summary>
        /// <param name="nodesToRead">A list of nodes to read.</param>
        /// <param name="results">A list of read results.</param>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public void Read(
            ReadValueIdCollection nodesToRead,
            out DataValueCollection results)
        {
            
            results = null;
            DiagnosticInfoCollection diagnosticInfos = null;

            try
            {
                // The actual call to the server.
                m_Session.Read(
                    null,
                    0,
                    TimestampsToReturn.Both,
                    nodesToRead,
                    out results,
                    out diagnosticInfos);
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// Reads the value attribute of one or more nodes.
        /// </summary>
        /// <param name="nodesToRead">A list of nodes to read.</param>
        /// <param name="results">A list of data values.</param>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public ResponseHeader ReadValues(
            NodeIdCollection nodesToRead,
            out DataValueCollection results)
        {
            ResponseHeader response = null;

            results = null;
            DiagnosticInfoCollection diagnosticInfos;

            // build list of attributes to read.
            ReadValueIdCollection valueIdsToRead = new ReadValueIdCollection();

            try
            {
                for (int i = 0; i < nodesToRead.Count; i++)
                {
                    ReadValueId attributeToRead = new ReadValueId();
                    attributeToRead.NodeId = nodesToRead[i];
                    attributeToRead.AttributeId = Attributes.Value;
                    attributeToRead.Handle = attributeIdToString(Attributes.Value);
                    valueIdsToRead.Add(attributeToRead);
                }

                response = m_Session.Read(
                        null,
                        0,
                        TimestampsToReturn.Both,
                        valueIdsToRead,
                        out results,
                        out diagnosticInfos);
            }
            catch (Exception e)
            {
                throw e;
            }
            return response;
        }
        
        /// <summary>
        /// Writes the Value attribute of one or more nodes.
        /// </summary>
        /// <param name="nodesToWrite">A list of nodes to write.</param>
        /// <param name="results">A list of values to write.</param>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public void WriteValues(
            NodeIdCollection nodesToWrite,
            DataValueCollection values,
            out StatusCodeCollection results)
        {
            results = null;
            
            DiagnosticInfoCollection diagnosticInfos = null;

            // Build list of attributes to write.
            WriteValueCollection valuesToWrite = new WriteValueCollection();

            try
            {
                for (int i = 0; i < nodesToWrite.Count; i++)
                {
                    WriteValue attributeToWrite = new WriteValue();
                    attributeToWrite.NodeId = nodesToWrite[i];
                    // We have to write the Value attribute only:
                    attributeToWrite.AttributeId = Attributes.Value;
                    attributeToWrite.Handle = attributeIdToString(Attributes.Value);
                    // Set the according value:
                    attributeToWrite.Value = values[i];
                    valuesToWrite.Add(attributeToWrite);
                }

                // Actual call to server.
                m_Session.Write(
                        null,
                        valuesToWrite,
                        out results,
                        out diagnosticInfos);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        #endregion

        #region 20220518 zb add asynchronous Writes
        /// <summary>
        /// asynchronous Writes the Value attribute of one or more nodes.
        /// </summary>
        /// <param name="nodesToWrite">A list of nodes to write.</param>
        /// <param name="results">A list of values to write.</param>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public void AsyWriteValues(
            NodeIdCollection nodesToWrite,
            DataValueCollection values,
            out StatusCodeCollection results)
        {
            results = null;

            //DiagnosticInfoCollection diagnosticInfos = null;

            // Build list of attributes to write.
            WriteValueCollection valuesToWrite = new WriteValueCollection();

            try
            {
                for (int i = 0; i < nodesToWrite.Count; i++)
                {
                    WriteValue attributeToWrite = new WriteValue();
                    attributeToWrite.NodeId = nodesToWrite[i];
                    // We have to write the Value attribute only:
                    attributeToWrite.AttributeId = Attributes.Value;
                    attributeToWrite.Handle = attributeIdToString(Attributes.Value);
                    // Set the according value:
                    attributeToWrite.Value = values[i];
                    valuesToWrite.Add(attributeToWrite);
                }

                // Actual call to server. AsyWriteValues
                m_Session.BeginWrite(
                        null,
                        valuesToWrite,
                        null,  // miss the call back function
                        null); // miss the call back function 
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        #endregion


        #region Subscribe for Data Changes
        /// <summary>
        /// Creates a subscription and associate it with the current session.
        /// </summary>
        /// <param name="publishingInterval">The requested publishing interval.</param>
        /// <returns>Instance of class Subscription.</returns>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public Subscription AddSubscription(int publishingInterval)
        {
            Subscription newSubscription = null;
            
            try
            {
                Opc.Ua.Client.Subscription innerSubscription = new Opc.Ua.Client.Subscription(m_Session.DefaultSubscription);

                innerSubscription.DisplayName = "My Subscription Name";
                innerSubscription.PublishingEnabled = true;
                innerSubscription.PublishingInterval = publishingInterval; // in milliseconds.
                innerSubscription.KeepAliveCount = 10;   // 10*UaRefreshRate  = 5s if UaRefreshRate = 500
                innerSubscription.LifetimeCount = 100;  // UaRefreshRate*100 = 50s if UaRefreshRate = 500;
                innerSubscription.MaxNotificationsPerPublish = 1000;//ÿ�ζ��ķ��أ������������������//20151120 
                
                // Associate the subscription with the session.            
                m_Session.AddSubscription(innerSubscription);

                // Call the server and create the subscription.
                innerSubscription.Create();

                // At this point the subscription is sending publish requests at the keep alive rate.
                // Use the Notification event the session to receive updates when a publish completes.
                m_Session.Notification += new NotificationEventHandler(Session_Notification);

                newSubscription = new Subscription();
                newSubscription.Session = m_Session;
                newSubscription.innerSubscription = innerSubscription;
            }
            catch (Exception e)
            {
                throw e;
            }

            return newSubscription;
        }

        // Modifies the publishing interval of a subscription.
        /// <summary>
        /// Creates a subscription and associates it with the current session.
        /// </summary>
        /// <param name="subscription">The subscription object.</param>
        /// <param name="subscription">The newly requested publishing interval.</param>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public void ModifySubscription(Subscription subscription,int publishingInterval)
        {
            try
            {
                // Modify publishing interval
                subscription.innerSubscription.PublishingInterval = publishingInterval;

                // Apply change
                subscription.innerSubscription.Modify();
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        /// <summary>
        /// Removes a subscription from the session.
        /// </summary>
        /// <param name="subscription">The subscription object.</param>
        /// <exception cref="Exception">Throws and forwards any exception with short error description.</exception>
        public void RemoveSubscription(Subscription subscription)
        {
            try
            {                
                // Remove the subscription from the corresponding session.
                m_Session.RemoveSubscription(subscription.innerSubscription);
            }
            catch (Exception e)
            {
                throw e;
            }
        }
        #endregion

        #region Event Handlers
        /// <summary>
        /// Will be raised when a certificate is classified as untrusted.
        /// </summary>
        /// <param name="validator">The event handler.</param>
        /// <param name="e">The <see cref="Opc.Ua.CertificateValidationEventArgs"/>Instance containing the event data.</param>
        private void CertificateValidator_CertificateValidation(CertificateValidator validator, CertificateValidationEventArgs e)
        {
            if (CertificateEvent != null)
            {
                CertificateEvent(validator, e);
            }
            else
            {
                e.Accept = false;
            }
        }

        /// <summary>
        /// Keep-alive callback.
        /// </summary>
        /// <param name="session">The target of the event.</param>
        /// <param name="e">The <see cref="Opc.Ua.KeepAliveEventArgs"/>Instance containing the event data.</param>
        private void Session_KeepAlive(Session session, KeepAliveEventArgs e)
        {
        }

        /// <summary>
        /// Raised when a publish response arrives from the server.
        /// </summary>
        /// <param name="session">The target of the event.</param>
        /// <param name="e">The <see cref="Opc.Ua.Client.NotifacationEventArgs"/>Instance containing the event data.</param> 
        private void Session_Notification(Session session, NotificationEventArgs e)
        {
            lock (lockThis)
            {
                NotificationMessage message = e.NotificationMessage;

                // Check for keep alive.
                if (message.NotificationData.Count == 0)
                {
                    return;
                }

                // Get the data changes (oldest to newest).
                foreach (MonitoredItemNotification datachange in message.GetDataChanges(false))
                {
                    // Lookup the monitored item.
                    MonitoredItem monitoredItem = e.Subscription.FindItemByClientHandle(datachange.ClientHandle);

                    if (monitoredItem == null)
                    {
                        continue;
                    }

                    ClientMonitoredItemData clientData = monitoredItem.Handle as ClientMonitoredItemData;

                    clientData.callback(clientData.clientHandle, datachange.Value);
                }
            }
        }
        #endregion
                
        #region Helper Methods
        /// <summary>
        /// Converts an attribute id to string.
        /// </summary>
        /// <param name="attributeId">The attribute id.</param>
        /// <returns>The attribute name as string.</returns>
        public string attributeIdToString(uint attributeId)
        {
            // Populate hashtable if called for the first time
            if (m_hashAttributeNames == null)
            {
                m_hashAttributeNames = new Hashtable();

                m_hashAttributeNames.Add(Attributes.AccessLevel, "AccessLevel");
                m_hashAttributeNames.Add(Attributes.ArrayDimensions, "ArrayDimensions");
                m_hashAttributeNames.Add(Attributes.BrowseName, "BrowseName");
                m_hashAttributeNames.Add(Attributes.ContainsNoLoops, "ContainsNoLoops");
                m_hashAttributeNames.Add(Attributes.DataType, "DataType");
                m_hashAttributeNames.Add(Attributes.Description, "Description");
                m_hashAttributeNames.Add(Attributes.DisplayName, "DisplayName");
                m_hashAttributeNames.Add(Attributes.EventNotifier, "EventNotifier");
                m_hashAttributeNames.Add(Attributes.Executable, "Executable");
                m_hashAttributeNames.Add(Attributes.Historizing, "Historizing");
                m_hashAttributeNames.Add(Attributes.InverseName, "InverseName");
                m_hashAttributeNames.Add(Attributes.IsAbstract, "IsAbstract");
                m_hashAttributeNames.Add(Attributes.MinimumSamplingInterval, "MinimumSamplingInterval");
                m_hashAttributeNames.Add(Attributes.NodeClass, "NodeClass");
                m_hashAttributeNames.Add(Attributes.NodeId, "NodeId");
                m_hashAttributeNames.Add(Attributes.Symmetric, "Symmetric");
                m_hashAttributeNames.Add(Attributes.UserAccessLevel, "UserAccessLevel");
                m_hashAttributeNames.Add(Attributes.UserExecutable, "UserExecutable");
                m_hashAttributeNames.Add(Attributes.UserWriteMask, "UserWriteMask");
                m_hashAttributeNames.Add(Attributes.Value, "Value");
                m_hashAttributeNames.Add(Attributes.ValueRank, "ValueRank");
                m_hashAttributeNames.Add(Attributes.WriteMask, "WriteMask");       
            }

            string ret = (string)m_hashAttributeNames[attributeId];

            return ret;
        }

        // 
        /// <summary>
        /// Converts an attribute value to an user readable string.
        /// </summary>
        /// <param name="nodeRead">Contains the attriute id.</param>
        /// <param name="nodeRead">The value to convert.</param>
        /// <returns>The attribute value as string.</returns>
        public string attributeValueToString(ReadValueId nodeRead, DataValue result)
        {
            if (result == null || result.Value == null)
            {
                return "";
            }

            string ret = "";
            switch (nodeRead.AttributeId)
            {
                case Attributes.AccessLevel:
                case Attributes.UserAccessLevel:
                    {
                        // Check datatype.
                        if (result.Value.GetType() == typeof(byte))
                        {
                            if (((byte)result.Value & AccessLevels.CurrentRead) == AccessLevels.CurrentRead)
                            {
                                ret = "Readable";
                            }
                            if (((byte)result.Value & AccessLevels.CurrentWrite) == AccessLevels.CurrentWrite)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "Writeable";
                            }
                            if (((byte)result.Value & AccessLevels.HistoryRead) == AccessLevels.HistoryRead)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "HistoryReadable";
                            }
                            if (((byte)result.Value & AccessLevels.HistoryWrite) == AccessLevels.HistoryWrite)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "HistoryWriteable";
                            }
                            if (ret.Length == 0)
                            {
                                ret = "None";
                            }
                        }
                        else
                        {
                            ret = "Invalid Datatype: Expected Byte, Received " + result.Value.GetType().ToString();
                        }
                    }
                    break;

                case Attributes.WriteMask:
                case Attributes.UserWriteMask:
                    {
                        // Check datatype.
                        if (result.Value.GetType() == typeof(uint))
                        {
                            if (((uint)result.Value & (uint)AttributeWriteMask.AccessLevel) == (uint)AttributeWriteMask.AccessLevel)
                            {
                                ret = "AccessLevel";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.BrowseName) == (uint)AttributeWriteMask.BrowseName)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "BrowseName";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.ContainsNoLoops) == (uint)AttributeWriteMask.ContainsNoLoops)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "ContainsNoLoops";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.DataType) == (uint)AttributeWriteMask.DataType)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "DataType";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.Description) == (uint)AttributeWriteMask.Description)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "Description";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.DisplayName) == (uint)AttributeWriteMask.DisplayName)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "DisplayName";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.EventNotifier) == (uint)AttributeWriteMask.EventNotifier)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "EventNotifier";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.Executable) == (uint)AttributeWriteMask.Executable)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "Executable";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.Historizing) == (uint)AttributeWriteMask.Historizing)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "Historizing";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.InverseName) == (uint)AttributeWriteMask.InverseName)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "InverseName";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.IsAbstract) == (uint)AttributeWriteMask.IsAbstract)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "IsAbstract";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.MinimumSamplingInterval) == (uint)AttributeWriteMask.MinimumSamplingInterval)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "MinimumSamplingInterval";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.Symmetric) == (uint)AttributeWriteMask.Symmetric)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "Symmetric";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.UserAccessLevel) == (uint)AttributeWriteMask.UserAccessLevel)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "UserAccessLevel";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.UserExecutable) == (uint)AttributeWriteMask.UserExecutable)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "UserExecutable";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.UserWriteMask) == (uint)AttributeWriteMask.UserWriteMask)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "UserWriteMask";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.ValueRank) == (uint)AttributeWriteMask.ValueRank)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "ValueRank";
                            }
                            if (((uint)result.Value & (uint)AttributeWriteMask.WriteMask) == (uint)AttributeWriteMask.WriteMask)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "WriteMask";
                            }
                            if (ret.Length == 0)
                            {
                                ret = "None";
                            }
                        }
                        else
                        {
                            ret = "Invalid Datatype: Expected Uint32, Received " + result.Value.GetType().ToString();
                        }
                    }
                    break;

                case Attributes.NodeClass:
                    {
                        // Check datatype.
                        if (result.Value.GetType() == typeof(Int32))
                        {
                            switch ((Int32)result.Value)
                            {
                                case (int)NodeClass.DataType:
                                    ret = "DataType";
                                    break;
                                case (int)NodeClass.Method:
                                    ret = "Method";
                                    break;
                                case (int)NodeClass.Object:
                                    ret = "Object";
                                    break;
                                case (int)NodeClass.ObjectType:
                                    ret = "ObjectType";
                                    break;
                                case (int)NodeClass.ReferenceType:
                                    ret = "ReferenceType";
                                    break;
                                case (int)NodeClass.Unspecified:
                                    ret = "Unspecified";
                                    break;
                                case (int)NodeClass.Variable:
                                    ret = "Variable";
                                    break;
                                case (int)NodeClass.VariableType:
                                    ret = "VariableType";
                                    break;
                                case (int)NodeClass.View:
                                    ret = "View";
                                    break;
                                default:
                                    ret = "Invalid NodeClass";
                                    break;
                            }
                        }
                        else
                        {
                            ret = "Invalid Datatype: Expected Int32, Received " + result.Value.GetType().ToString();
                        }
                    }
                    break;

                case Attributes.EventNotifier:
                    {
                        // Check datatype.
                        if (result.Value.GetType() == typeof(byte))
                        {
                            if (((byte)result.Value & EventNotifiers.HistoryRead) == EventNotifiers.HistoryRead)
                            {
                                ret = "HistoryRead";
                            }
                            if (((byte)result.Value & EventNotifiers.HistoryWrite) == EventNotifiers.HistoryWrite)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "HistoryWrite";
                            }
                            if (((byte)result.Value & EventNotifiers.SubscribeToEvents) == EventNotifiers.SubscribeToEvents)
                            {
                                if (ret.Length > 0)
                                {
                                    ret += ", ";
                                }
                                ret += "SubscribeToEvents";
                            }
                            if (ret.Length == 0)
                            {
                                ret += "None";
                            }
                        }
                        else
                        {
                            ret = "Invalid Datatype: Expected Byte, Received " + result.Value.GetType().ToString();
                        }
                    }
                    break;
                case Attributes.DataType:
                    {
                        // Check datatype.
                        if (result.Value.GetType() == typeof(NodeId))
                        {
                            NodeId node = (NodeId)result.Value;
                            INode datatype = m_Session.NodeCache.Find(node);

                            if (datatype != null)
                            {
                                ret = String.Format("{0}", datatype);
                            }
                            else
                            {
                                ret = String.Format("{0}", node);
                            }
                        }
                        else
                        {
                            ret = "Invalid Datatype: Expected NodeId, Received " + result.Value.GetType().ToString();
                        }                       

                    }
                    break;

                case Attributes.ArrayDimensions:
                case Attributes.BrowseName:
                case Attributes.ContainsNoLoops:
                case Attributes.Description:
                case Attributes.DisplayName:
                case Attributes.Executable:
                case Attributes.Historizing:
                case Attributes.InverseName:
                case Attributes.IsAbstract:
                case Attributes.MinimumSamplingInterval:
                case Attributes.NodeId:
                case Attributes.Symmetric:
                case Attributes.UserExecutable:
                case Attributes.Value:
                case Attributes.ValueRank:

                default:
                    ret = result.Value.ToString();
                    break;
            }
            return ret;
        }
        #endregion

    }
}