/// <summary>
        /// Create patch twin model to upload
        /// </summary>
        /// <param name="registration"></param>
        /// <param name="update"></param>
        public static DeviceTwinModel Patch(this ApplicationRegistration registration,
                                            ApplicationRegistration update)
        {
            var twin = BaseRegistrationEx.PatchBase(registration, update);

            // Tags

            if (update?.ApplicationType != null &&
                update?.ApplicationType != registration?.ApplicationType)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.ApplicationType),
                              JToken.FromObject(update.ApplicationType));
                twin.Tags.Add(nameof(ApplicationType.Server),
                              update.ApplicationType != ApplicationType.Client);
                twin.Tags.Add(nameof(ApplicationType.Client),
                              update.ApplicationType != ApplicationType.Server &&
                              update.ApplicationType != ApplicationType.DiscoveryServer);
                twin.Tags.Add(nameof(ApplicationType.DiscoveryServer),
                              update.ApplicationType == ApplicationType.DiscoveryServer);
            }

            if ((update?.ApplicationState ?? ApplicationState.New) !=
                registration?.ApplicationState)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.ApplicationState),
                              JToken.FromObject(
                                  update?.ApplicationState ?? ApplicationState.New));
            }

            if (update?.ApplicationUri != registration?.ApplicationUri)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.ApplicationUri),
                              update?.ApplicationUri);
                twin.Tags.Add(nameof(ApplicationRegistration.ApplicationUriLC),
                              update?.ApplicationUriLC);
            }

            if (update?.RecordId != registration?.RecordId)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.RecordId),
                              update?.RecordId);
            }

            if (update?.ApplicationName != registration?.ApplicationName)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.ApplicationName),
                              update?.ApplicationName);
            }

            if (update?.Locale != registration?.Locale)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.Locale),
                              update?.Locale);
            }

            if (update?.DiscoveryProfileUri != registration?.DiscoveryProfileUri)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.DiscoveryProfileUri),
                              update?.DiscoveryProfileUri);
            }

            if (update?.GatewayServerUri != registration?.GatewayServerUri)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.GatewayServerUri),
                              update?.GatewayServerUri);
            }

            if (update?.ProductUri != registration?.ProductUri)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.ProductUri), update?.ProductUri);
            }

            var urlUpdate = update?.DiscoveryUrls.DecodeAsList().SequenceEqualsSafe(
                registration?.DiscoveryUrls?.DecodeAsList());

            if (!(urlUpdate ?? true))
            {
                twin.Tags.Add(nameof(ApplicationRegistration.DiscoveryUrls),
                              update?.DiscoveryUrls == null ?
                              null : JToken.FromObject(update.DiscoveryUrls));
            }

            var capsUpdate = update?.Capabilities.DecodeAsSet().SetEqualsSafe(
                registration?.Capabilities?.DecodeAsSet());

            if (!(capsUpdate ?? true))
            {
                twin.Tags.Add(nameof(ApplicationRegistration.Capabilities),
                              update?.Capabilities == null ?
                              null : JToken.FromObject(update.Capabilities));
            }

            var namesUpdate = update?.LocalizedNames.DictionaryEqualsSafe(
                registration?.LocalizedNames);

            if (!(namesUpdate ?? true))
            {
                twin.Tags.Add(nameof(ApplicationRegistration.LocalizedNames),
                              update?.LocalizedNames == null ?
                              null : JToken.FromObject(update.LocalizedNames));
            }

            var hostsUpdate = update?.HostAddresses.DecodeAsList().SequenceEqualsSafe(
                registration?.HostAddresses?.DecodeAsList());

            if (!(hostsUpdate ?? true))
            {
                twin.Tags.Add(nameof(ApplicationRegistration.HostAddresses),
                              update?.HostAddresses == null ?
                              null : JToken.FromObject(update.HostAddresses));
            }


            if (update?.ApproveAuthorityId != registration?.ApproveAuthorityId)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.ApproveAuthorityId),
                              update?.ApproveAuthorityId);
            }
            if (update?.ApproveTime != registration?.ApproveTime)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.ApproveTime),
                              update?.ApproveTime);
            }

            if (update?.CreateAuthorityId != registration?.CreateAuthorityId)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.CreateAuthorityId),
                              update?.CreateAuthorityId);
            }
            if (update?.CreateTime != registration?.CreateTime)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.CreateTime),
                              update?.CreateTime);
            }

            if (update?.UpdateAuthorityId != registration?.UpdateAuthorityId)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.UpdateAuthorityId),
                              update?.UpdateAuthorityId);
            }
            if (update?.UpdateTime != registration?.UpdateTime)
            {
                twin.Tags.Add(nameof(ApplicationRegistration.UpdateTime),
                              update?.UpdateTime);
            }

            // Recalculate identity

            var applicationUri = registration?.ApplicationUri;

            if (update?.ApplicationUri != null)
            {
                applicationUri = update?.ApplicationUri;
            }
            if (applicationUri == null)
            {
                throw new ArgumentException(nameof(ApplicationRegistration.ApplicationUri));
            }
            var siteOrSupervisorId = registration?.SiteId ?? registration?.SupervisorId;

            if (update?.SupervisorId != null || update?.SiteId != null)
            {
                siteOrSupervisorId = update?.SiteId ?? update?.SupervisorId;
            }
            var applicationType = registration?.ApplicationType;

            if (update?.ApplicationType != null)
            {
                applicationType = update?.ApplicationType;
            }

            var applicationId = ApplicationInfoModelEx.CreateApplicationId(
                siteOrSupervisorId, applicationUri, applicationType);

            twin.Tags.Remove(nameof(ApplicationId));
            twin.Tags.Add(nameof(ApplicationId), applicationId);
            twin.Id = applicationId;

            if (registration?.DeviceId != twin.Id)
            {
                twin.Etag = null; // Force creation of new identity
            }
            return(twin);
        }
        /// <summary>
        /// Create patch twin model to upload
        /// </summary>
        /// <param name="existing"></param>
        /// <param name="update"></param>
        public static DeviceTwinModel Patch(this EndpointRegistration existing,
                                            EndpointRegistration update)
        {
            var twin = BaseRegistrationEx.PatchBase(existing, update);

            // Tags

            if (update?.EndpointRegistrationUrl != null &&
                update.EndpointRegistrationUrl != existing?.EndpointRegistrationUrl)
            {
                twin.Tags.Add(nameof(EndpointRegistration.EndpointUrlLC),
                              update.EndpointUrlLC);
                twin.Tags.Add(nameof(EndpointRegistration.EndpointRegistrationUrl),
                              update.EndpointRegistrationUrl);
            }

            if (update?.SecurityLevel != existing?.SecurityLevel)
            {
                twin.Tags.Add(nameof(EndpointRegistration.SecurityLevel), update?.SecurityLevel == null ?
                              null : JToken.FromObject(update?.SecurityLevel));
            }

            if (update?.Activated != null &&
                update.Activated != existing?.Activated)
            {
                twin.Tags.Add(nameof(EndpointRegistration.Activated), update?.Activated);
            }

            var methodEqual = update?.AuthenticationMethods.DecodeAsList().SetEqualsSafe(
                existing?.AuthenticationMethods?.DecodeAsList(), JToken.DeepEquals);

            if (!(methodEqual ?? true))
            {
                twin.Tags.Add(nameof(EndpointRegistration.AuthenticationMethods),
                              update?.AuthenticationMethods == null ?
                              null : JToken.FromObject(update.AuthenticationMethods,
                                                       new JsonSerializer {
                    NullValueHandling = NullValueHandling.Ignore
                }));
            }

            // Endpoint Property

            if (update?.EndpointUrl != null &&
                update.EndpointUrl != existing?.EndpointUrl)
            {
                twin.Properties.Desired.Add(nameof(EndpointRegistration.EndpointUrl),
                                            update.EndpointUrl);
            }

            var urlsEqual = update?.AlternativeUrls.DecodeAsList().ToHashSetSafe().SetEqualsSafe(
                existing?.AlternativeUrls?.DecodeAsList());

            if (!(urlsEqual ?? true))
            {
                twin.Properties.Desired.Add(nameof(EndpointRegistration.AlternativeUrls),
                                            update?.AlternativeUrls == null ?
                                            null : JToken.FromObject(update.AlternativeUrls));
            }

            if (update?.SecurityMode != null &&
                update.SecurityMode != existing?.SecurityMode)
            {
                twin.Properties.Desired.Add(nameof(EndpointRegistration.SecurityMode),
                                            JToken.FromObject(update.SecurityMode));
            }

            if (update?.SecurityPolicy != null &&
                update?.SecurityPolicy != existing?.SecurityPolicy)
            {
                twin.Properties.Desired.Add(nameof(EndpointRegistration.SecurityPolicy),
                                            update.SecurityPolicy);
            }

            if (update?.CredentialType != existing?.CredentialType)
            {
                twin.Properties.Desired.Add(nameof(EndpointRegistration.CredentialType),
                                            update?.CredentialType == null ?
                                            null : JToken.FromObject(update?.CredentialType));
            }

            if (!JToken.DeepEquals(update?.Credential, existing?.Credential))
            {
                twin.Properties.Desired.Add(nameof(EndpointRegistration.Credential),
                                            update?.Credential);
            }

            var certEqual = update?.Certificate.DecodeAsByteArray().SequenceEqualsSafe(
                existing?.Certificate.DecodeAsByteArray());

            if (update?.Certificate != null && !(certEqual ?? true))
            {
                twin.Properties.Desired.Add(nameof(EndpointRegistration.Certificate),
                                            update?.Certificate == null ?
                                            null : JToken.FromObject(update.Certificate));
            }

            // Recalculate identity

            var reportedEndpointUrl = existing?.EndpointRegistrationUrl;

            if (update?.EndpointRegistrationUrl != null)
            {
                reportedEndpointUrl = update.EndpointRegistrationUrl;
            }
            if (reportedEndpointUrl == null)
            {
                throw new ArgumentException(nameof(EndpointRegistration.EndpointUrl));
            }
            var applicationId = existing?.ApplicationId;

            if (update?.ApplicationId != null)
            {
                applicationId = update.ApplicationId;
            }
            if (applicationId == null)
            {
                throw new ArgumentException(nameof(EndpointRegistration.ApplicationId));
            }
            var securityMode = existing?.SecurityMode;

            if (update?.SecurityMode != null)
            {
                securityMode = update.SecurityMode;
            }
            var securityPolicy = existing?.SecurityPolicy;

            if (update?.SecurityPolicy != null)
            {
                securityPolicy = update.SecurityPolicy;
            }

            twin.Id = EndpointInfoModelEx.CreateEndpointId(
                applicationId, reportedEndpointUrl, securityMode, securityPolicy);

            if (existing?.DeviceId != twin.Id)
            {
                twin.Etag = null; // Force creation of new identity
            }
            return(twin);
        }