{%- import "cloudevents.jinja.include" as cloudEvents %}
{%- import "eventhubs.jinja.include" as eventhub %}
{%- import "util.jinja.include" as util -%}
{{ util.CommonFileHeader() }}
{%- import "amqp.jinja.include" as amqp %}
{%- set messagegroups = root.messagegroups %}
{%- set uses_cloudevents_message = (root | exists("envelope","CloudEvents/1.0")) %}
{%- set uses_plain_amqp_message = (root | exists("protocol","AMQP/1.0")) %}
{%- set uses_amqp_endpoint = amqp.uses_amqp_endpoint(root) %}
// This code was generated by the xRegistry tool.
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.

#nullable enable

using Azure;
using Azure.Core;
using Azure.Messaging.EventHubs;
using Azure.Messaging.EventHubs.Processor;
using Azure.Storage.Blobs;
using Microsoft.Extensions.Logging;
{%- if uses_cloudevents_message %}
{{ eventhub.CloudEventsUsings() }}
{%- endif %}

namespace {{ project_name | pascal }}
{
    {% for messagegroupid, messagegroup in messagegroups.items() -%}
    {%- set uses_cloudevents_message = (messagegroup | exists("envelope","CloudEvents/1.0")) %}
    {%- set uses_plain_amqp_message = (messagegroup | existswithout( "binding", "amqp", "format", "cloudevents" )) %}
    {%- set groupname = messagegroupid  | pascal -%}
    {%- set class_name = ( groupname | strip_namespace )+"EventDispatcher" %}
    namespace {{ groupname | pascal  }} 
    {
        public class {{ class_name }} : global::{{ project_name | pascal }}.Tools.DispatcherBase
        {
            {%- if (messagegroup | exists("envelope","CloudEvents/1.0")) %}
            
            {%- endif %}
            {%- for messageid, message in messagegroup.messages.items() -%}
            {%- set messagename = messageid | strip_namespace | pascal -%}
            {%- set message_body_type = util.body_type(data_project_name, root, message) -%}
            {%- if message.description %}   
            /// <summary>
            /// {{ message.description }}
            /// </summary>
            {%- endif -%}
            {%- set isCloudEvent = cloudEvents.isCloudEvent(message) %}
            {%- set is_amqp = amqp.is_amqp(message) %}
            public event Func<EventData, {%if isCloudEvent%}CloudEvent?, {%endif%}{{message_body_type}}?, Task>? {{ messagename | strip_namespace }}Async;
            {%- endfor %}
            public event Func<EventData, Task>? UnhandledMessageAsync; 

            public {{ class_name }}(ILoggerFactory? loggerFactory = null) : base(loggerFactory?.CreateLogger<{{ class_name }}>() ?? new LoggerFactory().CreateLogger<{{ class_name }}>())
            {
            }

            internal override async Task<bool> ProcessEventAsync(ProcessEventArgs eventArgs)
            {
                _logger.LogInformation($"Processing event from partition {eventArgs.Partition.PartitionId}");
                {%- if uses_cloudevents_message %}
                if (IsCloudEvent(eventArgs.Data))
                {
                    var cloudEvent = CloudEventFromEventData(eventArgs.Data);
                    if (await DispatchCloudEventAsync(eventArgs.Data, cloudEvent))
                    {
                        return true;
                    }
                }
                {%- else %}
                var eventData = eventArgs.Data;
                if (eventData != null)
                {
                    if ( await DispatchEventDataAsync(eventData))
                    {
                        return true;
                    }
                }
                {%- endif %}
                if (UnhandledMessageAsync != null)
                {
                    await UnhandledMessageAsync(eventArgs.Data);
                }   
                return false;
            }
            
