/// <summary>Queue messages for subscription.</summary>
        /// <param name="subscription">The subscription.</param>
        /// <param name="json">        The resource.</param>
        public static void QueueMessagesForSubscription(
            r5.Subscription subscription,
            string json)
        {
            if (subscription == null)
            {
                return;
            }

            if (!_instance._subscriptionInfosDict.ContainsKey(subscription.Id))
            {
                return;
            }

            foreach (WebsocketClientInformation client in _instance._subscriptionInfosDict[subscription.Id])
            {
                // add this message to this client's queue (caller should have set it up correctly)
                client.MessageQ.Enqueue(json);

                //string clientMessage = string.Empty;

                //// determine the type of message this client wants
                //switch (client.PayloadType)
                //{
                //    case WebsocketClientInformation.WebsocketPayloadTypes.Empty:
                //    case WebsocketClientInformation.WebsocketPayloadTypes.FullResource:
                //    case WebsocketClientInformation.WebsocketPayloadTypes.IdOnly:

                //        // serialize our bundle as our message
                //        clientMessage = json;

                //        break;

                //    case WebsocketClientInformation.WebsocketPayloadTypes.R4:

                //        // send a notification
                //        clientMessage = $"ping {subscription.Id}";

                //        break;
                //}

                //// add this message to this client's queue
                //client.MessageQ.Enqueue(clientMessage);
            }
        }
        /// <summary>Converts a different FHIR version subscription to R5.</summary>
        /// <param name="s4">The subscription.</param>
        /// <returns>R5 Subscription.</returns>
        public static r5.Subscription SubscriptionToR5(r4.Subscription s4)
        {
            if (s4 == null)
            {
                return(null);
            }

            r5.Subscription s5 = new r5.Subscription()
            {
                Id            = s4.Id,
                ImplicitRules = s4.ImplicitRules,
                Language      = s4.Language,
                End           = s4.End,
                Reason        = s4.Reason,
                Endpoint      = s4.Channel.Endpoint,
                Header        = s4.Channel.Header,
                ContentType   = s4.Channel.Payload,
            };

            if (s4.Meta != null)
            {
                s5.Meta = new Meta();

                if (s4.Meta.LastUpdated != null)
                {
                    s5.Meta.LastUpdated = s4.Meta.LastUpdated;
                }

                if (s4.Meta.Profile != null)
                {
                    s5.Meta.Profile = s4.Meta.Profile;
                }

                if (s4.Meta.Security != null)
                {
                    s5.Meta.Security = new List <Coding>();
                    foreach (Coding c4 in s4.Meta.Security)
                    {
                        s5.Meta.Security.Add((Coding)c4.DeepCopy());
                    }
                }

                if (s4.Meta.Source != null)
                {
                    s5.Meta.Source = s4.Meta.Source;
                }

                if (s4.Meta.Tag != null)
                {
                    s5.Meta.Tag = new List <Coding>();
                    foreach (Coding c4 in s4.Meta.Tag)
                    {
                        s5.Meta.Tag.Add((Coding)c4.DeepCopy());
                    }
                }
            }

            switch (s4.Status)
            {
            case r4.Subscription.SubscriptionStatus.Active:
                s5.Status = r5.SubscriptionState.Active;
                break;

            case r4.Subscription.SubscriptionStatus.Error:
                s5.Status = r5.SubscriptionState.Error;
                break;

            case r4.Subscription.SubscriptionStatus.Off:
                s5.Status = r5.SubscriptionState.Off;
                break;

            case r4.Subscription.SubscriptionStatus.Requested:
                s5.Status = r5.SubscriptionState.Requested;
                break;

            default:
                Console.WriteLine($"Invalid R4 Subscription.Status: {s4.Status}");
                return(null);
            }

            switch (s4.Channel.Type)
            {
            case r4.Subscription.SubscriptionChannelType.Email:
                s5.ChannelType = new Coding()
                {
                    System = CanonicalChannelType,
                    Code   = "email",
                };
                break;

            case r4.Subscription.SubscriptionChannelType.Message:
                s5.ChannelType = new Coding()
                {
                    System = CanonicalChannelType,
                    Code   = "message",
                };
                break;

            case r4.Subscription.SubscriptionChannelType.RestHook:
                s5.ChannelType = new Coding()
                {
                    System = CanonicalChannelType,
                    Code   = "rest-hook",
                };
                break;

            case r4.Subscription.SubscriptionChannelType.Websocket:
                s5.ChannelType = new Coding()
                {
                    System = CanonicalChannelType,
                    Code   = "websocket",
                };
                break;

            default:
                Console.WriteLine($"Invalid R4 Subscription.Channel.Type: {s4.Channel.Type}");
                return(null);
            }

            if ((s4.Extension == null) ||
                (s4.Extension.Count == 0))
            {
                return(null);
            }

            foreach (Extension ext in s4.Extension)
            {
                if (ext.Url == ExtensionUrlTopic)
                {
                    if (ext.Value is FhirUri)
                    {
                        s5.Topic = new ResourceReference(((FhirUri)ext.Value).Value);
                        break;
                    }
                    else if (ext.Value is Canonical)
                    {
                        s5.Topic = new ResourceReference(((Canonical)ext.Value).Value);
                        break;
                    }
                }
            }

            if (s5.Topic == null)
            {
#pragma warning disable CA1303 // Do not pass literals as localized parameters
                Console.WriteLine($"R4 Subscription requires Extension: {ExtensionUrlTopic}");
#pragma warning restore CA1303 // Do not pass literals as localized parameters
                return(null);
            }

            if (!SubscriptionTopicManager.TryGetTopic(s5.Topic.Reference, out r5.SubscriptionTopic topic))
            {
                Console.WriteLine($"Unknown R4 SubscriptionTopic: {s5.Topic.Reference}");
                return(null);
            }

            if (topic.ResourceTrigger == null)
            {
                Console.WriteLine($"SubscriptionTopic: {topic.Url} requires resourceTrigger");
                return(null);
            }

            if ((topic.ResourceTrigger.ResourceType == null) ||
                (!topic.ResourceTrigger.ResourceType.Any()))
            {
                Console.WriteLine($"SubscriptionTopic: {topic.Url} requires resourceTrigger.resourceType");
                return(null);
            }

            if (s4.Channel.Extension != null)
            {
                foreach (Extension ext in s4.Channel.Extension)
                {
                    switch (ext.Url)
                    {
                    case ExtensionUrlHeartbeat:
                        if ((ext.Value != null) && (ext.Value is UnsignedInt))
                        {
                            s5.HeartbeatPeriod = ((UnsignedInt)ext.Value).Value;
                        }

                        break;

                    case ExtensionUrlTimeout:
                        if ((ext.Value != null) && (ext.Value is UnsignedInt))
                        {
                            s5.Timeout = ((UnsignedInt)ext.Value).Value;
                        }

                        break;

                    case ExtensionNotificationUrlLocation:
                        if ((ext.Value != null) && (ext.Value is Code))
                        {
                            // TODO: Need December 2020 R5 build
                        }

                        break;

                    case ExtensionMaxCount:
                        if ((ext.Value != null) && (ext.Value is PositiveInt))
                        {
                            // TODO: Need December 2020 R5 Build
                        }

                        break;
                    }
                }
            }

            if ((s4.Channel.PayloadElement.Extension != null) &&
                (s4.Channel.PayloadElement.Extension.Count > 0))
            {
                foreach (Extension ext in s4.Channel.PayloadElement.Extension)
                {
                    if ((ext.Url == ExtensionUrlContent) && (ext.Value is Code))
                    {
                        switch (((Code)ext.Value).Value)
                        {
                        case "empty":
                            s5.Content = r5.Subscription.SubscriptionPayloadContent.Empty;
                            break;

                        case "id-only":
                            s5.Content = r5.Subscription.SubscriptionPayloadContent.IdOnly;
                            break;

                        case "full-resource":
                            s5.Content = r5.Subscription.SubscriptionPayloadContent.FullResource;
                            break;

                        default:
                            Console.WriteLine($"Invalid R4 Subscription.Channel.Payload Content: {((Code)ext.Value).Value}");
                            return(null);
                        }
                    }
                }
            }

            if (!string.IsNullOrEmpty(s4.Criteria))
            {
                string criteria = s4.Criteria;
                string resource = topic.ResourceTrigger.ResourceType.First().Value.ToString();

                if (!criteria.StartsWith(resource, StringComparison.Ordinal))
                {
                    Console.WriteLine(
                        $"R4 Subscription Criteria: {criteria}" +
                        $" must match SubscriptionTopic.resourceTrigger.resourceType: {resource}");
                    return(null);
                }

                // remove initial resource type plus the ?
                criteria = criteria.Substring(resource.Length + 1);

                string[] components = criteria.Split('&');

                if (components.Length > 0)
                {
                    s5.FilterBy = new List <r5.Subscription.FilterByComponent>();
                }

                foreach (string component in components)
                {
                    if (TryExpandFilter(component, out r5.Subscription.FilterByComponent filter))
                    {
                        s5.FilterBy.Add(filter);
                    }
                }
            }

            return(s5);
        }
        /// <summary>Converts an R5 subscription to another FHIR version.</summary>
        /// <param name="s5">The subscription.</param>
        /// <returns>The desired version of a subscription.</returns>
        public static r4.Subscription SubscriptionFromR5(r5.Subscription s5)
        {
            if (s5 == null)
            {
                return(null);
            }

            if (s5.Topic == null)
            {
                return(null);
            }

            if (!SubscriptionTopicManager.TryGetTopic(s5.Topic.Reference, out r5.SubscriptionTopic topic))
            {
                Console.WriteLine($"Unknown R5 SubscriptionTopic: {s5.Topic.Reference}");
                return(null);
            }

            if (topic.ResourceTrigger == null)
            {
                Console.WriteLine($"SubscriptionTopic: {topic.Url} requires resourceTrigger");
                return(null);
            }

            if ((topic.ResourceTrigger.ResourceType == null) ||
                (!topic.ResourceTrigger.ResourceType.Any()))
            {
                Console.WriteLine($"SubscriptionTopic: {topic.Url} requires resourceTrigger.resourceType");
                return(null);
            }

            r4.Subscription s4 = new r4.Subscription()
            {
                Id            = s5.Id,
                ImplicitRules = s5.ImplicitRules,
                Language      = s5.Language,
                End           = s5.End,
                Reason        = s5.Reason,
                Channel       = new r4.Subscription.ChannelComponent()
                {
                    Endpoint = s5.Endpoint,
                    Header   = s5.Header,
                    Payload  = s5.ContentType,
                },
            };

            if (s5.Meta != null)
            {
                s4.Meta = new Meta();

                if (s5.Meta.LastUpdated != null)
                {
                    s4.Meta.LastUpdated = s5.Meta.LastUpdated;
                }

                if (s5.Meta.Profile != null)
                {
                    s4.Meta.Profile = s5.Meta.Profile;
                }

                if (s5.Meta.Security != null)
                {
                    s4.Meta.Security = new List <Coding>();
                    foreach (Coding c5 in s5.Meta.Security)
                    {
                        s4.Meta.Security.Add((Coding)c5.DeepCopy());
                    }
                }

                if (s5.Meta.Source != null)
                {
                    s4.Meta.Source = s5.Meta.Source;
                }

                if (s5.Meta.Tag != null)
                {
                    s4.Meta.Tag = new List <Coding>();
                    foreach (Coding c5 in s5.Meta.Tag)
                    {
                        s4.Meta.Tag.Add((Coding)c5.DeepCopy());
                    }
                }
            }

            switch (s5.Status)
            {
            case r5.SubscriptionState.Active:
                s4.Status = r4.Subscription.SubscriptionStatus.Active;
                break;

            case r5.SubscriptionState.Error:
                s4.Status = r4.Subscription.SubscriptionStatus.Error;
                break;

            case r5.SubscriptionState.Off:
                s4.Status = r4.Subscription.SubscriptionStatus.Off;
                break;

            case r5.SubscriptionState.Requested:
                s4.Status = r4.Subscription.SubscriptionStatus.Requested;
                break;
            }

            switch (s5.ChannelType.Code)
            {
            case "email":
                s4.Channel.Type = r4.Subscription.SubscriptionChannelType.Email;
                break;

            case "message":
                s4.Channel.Type = r4.Subscription.SubscriptionChannelType.Message;
                break;

            case "rest-hook":
                s4.Channel.Type = r4.Subscription.SubscriptionChannelType.RestHook;
                break;

            case "websocket":
                s4.Channel.Type = r4.Subscription.SubscriptionChannelType.Websocket;
                break;
            }

            s4.Extension.Add(new Extension()
            {
                Url   = ExtensionUrlTopic,
                Value = new FhirUri(s5.Topic.Url),
            });

            if (s5.HeartbeatPeriod != null)
            {
                s4.Channel.Extension.Add(new Extension()
                {
                    Url   = ExtensionUrlHeartbeat,
                    Value = new UnsignedInt(s5.HeartbeatPeriod),
                });
            }

            if (s5.Timeout != null)
            {
                s4.Channel.Extension.Add(new Extension()
                {
                    Url   = ExtensionUrlTimeout,
                    Value = new UnsignedInt(s5.Timeout),
                });
            }

            switch (s5.Content)
            {
            case r5.Subscription.SubscriptionPayloadContent.Empty:
                s4.Channel.PayloadElement.Extension = new List <Extension>()
                {
                    new Extension()
                    {
                        Url   = ExtensionUrlContent,
                        Value = new Code("empty"),
                    },
                };
                break;

            case r5.Subscription.SubscriptionPayloadContent.IdOnly:
                s4.Channel.PayloadElement.Extension = new List <Extension>()
                {
                    new Extension()
                    {
                        Url   = ExtensionUrlContent,
                        Value = new Code("id-only"),
                    },
                };
                break;

            case r5.Subscription.SubscriptionPayloadContent.FullResource:
                s4.Channel.PayloadElement.Extension = new List <Extension>()
                {
                    new Extension()
                    {
                        Url   = ExtensionUrlContent,
                        Value = new Code("full-resource"),
                    },
                };
                break;
            }

            if ((s5.FilterBy != null) && (s5.FilterBy.Count > 0))
            {
                StringBuilder sb = new StringBuilder();

                sb.Append($"{topic.ResourceTrigger.ResourceType.First().Value}");

                int addedCount = 0;
                foreach (r5.Subscription.FilterByComponent filter in s5.FilterBy)
                {
                    if (TryCondenseFilter(filter, out string value))
                    {
                        if (addedCount++ == 0)
                        {
                            sb.Append('?');
                        }
                        else
                        {
                            sb.Append('&');
                        }

                        sb.Append(value);
                    }
                }

                s4.Criteria = sb.ToString();
            }

            // TODO: Need December 2020 R5 build to add NotificationUrlLocation
            s4.Channel.AddExtension(ExtensionNotificationUrlLocation, new Code("full-url"));

            // TODO: Need December 2020 R5 build to add MaxCount
            s4.Channel.AddExtension(ExtensionMaxCount, new PositiveInt(10));

            return(s4);
        }