// <auto-generated>
{{>partial_header}}
{{#nrt}}
#nullable enable

{{/nrt}}
using System;
using System.Collections.Generic;
using System.Linq;
{{^netStandard}}
using System.Threading.Channels;
{{/netStandard}}
{{#netStandard}}
using System.Threading.Channels;
{{/netStandard}}

namespace {{packageName}}.{{clientPackage}}
{
    /// <summary>
    /// Provides a token to the api clients. Tokens will be rate limited based on the provided TimeSpan.
    /// </summary>
    /// <typeparam name="TTokenBase"></typeparam>
    {{>visibility}} class RateLimitProvider<TTokenBase> : TokenProvider<TTokenBase> where TTokenBase : TokenBase
    {
        internal Dictionary<string, Channel<TTokenBase>> AvailableTokens { get; } = new{{^net70OrLater}} Dictionary<string, Channel<TTokenBase>>{{/net70OrLater}}();

        /// <summary>
        /// Instantiates a ThrottledTokenProvider. Your tokens will be rate limited based on the token's timeout.
        /// </summary>
        /// <param name="container"></param>
        public RateLimitProvider(TokenContainer<TTokenBase> container) : base(container.Tokens)
        {
            foreach(TTokenBase token in _tokens)
                token.StartTimer(token.Timeout ?? TimeSpan.FromMilliseconds(40));

            {{#lambda.copy}}
            BoundedChannelOptions options = new BoundedChannelOptions(_tokens.Length)
            {
                FullMode = BoundedChannelFullMode.DropWrite
            };

            AvailableTokens.Add(string.Empty, Channel.CreateBounded<TTokenBase>(options));
            {{/lambda.copy}}
            {{#hasApiKeyMethods}}
            if (container is TokenContainer<ApiKeyToken> apiKeyTokenContainer)
            {
                string[] headers = apiKeyTokenContainer.Tokens.Select(t => ClientUtils.ApiKeyHeaderToString(t.Header)).Distinct().ToArray();

                foreach (string header in headers)
                {
                    BoundedChannelOptions options = new BoundedChannelOptions(apiKeyTokenContainer.Tokens.Count(t => ClientUtils.ApiKeyHeaderToString(t.Header).Equals(header)))
                    {
                        FullMode = BoundedChannelFullMode.DropWrite
                    };

                    AvailableTokens.Add(header, Channel.CreateBounded<TTokenBase>(options));
                }
            }
            else
            {
                {{#lambda.indent1}}{{#lambda.pasteLine}}{{/lambda.pasteLine}}{{/lambda.indent1}}
            }
            {{/hasApiKeyMethods}}
            {{^hasApiKeyMethods}}
            {{#lambda.pasteLine}}{{/lambda.pasteLine}}
            {{/hasApiKeyMethods}}

            foreach(Channel<TTokenBase> tokens in AvailableTokens.Values)
                for (int i = 0; i < _tokens.Length; i++)
                    _tokens[i].TokenBecameAvailable += ((sender) => tokens.Writer.TryWrite((TTokenBase) sender));
        }

        internal override async System.Threading.Tasks.ValueTask<TTokenBase> GetAsync(string header = "", System.Threading.CancellationToken cancellation = default{{^netstandard20OrLater}}(System.Threading.CancellationToken){{/netstandard20OrLater}})
        {
            if (!AvailableTokens.TryGetValue(header, out Channel<TTokenBase>{{nrt?}} tokens))
                throw new KeyNotFoundException($"Could not locate a token for header '{header}'.");

            return await tokens.Reader.ReadAsync(cancellation).ConfigureAwait(false);
        }
    }
}
