csharp/42skillz/Diverse/Diverse/Fuzzer.cs

Fuzzer.cs
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Diverse.Address;
using Diverse.Address.Geography;
using Diverse.Collections;
using Diverse.DateTimes;
using Diverse.Numbers;
using Diverse.Strings;

namespace Diverse
{
    /// 
    /// Allows to generate lots of combination of things.  are very useful to detect hard coded values in our implementations.
    /// Note: you can instantiate another Deterministic Fuzzer by providing it the Seed you want to reuse.
    /// 
    public clast Fuzzer : IFuzz
    {
        private readonly Random _internalRandom;

        private readonly IFuzzStrings _stringFuzzer;
        private readonly IFuzzLorem _loremFuzzer;
        private readonly IFuzzNumbers _numberFuzzer;
        private readonly IFuzzAddress _addressFuzzer;
        private readonly IFuzzPersons _personFuzzer;
        private readonly IFuzzDatesAndTime _dateTimeFuzzer;
        private readonly IFuzzTypes _typeFuzzer;
        private readonly IFuzzGuid _guidFuzzer;
        private readonly IFuzzFromCollections _collectionFuzzer;
        

        // For NoDuplication mode
        private const int MaxFailingAttemptsForNoDuplicationDefaultValue = 100;
        private const int MaxRangeSizeAllowedForMemoizationDefaultValue = 1000000;
        private readonly Memoizer _memoizer = new Memoizer();
        private IFuzz _sideEffectFreeFuzzer;
        

        /// 
        /// internal Fuzzer instance to be used by the various lambdas
        /// related to the NoDuplication mode (i.e. when the option is
        /// set to true).
        ///
        /// Ironically, the NoDuplication mode of this Fuzzer needs
        /// to use another fuzzer instance for the Lastchance mode (i.e. when the
        /// MaxFailingAttemptsForNoDuplicationDefaultValue has been
        /// reached).
        ///
        /// E.g.: if you call the  method on a Fuzzer with
        /// NoDuplication set to true in a situation where the
        /// 
        /// was not enough to find another original value, the lastChance lambda
        /// of the  method will be called.
        ///
        /// In that case, since the last chance lambda of the 
        /// method is using the  method,
        /// we need to avoid  by using
        /// a  instance in that specific case
        /// (in all lastChance lambdas actually).
        /// 
        private IFuzz SideEffectFreeFuzzerWithDuplicationAllowed => _sideEffectFreeFuzzer ?? (_sideEffectFreeFuzzer = new Fuzzer(this.Seed, noDuplication: false));

        /// 
        /// Gets or sets the max number of attempts the Fuzzer should make in order to generate
        /// a not already provided value when  mode
        /// is enabled (via constructor).
        /// 
        public int MaxFailingAttemptsForNoDuplication { get; set; } = MaxFailingAttemptsForNoDuplicationDefaultValue;

        /// 
        /// Gets or sets the maximum number of entries to be memoized if
        ///  mode is enabled (via constructor).
        /// 
        public ulong MaxRangeSizeAllowedForMemoization { get; set; } = MaxRangeSizeAllowedForMemoizationDefaultValue;

        /// 
        /// Generates a DefaultSeed. Important to keep a trace of the used seed so that we can reproduce a failing situation with  involved.
        /// 
        public int Seed { get; }

        /// 
        /// Gets the name of this  instance.
        /// 
        public string Name { get; }

        /// 
        /// Gets of sets a value indicating whether the  should avoid providing twice the same value or not.
        /// 
        public bool NoDuplication { get; set; }

        /// 
        /// Gets the Random instance to be used when we want to create a new extension method for the .
        /// Beware: do not use this property if you do not want duplication (use all existing methods of  that can handle no duplication mode, like ).
        /// The use of explicit interface implementation for this property is made on purpose in order to hide this internal mechanic details from the Fuzzer end-user code.
        /// 
        Random IFuzz.Random => _internalRandom;

        /// 
        /// Gives easy access to the  explicit implementation.
        /// 
        private Random InternalRandom => ((IFuzz) this).Random;

        /// 
        /// Sets the way you want to log or receive what the  has to say about every generated seeds used for every fuzzer instance and test.
        /// 
        public static Action Log { get; set; }

        /// 
        /// Instantiates a .
        /// 
        /// The seed if you want to reuse in order to reproduce the very same conditions of another (failing) test.
        /// The name you want to specify for this  instance (useful for debuging purpose).
        /// true if you do not want the Fuzzer to provide you twice the same result for every fuzzing method type, false otherwise.
        public Fuzzer(int? seed = null, string name = null, bool? noDuplication = false)
        {
            var seedWasProvided = seed.HasValue;

            seed = seed ??
                   new Random().Next(); // the seed is not specified? pick a random one for this Fuzzer instance.
            Seed = seed.Value;

            _internalRandom = new Random(seed.Value);

            name = name ?? GenerateFuzzerName();
            Name = name;

            noDuplication = noDuplication ?? false;
            NoDuplication = noDuplication.Value;

            LogSeedAndTestInformations(seed.Value, seedWasProvided, name);

            // Instantiates implementation types for the various Fuzzer
            _loremFuzzer = new LoremFuzzer(this);
            _stringFuzzer = new StringFuzzer(this);
            _numberFuzzer = new NumberFuzzer(this);
            _addressFuzzer = new AddressFuzzer(this);
            _personFuzzer = new PersonFuzzer(this);
            _dateTimeFuzzer = new DateTimeFuzzer(this);
            _typeFuzzer = new TypeFuzzer(this);
            _guidFuzzer = new GuidFuzzer(this);
            _collectionFuzzer = new CollectionFuzzer(this);
        }

        #region Core

        /// 
        /// Returns a  instance that you can use to generate always different values from now
        /// (i.e. values that will be generated by this very specific  instance only).
        /// In other word, a  instance that will never return twice the same value (whatever the method called).
        /// 
        /// A  instance that will never return twice the same value (whatever the method called).
        public IFuzz GenerateNoDuplicationFuzzer()
        {
            return new Fuzzer(Seed, noDuplication: true);
        }

        private static void LogSeedAndTestInformations(int seed, bool seedWasProvided, string fuzzerName)
        {
            var testName = FindTheNameOfTheTestInvolved();

            if (Log == null)
            {
                throw new FuzzerException(BuildErrorMessageForMissingLogRegistration());
            }

            Log(
                $"----------------------------------------------------------------------------------------------------------------------");
            if (seedWasProvided)
            {
                Log($"--- Fuzzer (\"{fuzzerName}\") instantiated from a provided seed ({seed})");
                Log($"--- from the test: {testName}()");
            }
            else
            {
                Log($"--- Fuzzer (\"{fuzzerName}\") instantiated with the seed ({seed})");
                Log($"--- from the test: {testName}()");
                Log(
                    $"--- Note: you can instantiate another Fuzzer with that very same seed in order to reproduce the exact test conditions");
            }

            Log(
                $"----------------------------------------------------------------------------------------------------------------------");
        }

        private static string BuildErrorMessageForMissingLogRegistration()
        {
            var message =
                @"You must register (at least once) a log handler in your Test project for the Diverse library to be able to publish all the seeds used for every test (which is a prerequisite for deterministic test runs afterward).
The only thing you have to do is to set a value for the static " +
                $"{nameof(Log)} property of the {nameof(Fuzzer)} type." + @"

The best location for this call is within a unique AllFixturesSetup clast.
e.g.: with NUnit:

using NUnit.Framework;

namespace YouNameSpaceHere.Tests
{
    [SetUpFixture]
    public clast AllTestFixtures
    {
        [OneTimeSetUp]
        public void Init()
        {
            " + $"{nameof(Fuzzer)}.{nameof(Log)} = TestContext.WriteLine;" + @"
        }
    }
}

";
            return message;
        }

        private static string FindTheNameOfTheTestInvolved()
        {
            var testName = "(not found)";
            try
            {
                var stackTrace = new StackTrace();

                var testMethod = stackTrace.GetFrames().Select(sf => sf.GetMethod()).First(IsATestMethod);

                testName = $"{testMethod.DeclaringType.Name}.{testMethod.Name}";
            }
            catch
            {
            }

            return testName;
        }

        private static bool IsATestMethod(MethodBase mb)
        {
            var attributeTypes = mb.CustomAttributes.Select(c => c.AttributeType);

            var hasACustomAttributeOfTypeTest = attributeTypes.Any(y =>
                (y.Name == "TestAttribute" || y.Name == "TestCaseAttribute" || y.Name == "Fact"));

            if (hasACustomAttributeOfTypeTest)
            {
                return true;
            }

            return hasACustomAttributeOfTypeTest;
        }

        private string GenerateFuzzerName(bool upperCased = true)
        {
            // We are explicitly not using the Random field here to prevent from doing side effects on the deterministic fuzzer instances (depending on whether or not we specified a name)
            var index = new Random().Next(0, 1500);

            return $"fuzzer{index}";
        }

        /// 
        /// Flips a coin.
        /// 
        /// True if Heads; False otherwise (i.e. Tails).
        public bool HeadsOrTails()
        {
            return InternalRandom.Next(0, 2) == 1;
        }

        /// 
        /// Methods to be used when  is set to true
        /// for any fuzzing method of this  instance.
        /// It encapsulates the logic of various attempts and retries before
        /// falling back to a very specific 
        /// lambda astociated to the considered fuzzing method.
        /// 
        /// Type to be fuzzed/generated
        /// 
        ///     The current Method calling us (e.g.: ).
        ///     Used for memoization purpose.
        /// 
        /// 
        ///     A hash for the current method call arguments. Used for memoization purpose.
        /// 
        /// 
        ///     The maximum number of calls to the 
        ///     we should try before we fall-back and call the
        ///      lambda.
        /// 
        /// 
        ///     The function to use in order to generate the thing(s) we want.
        ///     It should be the same function that the one we call for the cases
        ///     where  is set to false.
        /// 
        /// 
        ///     The function to use in order to generate the thing(s) we want when
        ///     all the  attempts have failed.
        ///     To do our job, we receive:
        ///         - A  instance with all the previously
        ///           generated values
        /// 
        ///         - A side-effect free  instance to use if needed.
        ///           (one should not use the current instance of 
        ///           to do the job since it may have side-effects
        ///           and lead to )).
        /// 
        /// The thing(s) we want to generate.
        private T GenerateWithoutDuplication(MethodBase currentMethod, int argumentsHashCode,
                            int maxFailingAttemptsBeforeLastChanceFunctionIsCalled, 
                            Func standardGenerationFunction,
                            Func lastChanceGenerationFunction = null)
        {
            var memoizerKey = new MemoizerKey(currentMethod, argumentsHashCode);

            var maybe = TryGetNonAlreadyProvidedValuesWithRegularGenerationFunction(memoizerKey, out var alreadyProvidedValues, standardGenerationFunction, maxFailingAttemptsBeforeLastChanceFunctionIsCalled);

            if (!maybe.HasItem)
            {
                if (lastChanceGenerationFunction != null)
                {
                    // last attempt, we randomly pick the missing bits from the memoizer
                    maybe = lastChanceGenerationFunction(SideEffectFreeFuzzerWithDuplicationAllowed, _memoizer.GetAlreadyProvidedValues(memoizerKey));
                }

                if (!maybe.HasItem)
                {
                    throw new DuplicationException(typeof(T), maxFailingAttemptsBeforeLastChanceFunctionIsCalled, alreadyProvidedValues);
                }
            }

            alreadyProvidedValues.Add(maybe.Item);
            return maybe.Item;
        }

        private Maybe TryGetNonAlreadyProvidedValuesWithRegularGenerationFunction(MemoizerKey memoizerKey, 
                                    out SortedSet alreadyProvidedValues, 
                                    Func generationFunction, 
                                    int maxFailingAttempts)
        {
            alreadyProvidedValues = _memoizer.GetAlreadyProvidedValues(memoizerKey);

            var maybe = GenerateNotAlreadyProvidedValue(alreadyProvidedValues, maxFailingAttempts, generationFunction);

            return maybe;
        }

        private Maybe GenerateNotAlreadyProvidedValue(ISet alreadyProvidedValues, int maxAttempts, Func generationFunction)
        {
            T result = default(T);
            for (var i = 0; i < maxAttempts; i++)
            {
                result = generationFunction(SideEffectFreeFuzzerWithDuplicationAllowed);

                if (!alreadyProvidedValues.Contains(result))
                {
                    return new Maybe(result);
                }
            }

            return new Maybe();
        }

        #endregion

        #region NumberFuzzer

        /// 
        /// Generates a random integer value between a min (inclusive) and a max (exclusive) value.
        /// 
        /// The inclusive lower bound of the random number returned.
        /// The inclusive upper bound of the random number returned.
        /// An integer value generated randomly.
        public int GenerateInteger(int? minValue = null, int? maxValue = null)
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(minValue, maxValue),
                    MaxFailingAttemptsForNoDuplication,
                    standardGenerationFunction: (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.GenerateInteger(minValue, maxValue),
                    lastChanceGenerationFunction: (fuzzerWithDuplicationAllowed, alreadyProvidedSortedSet) => LastChanceToFindNotAlreadyProvidedInteger(alreadyProvidedSortedSet, minValue.Value, maxValue.Value, fuzzerWithDuplicationAllowed));
            }

