<commit_before>﻿using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace DSharpPlus.CommandsNext.Attributes
{
    /// <summary>
    /// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command
    /// </summary>
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
    public sealed class CooldownAttribute : CheckBaseAttribute
    {
        /// <summary>
        /// Gets the maximum number of uses before this command triggers a cooldown for its bucket.
        /// </summary>
        public int MaxUses { get; }

        /// <summary>
        /// Gets the time after which the cooldown is reset.
        /// </summary>
        public TimeSpan Reset { get; }

        /// <summary>
        /// Gets the type of the cooldown bucket. This determines how cooldowns are applied.
        /// </summary>
        public CooldownBucketType BucketType { get; }

        /// <summary>
        /// Gets the cooldown buckets for this command.
        /// </summary>
        private ConcurrentDictionary<string, CommandCooldownBucket> Buckets { get; }

        /// <summary>
        /// Defines a cooldown for this command. This means that users will be able to use the command a specific number of times before they have to wait to use it again.
        /// </summary>
        /// <param name="maxUses">Number of times the command can be used before triggering a cooldown.</param>
        /// <param name="resetAfter">Number of seconds after which the cooldown is reset.</param>
        /// <param name="bucketType">Type of cooldown bucket. This allows controlling whether the bucket will be cooled down per user, guild, channel, or globally.</param>
        public CooldownAttribute(int maxUses, double resetAfter, CooldownBucketType bucketType)
        {
            this.MaxUses = maxUses;
            this.Reset = TimeSpan.FromSeconds(resetAfter);
            this.BucketType = bucketType;
            this.Buckets = new ConcurrentDictionary<string, CommandCooldownBucket>();
        }

        /// <summary>
        /// Gets a cooldown bucket for given command context.
        /// </summary>
        /// <param name="ctx">Command context to get cooldown bucket for.</param>
        /// <returns>Requested cooldown bucket, or null if one wasn't present.</returns>
        public CommandCooldownBucket GetBucket(CommandContext ctx)
        {
            var bid = this.GetBucketId(ctx, out _, out _, out _);
            this.Buckets.TryGetValue(bid, out var bucket);
            return bucket;
        }

        /// <summary>
        /// Calculates the cooldown remaining for given command context.
        /// </summary>
        /// <param name="ctx">Context for which to calculate the cooldown.</param>
        /// <returns>Remaining cooldown, or zero if no cooldown is active.</returns>
        public TimeSpan GetRemainingCooldown(CommandContext ctx)
        {
            var bucket = this.GetBucket(ctx);
            if (bucket == null)
                return TimeSpan.Zero;

            if (bucket.RemainingUses > 0)
                return TimeSpan.Zero;

            return bucket.ResetsAt - DateTimeOffset.UtcNow;
        }

        /// <summary>
        /// Calculates bucket ID for given command context.
        /// </summary>
        /// <param name="ctx">Context for which to calculate bucket ID for.</param>
        /// <param name="userId">ID of the user with which this bucket is associated.</param>
        /// <param name="channelId">ID of the channel with which this bucket is associated.</param>
        /// <param name="guildId">ID of the guild with which this bucket is associated.</param>
        /// <returns>Calculated bucket ID.</returns>
        private string GetBucketId(CommandContext ctx, out ulong userId, out ulong channelId, out ulong guildId)
        {
            userId = 0ul;
            if ((this.BucketType & CooldownBucketType.User) != 0)
                userId = ctx.User.Id;

            channelId = 0ul;
            if ((this.BucketType & CooldownBucketType.Channel) != 0)
                channelId = ctx.Channel.Id;
            if ((this.BucketType & CooldownBucketType.Guild) != 0 && ctx.Guild == null)
                channelId = ctx.Channel.Id;

            guildId = 0ul;
            if (ctx.Guild != null && (this.BucketType & CooldownBucketType.Guild) != 0)
                guildId = ctx.Guild.Id;

            var bid = CommandCooldownBucket.MakeId(userId, channelId, guildId);
            return bid;
        }

        public override async Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
        {
            if (help)
                return true;

            var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld);
            if (!this.Buckets.TryGetValue(bid, out var bucket))
            {
                bucket = new CommandCooldownBucket(this.MaxUses, this.Reset, usr, chn, gld);
                this.Buckets.AddOrUpdate(bid, bucket, (k, v) => bucket);
            }

            return await bucket.DecrementUseAsync().ConfigureAwait(false);
        }
    }

    /// <summary>
    /// Defines how are command cooldowns applied.
    /// </summary>
    public enum CooldownBucketType : int
    {
        /// <summary>
        /// Denotes that the command will have its cooldown applied per-user.
        /// </summary>
        User = 1,

        /// <summary>
        /// Denotes that the command will have its cooldown applied per-channel.
        /// </summary>
        Channel = 2,

        /// <summary>
        /// Denotes that the command will have its cooldown applied per-guild. In DMs, this applies the cooldown per-channel.
        /// </summary>
        Guild = 4,

        /// <summary>
        /// Denotes that the command will have its cooldown applied globally.
        /// </summary>
        Global = 0<commit_msg>mark CooldownBucketType as Flags enum<commit_after>﻿using System;
using System.Collections.Concurrent;
using System.Globalization;
using System.Threading;
using System.Threading.Tasks;

namespace DSharpPlus.CommandsNext.Attributes
{
    /// <summary>
    /// Defines a cooldown for this command. This allows you to define how many times can users execute a specific command
    /// </summary>
    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
    public sealed class CooldownAttribute : CheckBaseAttribute
    {
        /// <summary>
        /// Gets the maximum number of uses before this command triggers a cooldown for its bucket.
        /// </summary>
        public int MaxUses { get; }

        /// <summary>
        /// Gets the time after which the cooldown is reset.
        /// </summary>
        public TimeSpan Reset { get; }

        /// <summary>
        /// Gets the type of the cooldown bucket. This determines how cooldowns are applied.
        /// </summary>
        public CooldownBucketType BucketType { get; }

        /// <summary>
        /// Gets the cooldown buckets for this command.
        /// </summary>
        private ConcurrentDictionary<string, CommandCooldownBucket> Buckets { get; }

        /// <summary>
        /// Defines a cooldown for this command. This means that users will be able to use the command a specific number of times before they have to wait to use it again.
        /// </summary>
        /// <param name="maxUses">Number of times the command can be used before triggering a cooldown.</param>
        /// <param name="resetAfter">Number of seconds after which the cooldown is reset.</param>
        /// <param name="bucketType">Type of cooldown bucket. This allows controlling whether the bucket will be cooled down per user, guild, channel, or globally.</param>
        public CooldownAttribute(int maxUses, double resetAfter, CooldownBucketType bucketType)
        {
            this.MaxUses = maxUses;
            this.Reset = TimeSpan.FromSeconds(resetAfter);
            this.BucketType = bucketType;
            this.Buckets = new ConcurrentDictionary<string, CommandCooldownBucket>();
        }

        /// <summary>
        /// Gets a cooldown bucket for given command context.
        /// </summary>
        /// <param name="ctx">Command context to get cooldown bucket for.</param>
        /// <returns>Requested cooldown bucket, or null if one wasn't present.</returns>
        public CommandCooldownBucket GetBucket(CommandContext ctx)
        {
            var bid = this.GetBucketId(ctx, out _, out _, out _);
            this.Buckets.TryGetValue(bid, out var bucket);
            return bucket;
        }

        /// <summary>
        /// Calculates the cooldown remaining for given command context.
        /// </summary>
        /// <param name="ctx">Context for which to calculate the cooldown.</param>
        /// <returns>Remaining cooldown, or zero if no cooldown is active.</returns>
        public TimeSpan GetRemainingCooldown(CommandContext ctx)
        {
            var bucket = this.GetBucket(ctx);
            if (bucket == null)
                return TimeSpan.Zero;

            if (bucket.RemainingUses > 0)
                return TimeSpan.Zero;

            return bucket.ResetsAt - DateTimeOffset.UtcNow;
        }

        /// <summary>
        /// Calculates bucket ID for given command context.
        /// </summary>
        /// <param name="ctx">Context for which to calculate bucket ID for.</param>
        /// <param name="userId">ID of the user with which this bucket is associated.</param>
        /// <param name="channelId">ID of the channel with which this bucket is associated.</param>
        /// <param name="guildId">ID of the guild with which this bucket is associated.</param>
        /// <returns>Calculated bucket ID.</returns>
        private string GetBucketId(CommandContext ctx, out ulong userId, out ulong channelId, out ulong guildId)
        {
            userId = 0ul;
            if ((this.BucketType & CooldownBucketType.User) != 0)
                userId = ctx.User.Id;

            channelId = 0ul;
            if ((this.BucketType & CooldownBucketType.Channel) != 0)
                channelId = ctx.Channel.Id;
            if ((this.BucketType & CooldownBucketType.Guild) != 0 && ctx.Guild == null)
                channelId = ctx.Channel.Id;

            guildId = 0ul;
            if (ctx.Guild != null && (this.BucketType & CooldownBucketType.Guild) != 0)
                guildId = ctx.Guild.Id;

            var bid = CommandCooldownBucket.MakeId(userId, channelId, guildId);
            return bid;
        }

        public override async Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
        {
            if (help)
                return true;

            var bid = this.GetBucketId(ctx, out var usr, out var chn, out var gld);
            if (!this.Buckets.TryGetValue(bid, out var bucket))
            {
                bucket = new CommandCooldownBucket(this.MaxUses, this.Reset, usr, chn, gld);
                this.Buckets.AddOrUpdate(bid, bucket, (k, v) => bucket);
            }

            return await bucket.DecrementUseAsync().ConfigureAwait(false);
        }
    }

    /// <summary>
    /// Defines how are command cooldowns applied.
    /// </summary>
    [Flags]
    public enum CooldownBucketType
    {
        /// <summary>
        /// Denotes that the command will have its cooldown applied per-user.
        /// </summary>
        User = 1,

        /// <summary>
        /// Denotes that the command will have its cooldown applied per-channel.
        /// </summary>
        Channel = 2,

        /// <summary>
        /// Denotes that the command will have its cooldown applied per-guild. In DMs, this applies the cooldown per-channel.
        /// </summary>
        Guild = 4,

        /// <summary>
        /// Denotes that the command will have its cooldown applied globally.
        /// </summary>
        Global = 0
{'fraction_non_alphanumeric': 0.10379390324718357, 'fraction_numerical': 0.0019880715705765406, 'mean_word_length': 1.9928111056023798, 'pattern_counts': {'":': 0, '<': 113, '<?xml version=': 0, '>': 117, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4935855', 'n_tokens_mistral': 3249, 'n_tokens_neox': 3150, 'n_words': 1053}