            {%- if uses_cloudevents_message %}        
            protected virtual async Task<bool> DispatchCloudEventAsync(EventData eventData, CloudEvent cloudEvent)
            {
                var cloudEventType = cloudEvent.Type;
                _logger.LogInformation($"Dispatching CloudEvent of type {cloudEventType}");
                switch (cloudEventType)
                {
                    {% for messageid, message in messagegroup.messages.items() if cloudEvents.isCloudEvent(message) -%}
                    {%- set messagename = messageid | pascal %}
                    {%- set message_body_type = util.body_type(data_project_name, root, message) -%}
                    {%- if "type" in message.envelopemetadata and "value" in message.envelopemetadata["type"] -%}
                    case "{{ message.envelopemetadata["type"]["value"] }}":
                    {%- else -%}
                    case "{{ messageid }}":
                    {%- endif %}
                        if ({{ messagename | strip_namespace }}Async != null)
                        {
                            var tasks = new List<Task>();
                            foreach (var handler in {{ messagename | strip_namespace }}Async.GetInvocationList())
                            {
                                if ( handler == null ) continue;
                                var t = (Task?)(handler.DynamicInvoke(
                                    eventData,
                                    cloudEvent,
                                    {%- if message_body_type != "byte[]" -%}
                                    {{ message_body_type }}.FromData(cloudEvent.Data, cloudEvent.DataContentType)
                                    {%- else -%}
                                    cloudEvent.Data is BinaryData binaryData ? binaryData.ToArray() : null
                                    {%- endif %}));
                                if (t != null)
                                {
                                    tasks.Add(t);
                                }
                            }
                            await Task.WhenAll(tasks);
                        }
                        return true;
                    {%- endfor %}
                    default:
                        return false;
                }
            }
            {%- elif uses_plain_amqp_message %}
            protected virtual async Task<bool> DispatchEventDataAsync(EventData eventData)
            {
                var messageSubject = eventData.GetRawAmqpMessage().Properties.Subject;
                _logger.LogInformation($"Dispatching EventData with subject {messageSubject}");
                switch (messageSubject)
                {
                {% for messageid, message in messagegroup.messages.items() if amqp.is_amqp(message) -%}
                {%- set messagename = messageid | pascal %}
                {%- set message_body_type = util.body_type(data_project_name, root, message) %}
                    case "{{ messageid }}":
                        if ({{ messagename | strip_namespace }}Async != null)
                        {
                            var tasks = new List<Task>();
                            foreach (var handler in {{ messagename | strip_namespace }}Async.GetInvocationList())
                            {
                                if ( handler == null ) continue;
                                var t = (Task?)(handler.DynamicInvoke(
                                    eventData,
                                    {%- if message_body_type != "byte[]" -%}{{ message_body_type }}.FromData(eventData, eventData.ContentType)
                                    {%- else -%}
                                    eventData.Body.ToArray()
                                    {%- endif %}));
                                if (t != null)
                                {
                                    tasks.Add(t);
                                }
                            }
                            await Task.WhenAll(tasks);
                        }
                        return true;
                {%- endfor %}
                    default:
                        return false;
                }
            }
            {%- endif %}

            
            public EventProcessorClient CreateEventProcessorClient(BlobContainerClient checkpointBlobClient, AzureNamedKeyCredential credential, string fullyQualifiedNamespace, string eventHubName, string consumerGroupName) 
            {       
                var eventHubProcessorClient = new EventProcessorClient(checkpointBlobClient, consumerGroupName, fullyQualifiedNamespace, eventHubName, credential);
                eventHubProcessorClient.Attach(this);
                return eventHubProcessorClient;
            }

            public EventProcessorClient CreateEventProcessorClient(BlobContainerClient checkpointBlobClient, TokenCredential credential, string fullyQualifiedNamespace, string eventHubName, string consumerGroupName) 
            {       
                var eventHubProcessorClient = new EventProcessorClient(checkpointBlobClient, consumerGroupName, fullyQualifiedNamespace, eventHubName, credential);
                eventHubProcessorClient.Attach(this);
                return eventHubProcessorClient;
            }

            public EventProcessorClient CreateEventProcessorClient(BlobContainerClient checkpointBlobClient, AzureSasCredential credential, string fullyQualifiedNamespace, string eventHubName, string consumerGroupName) 
            {       
                var eventHubProcessorClient = new EventProcessorClient(checkpointBlobClient, consumerGroupName, fullyQualifiedNamespace, eventHubName, credential);
                eventHubProcessorClient.Attach(this);
                return eventHubProcessorClient;
            }

            public EventProcessorClient CreateEventProcessorClient(BlobContainerClient checkpointBlobClient, string connectionString, string consumerGroupName)
            {
                var eventHubProcessorClient = new EventProcessorClient(checkpointBlobClient, consumerGroupName, connectionString);
                eventHubProcessorClient.Attach(this);
                return eventHubProcessorClient;
            }
        }
    }

    {% endfor %}    
    
    {% set dispatcherBase = "global::"+( project_name | pascal )+".Tools.DispatcherBase" %}
    public static class {{ project_name | pascal | strip_dots }}EventProcessorClientExtensions
    {
         private static readonly Dictionary<EventProcessorClient, List<{{ dispatcherBase }}>> Dispatchers =
            new Dictionary<EventProcessorClient, List<{{ dispatcherBase }}>>();

        public static void Attach(this EventProcessorClient client, {{ dispatcherBase }} dispatcher)
        {
            if (!Dispatchers.ContainsKey(client))
            {
                Dispatchers[client] = new List<{{ dispatcherBase }}>();
                client.ProcessEventAsync += async (eventArgs) => await ProcessEventAsync(client, eventArgs);
                client.ProcessErrorAsync += async (errorArgs) => await ProcessErrorAsync(client, errorArgs);
            }
            Dispatchers[client].Add(dispatcher);
        }

        public static void Detach(this EventProcessorClient client, {{ dispatcherBase }} dispatcher)
        {
            if (Dispatchers.ContainsKey(client))
            {
                Dispatchers[client].Remove(dispatcher);
                if (Dispatchers[client].Count == 0)
                {
                    Dispatchers.Remove(client);
                }
            }
        }

        private static async Task ProcessEventAsync(EventProcessorClient client, ProcessEventArgs eventArgs)
        {
            foreach (var dispatcher in Dispatchers[client])
            {
                if ( await dispatcher.ProcessEventAsync(eventArgs) )
                {
                    return;
                }
            }
        }