            return _numberFuzzer.GenerateInteger(minValue, maxValue);
        }
 
        private static Maybe LastChanceToFindNotAlreadyProvidedInteger(SortedSet alreadyProvidedValues, int? minValue, int? maxValue, IFuzz fuzzer)
        {
            minValue = minValue ?? int.MinValue;
            maxValue = maxValue ?? int.MaxValue;

            var allPossibleValues = Enumerable.Range(minValue.Value, maxValue.Value).ToArray();

            var remainingCandidates = allPossibleValues.Except(alreadyProvidedValues.Cast()).ToArray();

            if (remainingCandidates.Any())
            {
                var pickOneFrom = fuzzer.PickOneFrom(remainingCandidates);
                return new Maybe(pickOneFrom);
            }

            return new Maybe();
        }


        /// 
        /// Generates a random positive integer value.
        /// 
        /// The inclusive upper bound of the random number returned.
        /// A positive integer value generated randomly.
        public int GeneratePositiveInteger(int? maxValue = null)
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(maxValue),
                    MaxFailingAttemptsForNoDuplication,
                    standardGenerationFunction: (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.GeneratePositiveInteger(maxValue));
            }

            return _numberFuzzer.GeneratePositiveInteger(maxValue);
        }

        /// 
        /// Generates a random decimal value.
        /// 
        /// (optional) The inclusive lower bound of the random number returned.
        /// (optional) The inclusive upper bound of the random number returned.
        /// A decimal value generated randomly.
        /// minValue is greater than maxValue.
        public decimal GenerateDecimal(decimal? minValue = null, decimal? maxValue = null)
        {
            // No need to memoize decimals here since it is very unlikely that the lib generate twice the same decimal
            return _numberFuzzer.GenerateDecimal(minValue, maxValue);
        }

        /// 
        /// Generates a random positive decimal value.
        /// 
        /// (optional) The inclusive positive lower bound of the random number returned.
        /// (optional) The inclusive positive upper bound of the random number returned.
        /// A positive decimal value generated randomly.
        public decimal GeneratePositiveDecimal(decimal? minValue = null, decimal? maxValue = null)
        {
            // No need to memoize decimals here since it is very unlikely that the lib generate twice the same decimal
            return _numberFuzzer.GeneratePositiveDecimal(minValue, maxValue);
        }

        /// 
        /// Generates a random long value.
        /// 
        /// The inclusive lower bound of the random number returned.
        /// The inclusive upper bound of the random number returned.
        /// A long value generated randomly.
        public long GenerateLong(long? minValue = null, long? maxValue = null)
        {
            if (NoDuplication)
            {
                // We will only memoize if the range is not too wide
                var uRange = NumberExtensions.ComputeRange(minValue, maxValue);

                if (uRange  fuzzerWithDuplicationAllowed.GenerateLong(minValue, maxValue),
                        lastChanceGenerationFunction: (fuzzerWithDuplicationAllowed, alreadyProvidedSortedSet) => LastChanceToFindNotAlreadyProvidedLong(ref minValue, ref maxValue, alreadyProvidedSortedSet, fuzzerWithDuplicationAllowed));
                }
            }

            return _numberFuzzer.GenerateLong(minValue, maxValue);
        }

        private static Maybe LastChanceToFindNotAlreadyProvidedLong(ref long? minValue, ref long? maxValue, SortedSet alreadyProvidedValues, IFuzz fuzzer)
        {
            minValue = minValue ?? long.MinValue;
            maxValue = maxValue ?? long.MaxValue;

            var allPossibleValues = GenerateAllPossibleOptions(minValue.Value, maxValue.Value);
            
            var remainingCandidates = allPossibleValues.Except(alreadyProvidedValues.Cast()).ToArray();
            
            if (remainingCandidates.Any())
            {
                var index = fuzzer.GenerateInteger(0, remainingCandidates.Length - 1);
                var randomRemainingNumber = remainingCandidates[index];

                return new Maybe(randomRemainingNumber);
            }

            return new Maybe();
        }

        private static SortedSet GenerateAllPossibleOptions(long min, long max)
        {
            var allPossibleOptions = new SortedSet();
            for (var i = min; i < max + 1; i++)
            {
                allPossibleOptions.Add(i);
            }

            return allPossibleOptions;
        }

        #endregion

        #region Address

        /// 
        /// Randomly generates an .
        /// 
        /// The  of the address to generate.
        /// The generated Address.
        public Address.Address GenerateAddress(Country? country = null)
        {
            return _addressFuzzer.GenerateAddress(country);
        }

        #endregion

        #region PersonFuzzer

        /// 
        /// Generates a 'Diverse' first name (i.e. from all around the world and different cultures).
        /// 
        /// The  to be used as indication (optional).
        /// A 'Diverse' first name.
        public string GenerateFirstName(Gender? gender = null)
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(gender),
                    MaxFailingAttemptsForNoDuplication, 
                    standardGenerationFunction: (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.GenerateFirstName(gender));
            }

            return _personFuzzer.GenerateFirstName(gender);
        }

        /// 
        /// Generates a 'Diverse' first name (i.e. from all around the world and different cultures).
        /// 
        /// The first name of this person.
        /// A 'Diverse' last name.
        public string GenerateLastName(string firstName)
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(firstName),
                    MaxFailingAttemptsForNoDuplication, 
                    standardGenerationFunction: (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.GenerateLastName(firstName),
                    lastChanceGenerationFunction: (fuzzerWithDuplicationAllowed, alreadyProvidedSortedSet) => LastChanceToFindLastName(firstName, alreadyProvidedSortedSet, fuzzerWithDuplicationAllowed));
            }

            return _personFuzzer.GenerateLastName(firstName);
        }

        private static Maybe LastChanceToFindLastName(string firstName, SortedSet alreadyProvidedValues, IFuzz fuzzer)
        {
            var continent = Locations.FindContinent(firstName);
            var allPossibleValues = LastNames.PerContinent[continent];

            var remainingCandidates = allPossibleValues.Except(alreadyProvidedValues.Cast()).ToArray();

            if (remainingCandidates.Any())
            {
                var lastName = fuzzer.PickOneFrom(remainingCandidates);
                return new Maybe(lastName);
            }

            return new Maybe();
        }

        /// 
        /// Generates the number of year to be astociated with a person.
        /// 
        /// The number of year to be astociated with a person.
        public int GenerateAge()
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(),
                    MaxFailingAttemptsForNoDuplication,
                    standardGenerationFunction: (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.GenerateAge(),
                    lastChanceGenerationFunction: (fuzzerWithDuplicationAllowed, alreadyProvidedSortedSet) => LastChanceToFindAge(alreadyProvidedSortedSet, 18, 97, fuzzerWithDuplicationAllowed));
            }

            return _personFuzzer.GenerateAge();
        }

        private static Maybe LastChanceToFindAge(SortedSet alreadyProvidedValues, int minAge, int maxAge, IFuzz fuzzer)
        {
            var allPossibleValues = Enumerable.Range(minAge, maxAge - minAge).ToArray();

            var remainingCandidates = allPossibleValues.Except(alreadyProvidedValues.Cast()).ToArray();

            if (remainingCandidates.Any())
            {
                var pickOneFrom = fuzzer.PickOneFrom(remainingCandidates);
                return new Maybe(pickOneFrom);
            }

            return new Maybe();
        }

        /// 
        /// Generates a 'Diverse'  (i.e. from all around the world and different cultures). 
        /// 
        /// The (optional)  of this 
        /// A 'Diverse'  instance.
        public Person GeneratePerson(Gender? gender = null)
        {
            return _personFuzzer.GeneratePerson(gender);
        }

        /// 
        /// Generates a random Email.
        /// 
        /// The (optional) first name for this Email
        /// The (option) last name for this Email.
        /// A random Email.
        public string GenerateEMail(string firstName = null, string lastName = null)
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(firstName, lastName),
                    MaxFailingAttemptsForNoDuplication,
                    standardGenerationFunction: (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.GenerateEMail(firstName, lastName));
            }

            return _personFuzzer.GenerateEMail(firstName, lastName);
        }

        /// 
        /// Generates a pastword following some common rules asked on the internet.
        /// 
        /// The generated pastword
        public string GeneratePastword(int? minSize = null, int? maxSize = null, bool? includeSpecialCharacters = null)
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(minSize, maxSize, includeSpecialCharacters),
                    MaxFailingAttemptsForNoDuplication,
                    standardGenerationFunction: (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.GeneratePastword(minSize, maxSize, includeSpecialCharacters));
            }

            return _personFuzzer.GeneratePastword(minSize, maxSize, includeSpecialCharacters);
        }

        #endregion

        #region CollectionFuzzer

        /// 
        /// Randomly pick one element from a given collection.
        /// 
        /// 
        /// One of the elements from the candidates collection.
        public T PickOneFrom(IList candidates)
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(candidates),
                    MaxFailingAttemptsForNoDuplication, 
                    standardGenerationFunction: (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.PickOneFrom(candidates),
                    lastChanceGenerationFunction: (fuzzerWithDuplicationAllowed, alreadyProvidedSortedSet) => LastChanceToFindNotAlreadyPickedValue(alreadyProvidedSortedSet, candidates, fuzzerWithDuplicationAllowed));
            }

            return _collectionFuzzer.PickOneFrom(candidates);
        }

        private static Maybe LastChanceToFindNotAlreadyPickedValue(SortedSet alreadyProvidedValues, IList candidates, IFuzz fuzzer)
        {
            var allPossibleValues = candidates.ToArray();

            var remainingCandidates = allPossibleValues.Except(alreadyProvidedValues.Cast()).ToArray();

            if (remainingCandidates.Any())
            {
                var index = fuzzer.GenerateInteger(0, remainingCandidates.Length - 1);
                var pickOneFrom = remainingCandidates[index];

                return new Maybe(pickOneFrom);
            }

            return new Maybe();
        }

        #endregion

        #region DateTimeFuzzer

        /// 
        /// Generates a random .
        /// 
        /// A  value generated randomly.
        public DateTime GenerateDateTime()
        {
            return _dateTimeFuzzer.GenerateDateTime();
        }

        /// 
        /// Generates a random  in a Time Range.
        /// 
        /// The minimum inclusive boundary of the Time Range for this  generation.
        /// The maximum inclusive boundary of the Time Range for this  generation.
        /// A  instance between the min and the max inclusive boundaries.
        public DateTime GenerateDateTimeBetween(DateTime minValue, DateTime maxValue)
        {
            return _dateTimeFuzzer.GenerateDateTimeBetween(minValue, maxValue);
        }

        /// 
        /// Generates a random  in a Time Range.
        /// 
        /// The minimum inclusive boundary of the Time Range for this  generation, specified as a yyyy/MM/dd string.
        /// The maximum inclusive boundary of the Time Range for this  generation, specified as a yyyy/MM/dd string.
        /// A  instance between the min and the max inclusive boundaries.
        public DateTime GenerateDateTimeBetween(string minDate, string maxDate)
        {
            return _dateTimeFuzzer.GenerateDateTimeBetween(minDate, maxDate);
        }

        #endregion

        #region StringFuzzer

        /// 
        /// Generates a random adjective based on a feeling.
        /// 
        /// The expected feeling of the adjective
        /// An adjective based on a particular feeling or random one if not provided
        public string GenerateAdjective(Feeling? feeling = null)
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(feeling),
                    MaxFailingAttemptsForNoDuplication, 
                    standardGenerationFunction: fuzzerWithDuplicationAllowed => fuzzerWithDuplicationAllowed.GenerateAdjective(feeling),
                    lastChanceGenerationFunction: (fuzzerWithDuplicationAllowed, alreadyProvidedSortedSet) => LastChanceToFindAdjective(feeling, alreadyProvidedSortedSet, fuzzerWithDuplicationAllowed));
            }

            return _stringFuzzer.GenerateAdjective(feeling);
        }

        private static Maybe LastChanceToFindAdjective(Feeling? feeling, SortedSet alreadyProvidedValues, IFuzz fuzzer)
        {
            var allPossibleValues = Adjectives.PerFeeling[feeling.Value];

            var remainingCandidates = allPossibleValues.Except(alreadyProvidedValues.Cast()).ToArray();

            if (remainingCandidates.Any())
            {
                var adjective = fuzzer.PickOneFrom(remainingCandidates);
                return new Maybe(adjective);
            }

            return new Maybe();
        }

        /// 
        /// Generates a string from a given 'diverse' format (# for a single digit number, X for upper-cased letter, x for lower-cased letter). 
        /// 
        /// The 'diverse' format to use (# for a single digit number, X for upper-cased letter, x for lower-cased letter).
        /// A randomly generated string following the 'diverse' format.
        public string GenerateStringFromPattern(string diverseFormat)
        {
            return _stringFuzzer.GenerateStringFromPattern(diverseFormat);
        }

        #endregion

        #region GuidFuzzer

        /// 
        /// Generates a random 
        /// 
        /// A random .
        public Guid GenerateGuid()
        {
            // No need to memoize Guids here since it is very unlikely that the lib generate twice the same value
            return _guidFuzzer.GenerateGuid();
        }

        #endregion

        #region TypeFuzzer

        /// 
        /// Generates an instance of a type T.
        /// 
        /// An instance of type T with some fuzzed properties.
        public T GenerateInstanceOf()
        {
            return _typeFuzzer.GenerateInstanceOf();
        }

        /// 
        /// Generates an instance of an  type.
        /// 
        /// Type of the 
        /// An random value of the specified  type.
        public T GenerateEnum()
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(),
                    MaxFailingAttemptsForNoDuplication, 
                    (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.GenerateEnum());
            }

            return _typeFuzzer.GenerateEnum();
        }

        #endregion

        #region LoremFuzzer

        /// 
        /// Generates random latin words.
        /// 
        /// This method won't be affected by the  mode.
        /// (optional) Number of words to generate.
        /// The generated latin words.
        public IEnumerable GenerateWords(int? number = null)
        {
            return _loremFuzzer.GenerateWords(number);
        }

        /// 
        /// Generate a sentence in latin.
        /// 
        /// This method won't be affected by the  mode.
        /// (optional) Number of words for this sentence.
        /// The generated sentence in latin.
        public string GenerateSentence(int? nbOfWords = null)
        {
            return _loremFuzzer.GenerateSentence(nbOfWords);
        }

        /// 
        /// Generates a paragraph in latin.
        /// 
        /// This method won't be affected by the  mode.
        /// (optional) Number of sentences for this paragraph.
        /// The generated paragraph in latin.
        public string GenerateParagraph(int? nbOfSentences = null)
        {
            return _loremFuzzer.GenerateParagraph(nbOfSentences);
        }

        /// 
        /// Generates a collection of paragraphs. 
        /// 
        /// This method won't be affected by the  mode.
        /// (optional) Number of paragraphs to generate.
        /// The collection of paragraphs.
        public IEnumerable GenerateParagraphs(int? nbOfParagraphs = null)
        {
            return _loremFuzzer.GenerateParagraphs(nbOfParagraphs);
        }

        /// 
        /// Generates a text in latin with a specified number of paragraphs.
        /// 
        /// This method won't be affected by the  mode.
        /// (optional) Number of paragraphs to generate.
        /// The generated text in latin.
        public string GenerateText(int? nbOfParagraphs = null)
        {
            return _loremFuzzer.GenerateText(nbOfParagraphs);
        }

        /// 
        /// Generates a random letter.
        /// 
        /// The generated letter.
        public char GenerateLetter()
        {
            if (NoDuplication)
            {
                return GenerateWithoutDuplication(MethodCapture.CaptureCurrentMethod(), ArgumentHasher.HashArguments(),
                    MaxFailingAttemptsForNoDuplication,
                    standardGenerationFunction: (fuzzerWithDuplicationAllowed) => fuzzerWithDuplicationAllowed.GenerateLetter(),
                    lastChanceGenerationFunction: (fuzzerWithDuplicationAllowed, alreadyProvidedSortedSet) => LastChanceToFindLetter(alreadyProvidedSortedSet, fuzzerWithDuplicationAllowed));
            }

            return _loremFuzzer.GenerateLetter();
        }

        private static Maybe LastChanceToFindLetter(SortedSet alreadyProvidedValues, IFuzz fuzzer)
        {
            var allPossibleValues = LoremFuzzer.Alphabet;

            var remainingCandidates = allPossibleValues.Except(alreadyProvidedValues.Cast()).ToArray();
            
            if (remainingCandidates.Any())
            {
                var letter = fuzzer.PickOneFrom(remainingCandidates);
                return new Maybe(letter);
            }

            return new Maybe();
        }

        #endregion
    }
}