/// <summary>
        /// Get a Variation of an Experiment for a user to be allocated into.
        /// </summary>
        /// <param name = "experiment" > The Experiment the user will be bucketed into.</param>
        /// <param name = "userId" > The userId of the user.
        /// <param name = "filteredAttributes" > The user's attributes. This should be filtered to just attributes in the Datafile.</param>
        /// <returns>The Variation the user is allocated into.</returns>
        public virtual Variation GetVariation(Experiment experiment, string userId, ProjectConfig config, UserAttributes filteredAttributes)
        {
            if (!ExperimentUtils.IsExperimentActive(experiment, Logger))
            {
                return(null);
            }

            // check if a forced variation is set
            var forcedVariation = GetForcedVariation(experiment.Key, userId, config);

            if (forcedVariation != null)
            {
                return(forcedVariation);
            }

            var variation = GetWhitelistedVariation(experiment, userId);

            if (variation != null)
            {
                return(variation);
            }
            UserProfile userProfile = null;

            if (UserProfileService != null)
            {
                try
                {
                    Dictionary <string, object> userProfileMap = UserProfileService.Lookup(userId);
                    if (userProfileMap != null && UserProfileUtil.IsValidUserProfileMap(userProfileMap))
                    {
                        userProfile = UserProfileUtil.ConvertMapToUserProfile(userProfileMap);
                        variation   = GetStoredVariation(experiment, userProfile, config);
                        if (variation != null)
                        {
                            return(variation);
                        }
                    }
                    else if (userProfileMap == null)
                    {
                        Logger.Log(LogLevel.INFO, "We were unable to get a user profile map from the UserProfileService.");
                    }
                    else
                    {
                        Logger.Log(LogLevel.ERROR, "The UserProfileService returned an invalid map.");
                    }
                }
                catch (Exception exception)
                {
                    Logger.Log(LogLevel.ERROR, exception.Message);
                    ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message));
                }
            }

            if (ExperimentUtils.IsUserInExperiment(config, experiment, filteredAttributes, Logger))
            {
                // Get Bucketing ID from user attributes.
                string bucketingId = GetBucketingId(userId, filteredAttributes);

                variation = Bucketer.Bucket(config, experiment, bucketingId, userId);

                if (variation != null && variation.Key != null)
                {
                    if (UserProfileService != null)
                    {
                        var bucketerUserProfile = userProfile ?? new UserProfile(userId, new Dictionary <string, Decision>());
                        SaveVariation(experiment, variation, bucketerUserProfile);
                    }
                    else
                    {
                        Logger.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null.");
                    }
                }

                return(variation);
            }
            Logger.Log(LogLevel.INFO, $"User \"{userId}\" does not meet conditions to be in experiment \"{experiment.Key}\".");

            return(null);
        }
        /// <summary>
        /// Get a Variation of an Experiment for a user to be allocated into.
        /// </summary>
        /// <param name = "experiment" > The Experiment the user will be bucketed into.</param>
        /// <param name = "user" > optimizely user context.
        /// <param name = "config" > Project Config.</param>
        /// <param name = "options" >An array of decision options.</param>
        /// <returns>The Variation the user is allocated into.</returns>
        public virtual Result <Variation> GetVariation(Experiment experiment,
                                                       OptimizelyUserContext user,
                                                       ProjectConfig config,
                                                       OptimizelyDecideOption[] options)
        {
            var reasons = new DecisionReasons();
            var userId  = user.GetUserId();

            if (!ExperimentUtils.IsExperimentActive(experiment, Logger))
            {
                return(Result <Variation> .NullResult(reasons));
            }

            // check if a forced variation is set
            var decisionVariationResult = GetForcedVariation(experiment.Key, userId, config);

            reasons += decisionVariationResult.DecisionReasons;
            var variation = decisionVariationResult.ResultObject;

            if (variation == null)
            {
                decisionVariationResult = GetWhitelistedVariation(experiment, user.GetUserId());
                reasons += decisionVariationResult.DecisionReasons;

                variation = decisionVariationResult.ResultObject;
            }

            if (variation != null)
            {
                decisionVariationResult.SetReasons(reasons);
                return(decisionVariationResult);
            }

            // fetch the user profile map from the user profile service
            var ignoreUPS = Array.Exists(options, option => option == OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE);

            UserProfile userProfile = null;

            if (!ignoreUPS && UserProfileService != null)
            {
                try
                {
                    Dictionary <string, object> userProfileMap = UserProfileService.Lookup(user.GetUserId());
                    if (userProfileMap != null && UserProfileUtil.IsValidUserProfileMap(userProfileMap))
                    {
                        userProfile             = UserProfileUtil.ConvertMapToUserProfile(userProfileMap);
                        decisionVariationResult = GetStoredVariation(experiment, userProfile, config);
                        reasons += decisionVariationResult.DecisionReasons;
                        if (decisionVariationResult.ResultObject != null)
                        {
                            return(decisionVariationResult.SetReasons(reasons));
                        }
                    }
                    else if (userProfileMap == null)
                    {
                        Logger.Log(LogLevel.INFO, reasons.AddInfo("We were unable to get a user profile map from the UserProfileService."));
                    }
                    else
                    {
                        Logger.Log(LogLevel.ERROR, reasons.AddInfo("The UserProfileService returned an invalid map."));
                    }
                }
                catch (Exception exception)
                {
                    Logger.Log(LogLevel.ERROR, reasons.AddInfo(exception.Message));
                    ErrorHandler.HandleError(new Exceptions.OptimizelyRuntimeException(exception.Message));
                }
            }
            var filteredAttributes = user.GetAttributes();
            var doesUserMeetAudienceConditionsResult = ExperimentUtils.DoesUserMeetAudienceConditions(config, experiment, filteredAttributes, LOGGING_KEY_TYPE_EXPERIMENT, experiment.Key, Logger);

            reasons += doesUserMeetAudienceConditionsResult.DecisionReasons;
            if (doesUserMeetAudienceConditionsResult.ResultObject)
            {
                // Get Bucketing ID from user attributes.
                var bucketingIdResult = GetBucketingId(userId, filteredAttributes);
                reasons += bucketingIdResult.DecisionReasons;

                decisionVariationResult = Bucketer.Bucket(config, experiment, bucketingIdResult.ResultObject, userId);
                reasons += decisionVariationResult.DecisionReasons;

                if (decisionVariationResult.ResultObject?.Key != null)
                {
                    if (UserProfileService != null && !ignoreUPS)
                    {
                        var bucketerUserProfile = userProfile ?? new UserProfile(userId, new Dictionary <string, Decision>());
                        SaveVariation(experiment, decisionVariationResult.ResultObject, bucketerUserProfile);
                    }
                    else
                    {
                        Logger.Log(LogLevel.INFO, "This decision will not be saved since the UserProfileService is null.");
                    }
                }

                return(decisionVariationResult.SetReasons(reasons));
            }
            Logger.Log(LogLevel.INFO, reasons.AddInfo($"User \"{user.GetUserId()}\" does not meet conditions to be in experiment \"{experiment.Key}\"."));

            return(Result <Variation> .NullResult(reasons));
        }