        private static async Task ProcessErrorAsync(EventProcessorClient client, ProcessErrorEventArgs errorArgs)
        {
            foreach (var dispatcher in Dispatchers[client])
            {
                await dispatcher.ProcessErrorAsync(errorArgs);
            }
        }        
    }

    {%- if root.endpoints -%} 
    {%- for endpointid, endpoint in root.endpoints.items() -%}
    {%- set endpointname = endpointid | default(endpointid) | pascal -%}
    {%- if endpoint.usage and "consumer" in endpoint.usage -%}
    {%- set protocol = endpoint.protocol | lower -%}
    {%- if protocol.startswith("amqp") -%}
    {%- set options = endpoint.protocoloptions -%}
    {%- set endpoints = endpoint.endpoints -%}
    {%- set messagegroupuris = endpoint.messagegroups %}
    
    {%- macro MessageGroupArgs() -%}
    {%- for uri in messagegroupuris %}
    {%- set messagegroup = schema_object( root, uri ) -%}
    {%- if messagegroup %}
    {%- set messagegroupid = uri.split('/')[-1] -%}
    {%- set messagegroupname = messagegroupid | pascal -%}
    global::{{ messagegroupname | concat_namespace(project_name) | pascal }}.{{ messagegroupname | strip_namespace }}EventDispatcher? {{ messagegroupname | strip_namespace | camel }}Dispatcher = null
    {%- if not loop.last -%}, {%- endif -%}
    {%- endif %}
    {%- endfor %}
    {%- endmacro -%}    
    
    {%- macro MessageGroupHooks(arg) -%}
    Task.WhenAll(
    {%- for uri in messagegroupuris %}
    {%- set messagegroup = schema_object( root, uri ) -%}
    {% if messagegroup %}
    {%- set messagegroupid = uri.split('/')[-1] -%}
    {%- set messagegroupname = messagegroupid | pascal -%}
    {{- messagegroupname | strip_namespace | camel }}Dispatcher != null?{{- messagegroupname | strip_namespace | camel }}Dispatcher.ProcessEventAsync({{ arg }}):Task.CompletedTask
    {%- if not loop.last -%}, {%- endif -%}
    {%- endif %}
    {%- endfor -%}
    )
    {%- endmacro -%}    

    {%- macro createforbody(class_name, endpoints, options) -%}
        {%- set ehnamespace = geturlhost(endpoints[0].uri) -%}
        {%- set ehtopic = geturlpath(endpoints[0].uri)[1:] -%}
        fullyQualifiedNamespace = fullyQualifiedNamespace ?? "{{ ehnamespace }}";
        {%- if options and 'node' in options %}
        eventHubName = eventHubName ?? "{{ options['node'] }}";
        {%- elif ehtopic %}
        eventHubName = eventHubName ?? "{{ ehtopic }}";
        {%- else %}
        if ( eventHubName == null )
        {
            throw new ArgumentNullException(nameof(eventHubName));
        }
        {%- endif %}
        {%- if options and 'consumer_group' in options %}
        consumerGroupName = consumerGroupName ?? "{{ options['consumer_group'] }}";
        {%- else %}
        consumerGroupName = consumerGroupName ?? "$Default";
        {%- endif %}
        var eventHubProcessorClient = new EventProcessorClient(checkpointBlobClient, consumerGroupName, fullyQualifiedNamespace, eventHubName, credential);
        eventHubProcessorClient.ProcessEventAsync += (eventArgs) => {{ MessageGroupHooks("eventArgs") }};
        return eventHubProcessorClient;
    {%- endmacro %}
    

    namespace {{ endpointname | concat_namespace(project_name)| pascal  }}
    {
        {%- set class_name = ( endpointname | strip_namespace )+"EventProcessorFactory" %}
        public class {{ class_name }} 
        {
            public static EventProcessorClient CreateEventProcessorClientFor{{ endpointid | pascal | strip_namespace }}(BlobContainerClient checkpointBlobClient, TokenCredential credential, string? fullyQualifiedNamespace = null, string? eventHubName = null, string? consumerGroupName = null, {{ MessageGroupArgs() }}) 
            {   
                {{ createforbody(class_name, endpoints, options) | indent(8) }}
            }

            public static EventProcessorClient CreateEventProcessorClientFor{{ endpointid | pascal | strip_namespace }}(BlobContainerClient checkpointBlobClient, AzureNamedKeyCredential credential, string? fullyQualifiedNamespace = null, string? eventHubName = null, string? consumerGroupName = null, {{ MessageGroupArgs() }}) 
            {       
                {{ createforbody(class_name, endpoints, options) | indent(8) }}
            }

            public static EventProcessorClient CreateEventProcessorClientFor{{ endpointid | pascal | strip_namespace }}(BlobContainerClient checkpointBlobClient, AzureSasCredential credential, string? fullyQualifiedNamespace = null, string? eventHubName = null, string? consumerGroupName = null, {{ MessageGroupArgs() }}) 
            {       
                {{ createforbody(class_name, endpoints, options) | indent(8) }}
            }
        }
    }

        {%- endif -%}
        {%- endif -%}
        {%- endfor -%}
        {% endif %}
}
