/// <summary>
        /// Returns an item randomly, based on the specified weightings.
        /// </summary>
        /// <returns>The item generated.</returns>
        /// <exception cref="System.InvalidOperationException">No weightings are defined.</exception>
        public T Generate()
        {
            if (weightingRangesAndItems.Count == 0)
            {
                throw new InvalidOperationException("No weightings are defined.");
            }

            Int64 randomIndex = randomGenerator.Next(weightingsTotal) + weightingStartOffset;

            // Create an item and weighting with the random index
            var randomItemAndWeighting = new ItemAndWeighting <T>(default(T), new LongIntegerRange(randomIndex, 1));

            if (weightingRangesAndItems.Contains(randomItemAndWeighting) == true)
            {
                // Overwrite 'randomItemAndWeighting' with the actual item and weighting from the tree (which will contain the correct item... not a default)
                randomItemAndWeighting = weightingRangesAndItems.Get(randomItemAndWeighting);
            }
            else
            {
                // If a range starting with the random index doesn't exist, get the next lower range (which will include the random index)
                randomItemAndWeighting = weightingRangesAndItems.GetNextLessThan(randomItemAndWeighting).Item2;
            }

            return(randomItemAndWeighting.Item);
        }
        /// <summary>
        /// Removes an item and its weighting from the generator.
        /// </summary>
        /// <param name="item">The item to remove.</param>
        /// <remarks>This method consumes O(n * log(n)) time complexity (where n is the number of weightings defined).</remarks>
        /// <exception cref="System.ArgumentException">A weighting for the specified item does not exist.</exception>
        public void RemoveWeighting(T item)
        {
            if (itemToWeightingMap.ContainsKey(item) == false)
            {
                throw new ArgumentException("A weighting for the specified item does not exist.", nameof(item));
            }

            var   itemAndWeightingToRemove = new ItemAndWeighting <T>(item, itemToWeightingMap[item]);
            Int32 numberOfLowerRanges      = weightingRangesAndItems.GetCountLessThan(itemAndWeightingToRemove);
            Int32 numberOfHigherRanges     = weightingRangesAndItems.GetCountGreaterThan(itemAndWeightingToRemove);

            // Remove the weighting corresponding to the specified item
            weightingRangesAndItems.Remove(itemAndWeightingToRemove);
            itemToWeightingMap.Remove(item);

            // TODO: Could potentially refactor each half of if statement since most codes lines are the same... abstract GetNextLessThan() and GetNextGreaterThan() behind lambdas (and also have a lambda negating rangeToRemove.Length for -= and += operators)
            if (numberOfLowerRanges < numberOfHigherRanges)
            {
                // Shuffle up the values of all the ranges lower than the one removed
                Tuple <Boolean, ItemAndWeighting <T> > nextLowerRangeResult = weightingRangesAndItems.GetNextLessThan(itemAndWeightingToRemove);
                while (nextLowerRangeResult.Item1 == true)
                {
                    nextLowerRangeResult.Item2.Weighting.StartValue    += itemAndWeightingToRemove.Weighting.Length;
                    itemToWeightingMap[nextLowerRangeResult.Item2.Item] = nextLowerRangeResult.Item2.Weighting;
                    nextLowerRangeResult = weightingRangesAndItems.GetNextLessThan(nextLowerRangeResult.Item2);
                }

                weightingStartOffset += itemAndWeightingToRemove.Weighting.Length;
            }
            else
            {
                // Shuffle down the values of all the ranges higher than the one removed
                Tuple <Boolean, ItemAndWeighting <T> > nextHigherRangeResult = weightingRangesAndItems.GetNextGreaterThan(itemAndWeightingToRemove);
                while (nextHigherRangeResult.Item1 == true)
                {
                    nextHigherRangeResult.Item2.Weighting.StartValue    -= itemAndWeightingToRemove.Weighting.Length;
                    itemToWeightingMap[nextHigherRangeResult.Item2.Item] = nextHigherRangeResult.Item2.Weighting;
                    nextHigherRangeResult = weightingRangesAndItems.GetNextGreaterThan(nextHigherRangeResult.Item2);
                }
            }

            weightingsTotal -= itemAndWeightingToRemove.Weighting.Length;

            if (weightingRangesAndItems.Count == 0)
            {
                weightingStartOffset = 0;
            }
        }
        /// <summary>
        /// Sets the random weightings, and the item corresponding to each weighting.
        /// </summary>
        /// <param name="weightings">The set of weightings defined by list of tuples containing 2 values: the item to attach to the weighting, and a weighting allocated to random selection of the item (i.e. an item with weight 2 will be twice as likely to be selected as an item with weight 1).</param>
        /// <exception cref="System.ArgumentException">The specified set of weightings is empty.</exception>
        /// <exception cref="System.ArgumentException">The total of the specified weightings exceeds Int64.MaxValue.</exception>
        /// <exception cref="System.ArgumentException">The specified weightings contain a duplicate item.</exception>
        public void SetWeightings(IList <Tuple <T, Int64> > weightings)
        {
            if (weightings.Count == 0)
            {
                throw new ArgumentException("The specified set of weightings is empty.", nameof(weightings));
            }

            Clear();
            var   weightingsAsRanges  = new ItemAndWeighting <T> [weightings.Count];
            Int64 nextRangeStartValue = 0;
            Int32 currentIndex        = 0;

            foreach (Tuple <T, Int64> currentWeighting in weightings)
            {
                if ((Int64.MaxValue - 1) - nextRangeStartValue < (currentWeighting.Item2 - 1))
                {
                    throw new ArgumentException("The total of the specified weightings cannot exceed Int64.MaxValue.", nameof(weightings));
                }

                var currentRange = new LongIntegerRange(nextRangeStartValue, currentWeighting.Item2);
                weightingsAsRanges[currentIndex] = new ItemAndWeighting <T>(currentWeighting.Item1, currentRange);
                currentIndex++;
                weightingsTotal     += (currentWeighting.Item2);
                nextRangeStartValue += currentWeighting.Item2;
            }
            foreach (ItemAndWeighting <T> currentItemAndWeighting in weightingsAsRanges)
            {
                if (itemToWeightingMap.ContainsKey(currentItemAndWeighting.Item) == true)
                {
                    throw new ArgumentException($"The specified weightings contain duplicate item with value = '{currentItemAndWeighting.Item.ToString()}'.", nameof(weightings));
                }

                weightingRangesAndItems.Add(currentItemAndWeighting);
                itemToWeightingMap.Add(currentItemAndWeighting.Item, currentItemAndWeighting.Weighting);
            }
        }
 #pragma warning disable 1591
 public Int32 CompareTo(ItemAndWeighting <T> other)
 {
     return(this.weighting.CompareTo(other.weighting));
 }