/// <summary>
        /// Performs a targeting evaluation using the provided <see cref="TargetingContext"/> to determine if a feature should be enabled.
        /// </summary>
        /// <param name="context">The feature evaluation context.</param>
        /// <param name="targetingContext">The targeting context to use during targeting evaluation.</param>
        /// <exception cref="ArgumentNullException">Thrown if either <paramref name="context"/> or <paramref name="targetingContext"/> is null.</exception>
        /// <returns>True if the feature is enabled, false otherwise.</returns>
        public Task <bool> EvaluateAsync(FeatureFilterEvaluationContext context, ITargetingContext targetingContext)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }

            if (targetingContext == null)
            {
                throw new ArgumentNullException(nameof(targetingContext));
            }

            TargetingFilterSettings settings = context.Parameters.Get <TargetingFilterSettings>() ?? new TargetingFilterSettings();

            if (!TryValidateSettings(settings, out string paramName, out string message))
            {
                throw new ArgumentException(message, paramName);
            }

            //
            // Check if the user is being targeted directly
            if (targetingContext.UserId != null &&
                settings.Audience.Users != null &&
                settings.Audience.Users.Any(user => targetingContext.UserId.Equals(user, ComparisonType)))
            {
                return(Task.FromResult(true));
            }

            //
            // Check if the user is in a group that is being targeted
            if (targetingContext.Groups != null &&
                settings.Audience.Groups != null)
            {
                foreach (string group in targetingContext.Groups)
                {
                    GroupRollout groupRollout = settings.Audience.Groups.FirstOrDefault(g => g.Name.Equals(group, ComparisonType));

                    if (groupRollout != null)
                    {
                        string audienceContextId = $"{targetingContext.UserId}\n{context.FeatureName}\n{group}";

                        if (IsTargeted(audienceContextId, groupRollout.RolloutPercentage))
                        {
                            return(Task.FromResult(true));
                        }
                    }
                }
            }

            //
            // Check if the user is being targeted by a default rollout percentage
            string defaultContextId = $"{targetingContext.UserId}\n{context.FeatureName}";

            return(Task.FromResult(IsTargeted(defaultContextId, settings.Audience.DefaultRolloutPercentage)));
        }
        /// <summary>
        /// Performs validation of targeting settings.
        /// </summary>
        /// <param name="settings">The settings to validate.</param>
        /// <param name="paramName">The name of the invalid setting, if any.</param>
        /// <param name="reason">The reason that the setting is invalid.</param>
        /// <returns>True if the provided settings are valid. False if the provided settings are invalid.</returns>
        private bool TryValidateSettings(TargetingFilterSettings settings, out string paramName, out string reason)
        {
            const string OutOfRange = "The value is out of the accepted range.";

            const string RequiredParameter = "Value cannot be null.";

            paramName = null;

            reason = null;

            if (settings.Audience == null)
            {
                paramName = nameof(settings.Audience);

                reason = RequiredParameter;

                return(false);
            }

            if (settings.Audience.DefaultRolloutPercentage < 0 || settings.Audience.DefaultRolloutPercentage > 100)
            {
                paramName = $"{settings.Audience}.{settings.Audience.DefaultRolloutPercentage}";

                reason = OutOfRange;

                return(false);
            }

            if (settings.Audience.Groups != null)
            {
                int index = 0;

                foreach (GroupRollout groupRollout in settings.Audience.Groups)
                {
                    index++;

                    if (groupRollout.RolloutPercentage < 0 || groupRollout.RolloutPercentage > 100)
                    {
                        //
                        // Audience.Groups[1].RolloutPercentage
                        paramName = $"{settings.Audience}.{settings.Audience.Groups}[{index}].{groupRollout.RolloutPercentage}";

                        reason = OutOfRange;

                        return(false);
                    }
                }
            }

            return(true);
        }