/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.rng.sampling;

import java.util.List;
import java.util.Objects;
import java.util.ArrayList;

import org.apache.commons.rng.UniformRandomProvider;
import org.apache.commons.rng.sampling.distribution.AliasMethodDiscreteSampler;
import org.apache.commons.rng.sampling.distribution.ContinuousSampler;
import org.apache.commons.rng.sampling.distribution.DiscreteSampler;
import org.apache.commons.rng.sampling.distribution.DiscreteUniformSampler;
import org.apache.commons.rng.sampling.distribution.GuideTableDiscreteSampler;
import org.apache.commons.rng.sampling.distribution.LongSampler;
import org.apache.commons.rng.sampling.distribution.MarsagliaTsangWangDiscreteSampler;
import org.apache.commons.rng.sampling.distribution.SharedStateContinuousSampler;
import org.apache.commons.rng.sampling.distribution.SharedStateDiscreteSampler;
import org.apache.commons.rng.sampling.distribution.SharedStateLongSampler;

/**
 * Factory class to create a sampler that combines sampling from multiple samplers.
 *
 * <p>The composite sampler is constructed using a {@link Builder builder} for the type of samplers
 * that will form the composite. Each sampler has a weight in the composition.
 * Samples are returned using a 2 step algorithm:
 *
 * <ol>
 *  <li>Select a sampler based on its weighting</li>
 *  <li>Return a sample from the selected sampler</li>
 * </ol>
 *
 * <p>The weights used for each sampler create a discrete probability distribution. This is
 * sampled using a discrete probability distribution sampler. The builder provides methods
 * to change the default implementation.
 *
 * <p>The following example will create a sampler to uniformly sample the border of a triangle
 * using the line segment lengths as weights:
 *
 * <pre>
 * UniformRandomProvider rng = RandomSource.KISS.create();
 * double[] a = {1.23, 4.56};
 * double[] b = {6.78, 9.01};
 * double[] c = {3.45, 2.34};
 * ObjectSampler&lt;double[]&gt; sampler =
 *     CompositeSamplers.&lt;double[]&gt;newObjectSamplerBuilder()
 *         .add(LineSampler.of(rng, a, b), Math.hypot(a[0] - b[0], a[1] - b[1]))
 *         .add(LineSampler.of(rng, b, c), Math.hypot(b[0] - c[0], b[1] - c[1]))
 *         .add(LineSampler.of(rng, c, a), Math.hypot(c[0] - a[0], c[1] - a[1]))
 *         .build(rng);
 * </pre>
 *
 * @since 1.4
 */
public final class CompositeSamplers {
    /**
     * A factory for creating a sampler of a user-defined
     * <a href="https://en.wikipedia.org/wiki/Probability_distribution#Discrete_probability_distribution">
     * discrete probability distribution</a>.
     */
    @FunctionalInterface
    public interface DiscreteProbabilitySamplerFactory {
        /**
         * Creates the sampler.
         *
         * @param rng Source of randomness.
         * @param probabilities Discrete probability distribution.
         * @return the sampler
         */
        DiscreteSampler create(UniformRandomProvider rng,
                               double[] probabilities);
    }

    /**
     * Enumerates implementations that sample from a user-defined
     * <a href="https://en.wikipedia.org/wiki/Probability_distribution#Discrete_probability_distribution">
     * discrete probability distribution</a>.
     *
     * <p>All implementations support the {@link SharedStateDiscreteSampler} interface.
     */
    public enum DiscreteProbabilitySampler implements DiscreteProbabilitySamplerFactory {
        /** Sample using a guide table (see {@link GuideTableDiscreteSampler}). */
        GUIDE_TABLE {
            @Override
            public SharedStateDiscreteSampler create(UniformRandomProvider rng, double[] probabilities) {
                return GuideTableDiscreteSampler.of(rng, probabilities);
            }
        },
        /** Sample using the alias method (see {@link AliasMethodDiscreteSampler}). */
        ALIAS_METHOD {
            @Override
            public SharedStateDiscreteSampler create(UniformRandomProvider rng, double[] probabilities) {
                return AliasMethodDiscreteSampler.of(rng, probabilities);
            }
        },
        /**
         * Sample using an optimised look-up table (see
         * {@link org.apache.commons.rng.sampling.distribution.MarsagliaTsangWangDiscreteSampler.Enumerated
         * MarsagliaTsangWangDiscreteSampler.Enumerated}).
         */
        LOOKUP_TABLE {
            @Override
            public SharedStateDiscreteSampler create(UniformRandomProvider rng, double[] probabilities) {
                return MarsagliaTsangWangDiscreteSampler.Enumerated.of(rng, probabilities);
            }
        }
    }

    /**
     * A class to implement the SharedStateDiscreteSampler interface for a discrete probability
     * sampler given a factory and the probability distribution. Each new instance will recreate
     * the distribution sampler using the factory.
     */
    private static final class SharedStateDiscreteProbabilitySampler implements SharedStateDiscreteSampler {
        /** The sampler. */
        private final DiscreteSampler sampler;
        /** The factory to create a new discrete sampler. */
        private final DiscreteProbabilitySamplerFactory factory;
        /** The probabilities. */
        private final double[] probabilities;

        /**
         * @param sampler Sampler of the discrete distribution.
         * @param factory Factory to create a new discrete sampler.
         * @param probabilities Probabilities of the discrete distribution.
         * @throws NullPointerException if the {@code sampler} is null
         */
        SharedStateDiscreteProbabilitySampler(DiscreteSampler sampler,
                                              DiscreteProbabilitySamplerFactory factory,
                                              double[] probabilities) {
            this.sampler = Objects.requireNonNull(sampler, "discrete sampler");
            // Assume the factory and probabilities are not null
            this.factory = factory;
            this.probabilities = probabilities;
        }

        @Override
        public int sample() {
            // Delegate
            return sampler.sample();
        }

        @Override
        public SharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
            // The factory may destructively modify the probabilities
            return new SharedStateDiscreteProbabilitySampler(factory.create(rng, probabilities.clone()),
                                                             factory, probabilities);
        }
    }

    /**
     * Builds a composite sampler.
     *
     * <p>A composite sampler is a combination of multiple samplers
     * that all return the same sample type. Each sampler has a weighting in the composition.
     * Samples are returned using a 2 step algorithm:
     *
     * <ol>
     *  <li>Select a sampler based on its weighting</li>
     *  <li>Return a sample from the selected sampler</li>
     * </ol>
     *
     * <p>Step 1 requires a discrete sampler constructed from a discrete probability distribution.
     * The probability for each sampler is the sampler weight divided by the sum of the weights:
     * <pre>
     * p(i) = w(i) / sum(w)
     * </pre>
     *
     * <p>The builder provides a method to set the factory used to generate the discrete sampler.
     *
     * @param <S> Type of sampler
     */
    public interface Builder<S> {
        /**
         * Return the number of samplers in the composite. The size must be non-zero before
         * the {@link #build(UniformRandomProvider) build} method can create a sampler.
         *
         * @return the size
         */
        int size();

        /**
         * Adds the sampler to the composite. A sampler with a zero weight is ignored.
         *
         * @param sampler Sampler.
         * @param weight Weight for the composition.
         * @return a reference to this builder
         * @throws IllegalArgumentException if {@code weight} is negative, infinite or {@code NaN}.
         * @throws NullPointerException if {@code sampler} is null.
         */
        Builder<S> add(S sampler, double weight);

        /**
         * Sets the factory to use to generate the composite's discrete sampler from the sampler
         * weights.
         *
         * <p>Note: If the factory is not explicitly set then a default will be used.
         *
         * @param factory Factory.
         * @return a reference to this builder
         * @throws NullPointerException if {@code factory} is null.
         */
        Builder<S> setFactory(DiscreteProbabilitySamplerFactory factory);

        /**
         * Builds the composite sampler. The {@code rng} is the source of randomness for selecting
         * which sampler to use for each sample.
         *
         * <p>Note: When the sampler is created the builder is reset to an empty state.
         * This prevents building multiple composite samplers with the same samplers and
         * their identical underlying source of randomness.
         *
         * @param rng Generator of uniformly distributed random numbers.
         * @return the sampler
         * @throws IllegalStateException if no samplers have been added to create a composite.
         * @see #size()
         */
        S build(UniformRandomProvider rng);
    }

    /**
     * Builds a composite sampler.
     *
     * <p>A single builder can be used to create composites of different implementing classes
     * which support different sampler interfaces. The type of sampler is generic. The individual
     * samplers and their weights can be collected by the builder. The build method creates
     * the discrete probability distribution from the weights. The final composite is created
     * using a factory to create the class.
     *
     * @param <S> Type of sampler
     */
    private static final class SamplerBuilder<S> implements Builder<S> {
        /** The specialisation of the sampler. */
        private final Specialisation specialisation;
        /** The weighted samplers. */
        private final List<WeightedSampler<S>> weightedSamplers;
        /** The factory to create the discrete probability sampler from the weights. */
        private DiscreteProbabilitySamplerFactory factory;
        /** The factory to create the composite sampler. */
        private final SamplerFactory<S> compositeFactory;

        /**
         * The specialisation of composite sampler to build.
         * This is used to determine if specialised interfaces from the sampler
         * type must be supported, e.g. {@link SharedStateSampler}.
         */
        enum Specialisation {
            /** Instance of {@link SharedStateSampler}. */
            SHARED_STATE_SAMPLER,
            /** No specialisation. */
            NONE
        }

        /**
         * A factory for creating composite samplers.
         *
         * <p>This interface is used to build concrete implementations
         * of different sampler interfaces.
         *
         * @param <S> Type of sampler
         */
        @FunctionalInterface
        interface SamplerFactory<S> {
            /**
             * Creates a new composite sampler.
             *
             * <p>If the composite specialisation is a
             * {@link Specialisation#SHARED_STATE_SAMPLER shared state sampler}
             * the discrete sampler passed to this method will be an instance of
             * {@link SharedStateDiscreteSampler}.
             *
             * @param discreteSampler Discrete sampler.
             * @param samplers Samplers.
             * @return the sampler
             */
            S createSampler(DiscreteSampler discreteSampler,
                            List<S> samplers);
        }

        /**
         * Contains a weighted sampler.
         *
         * @param <S> Sampler type
         */
        private static final class WeightedSampler<S> {
            /** The weight. */
            private final double weight;
            /** The sampler. */
            private final S sampler;

            /**
             * @param weight the weight
             * @param sampler the sampler
             * @throws IllegalArgumentException if {@code weight} is negative, infinite or {@code NaN}.
             * @throws NullPointerException if {@code sampler} is null.
             */
            WeightedSampler(double weight, S sampler) {
                this.weight = requirePositiveFinite(weight, "weight");
                this.sampler = Objects.requireNonNull(sampler, "sampler");
            }

            /**
             * Gets the weight.
             *
             * @return the weight
             */
            double getWeight() {
                return weight;
            }

            /**
             * Gets the sampler.
             *
             * @return the sampler
             */
            S getSampler() {
                return sampler;
            }

            /**
             * Checks that the specified value is positive finite and throws a customized
             * {@link IllegalArgumentException} if it is not.
             *
             * @param value the value
             * @param message detail message to be used in the event that a {@code
             *                IllegalArgumentException} is thrown
             * @return {@code value} if positive finite
             * @throws IllegalArgumentException if {@code weight} is negative, infinite or {@code NaN}.
             */
            private static double requirePositiveFinite(double value, String message) {
                // Must be positive finite
                if (!(value >= 0 && value < Double.POSITIVE_INFINITY)) {
                    throw new IllegalArgumentException(message + " is not positive finite: " + value);
                }
                return value;
            }
        }

        /**
         * @param specialisation Specialisation of the sampler.
         * @param compositeFactory Factory to create the final composite sampler.
         */
        SamplerBuilder(Specialisation specialisation,
                       SamplerFactory<S> compositeFactory) {
            this.specialisation = specialisation;
            this.compositeFactory = compositeFactory;
            weightedSamplers = new ArrayList<>();
            factory = DiscreteProbabilitySampler.GUIDE_TABLE;
        }

        @Override
        public int size() {
            return weightedSamplers.size();
        }

        @Override
        public Builder<S> add(S sampler, double weight) {
            // Ignore zero weights. The sampler and weight are validated by the WeightedSampler.
            if (weight != 0) {
                weightedSamplers.add(new WeightedSampler<>(weight, sampler));
            }
            return this;
        }

        /**
         * {@inheritDoc}
         *
         * <p>If the weights are uniform the factory is ignored and composite's discrete sampler
         * is a {@link DiscreteUniformSampler uniform distribution sampler}.
         */
        @Override
        public Builder<S> setFactory(DiscreteProbabilitySamplerFactory samplerFactory) {
            this.factory = Objects.requireNonNull(samplerFactory, "factory");
            return this;
        }

        /**
         * {@inheritDoc}
         *
         * <p>If only one sampler has been added to the builder then the sampler is returned
         * and the builder is reset.
         *
         * @throws IllegalStateException if no samplers have been added to create a composite.
         */
        @Override
        public S build(UniformRandomProvider rng) {
            final List<WeightedSampler<S>> list = this.weightedSamplers;
            final int n = list.size();
            if (n == 0) {
                throw new IllegalStateException("No samplers to build the composite");
            }
            if (n == 1) {
                // No composite
                final S sampler = list.get(0).sampler;
                reset();
                return sampler;
            }

            // Extract the weights and samplers.
            final double[] weights = new double[n];
            final List<S> samplers = new ArrayList<>(n);
            for (int i = 0; i < n; i++) {
                final WeightedSampler<S> weightedItem = list.get(i);
                weights[i] = weightedItem.getWeight();
                samplers.add(weightedItem.getSampler());
            }

            reset();

            final DiscreteSampler discreteSampler = createDiscreteSampler(rng, weights);

            return compositeFactory.createSampler(discreteSampler, samplers);
        }

        /**
         * Reset the builder.
         */
        private void reset() {
            weightedSamplers.clear();
        }

        /**
         * Creates the discrete sampler of the enumerated probability distribution.
         *
         * <p>If the specialisation is a {@link Specialisation#SHARED_STATE_SAMPLER shared state sampler}
         * the discrete sampler will be an instance of {@link SharedStateDiscreteSampler}.
         *
         * @param rng Generator of uniformly distributed random numbers.
         * @param weights Weight associated to each item.
         * @return the sampler
         */
        private DiscreteSampler createDiscreteSampler(UniformRandomProvider rng,
                                                      double[] weights) {
            // Edge case. Detect uniform weights.
            final int n = weights.length;
            if (uniform(weights)) {
                // Uniformly sample from the size.
                // Note: Upper bound is inclusive.
                return DiscreteUniformSampler.of(rng, 0, n - 1);
            }

            // If possible normalise with a simple sum.
            final double sum = sum(weights);
            if (sum < Double.POSITIVE_INFINITY) {
                // Do not use f = 1.0 / sum and multiplication by f.
                // Use of divide handles a sub-normal sum.
                for (int i = 0; i < n; i++) {
                    weights[i] /= sum;
                }
            } else {
                // The sum is not finite. We know the weights are all positive finite.
                // Compute the mean without overflow and divide by the mean and number of items.
                final double mean = mean(weights);
                for (int i = 0; i < n; i++) {
                    // Two step division avoids using the denominator (mean * n)
                    weights[i] = weights[i] / mean / n;
                }
            }

            // Create the sampler from the factory.
            // Check if a SharedStateSampler is required.
            // If a default factory then the result is a SharedStateDiscreteSampler,
            // otherwise the sampler must be checked.
            if (specialisation == Specialisation.SHARED_STATE_SAMPLER &&
                !(factory instanceof DiscreteProbabilitySampler)) {
                // If the factory was user-defined then clone the weights as they may be required
                // to create a SharedStateDiscreteProbabilitySampler.
                final DiscreteSampler sampler = factory.create(rng, weights.clone());
                return sampler instanceof SharedStateDiscreteSampler ?
                     sampler :
                     new SharedStateDiscreteProbabilitySampler(sampler, factory, weights);
            }

            return factory.create(rng, weights);
        }

        /**
         * Check if all the values are the same.
         *
         * <p>Warning: This method assumes there are input values. If the length is zero an
         * {@link ArrayIndexOutOfBoundsException} will be thrown.
         *
         * @param values the values
         * @return true if all values are the same
         */
        private static boolean uniform(double[] values) {
            final double value = values[0];
            for (int i = 1; i < values.length; i++) {
                if (value != values[i]) {
                    return false;
                }
            }
            return true;
        }

        /**
         * Compute the sum of the values.
         *
         * @param values the values
         * @return the sum
         */
        private static double sum(double[] values) {
            double sum = 0;
            for (final double value : values) {
                sum += value;
            }
            return sum;
        }

        /**
         * Compute the mean of the values. Uses a rolling algorithm to avoid overflow of a simple sum.
         * This method can be used to compute the mean of observed counts for normalisation to a
         * probability:
         *
         * <pre>
         * double[] values = ...;
         * int n = values.length;
         * double mean = mean(values);
         * for (int i = 0; i &lt; n; i++) {
         *     // Two step division avoids using the denominator (mean * n)
         *     values[i] = values[i] / mean / n;
         * }
         * </pre>
         *
         * <p>Warning: This method assumes there are input values. If the length is zero an
         * {@link ArrayIndexOutOfBoundsException} will be thrown.
         *
         * @param values the values
         * @return the mean
         */
        private static double mean(double[] values) {
            double mean = values[0];
            int i = 1;
            while (i < values.length) {
                // Deviation from the mean
                final double dev = values[i] - mean;
                i++;
                mean += dev / i;
            }
            return mean;
        }
    }

    /**
     * A composite sampler.
     *
     * <p>The source sampler for each sampler is chosen based on a user-defined continuous
     * probability distribution.
     *
     * @param <S> Type of sampler
     */
    private static class CompositeSampler<S> {
        /** Continuous sampler to choose the individual sampler to sample. */
        protected final DiscreteSampler discreteSampler;
        /** Collection of samplers to be sampled from. */
        protected final List<S> samplers;

        /**
         * @param discreteSampler Continuous sampler to choose the individual sampler to sample.
         * @param samplers Collection of samplers to be sampled from.
         */
        CompositeSampler(DiscreteSampler discreteSampler,
                         List<S> samplers) {
            this.discreteSampler = discreteSampler;
            this.samplers = samplers;
        }

        /**
         * Gets the next sampler to use to create a sample.
         *
         * @return the sampler
         */
        S nextSampler() {
            return samplers.get(discreteSampler.sample());
        }
    }

    /**
     * A factory for creating a composite ObjectSampler.
     *
     * @param <T> Type of sample
     */
    private static final class ObjectSamplerFactory<T> implements
            SamplerBuilder.SamplerFactory<ObjectSampler<T>> {
        /** The instance. */
        @SuppressWarnings("rawtypes")
        private static final ObjectSamplerFactory INSTANCE = new ObjectSamplerFactory<>();

        /**
         * Get an instance.
         *
         * @param <T> Type of sample
         * @return the factory
         */
        @SuppressWarnings("unchecked")
        static <T> ObjectSamplerFactory<T> instance() {
            return (ObjectSamplerFactory<T>) INSTANCE;
        }

        @Override
        public ObjectSampler<T> createSampler(DiscreteSampler discreteSampler,
                                              List<ObjectSampler<T>> samplers) {
            return new CompositeObjectSampler<>(discreteSampler, samplers);
        }

        /**
         * A composite object sampler.
         *
         * @param <T> Type of sample
         */
        private static final class CompositeObjectSampler<T>
                extends CompositeSampler<ObjectSampler<T>>
                implements ObjectSampler<T> {
            /**
             * @param discreteSampler Discrete sampler to choose the individual sampler to sample.
             * @param samplers Collection of samplers to be sampled from.
             */
            CompositeObjectSampler(DiscreteSampler discreteSampler,
                                   List<ObjectSampler<T>> samplers) {
                super(discreteSampler, samplers);
            }

            @Override
            public T sample() {
                return nextSampler().sample();
            }
        }
    }

    /**
     * A factory for creating a composite SharedStateObjectSampler.
     *
     * @param <T> Type of sample
     */
    private static final class SharedStateObjectSamplerFactory<T> implements
            SamplerBuilder.SamplerFactory<SharedStateObjectSampler<T>> {
        /** The instance. */
        @SuppressWarnings("rawtypes")
        private static final SharedStateObjectSamplerFactory INSTANCE = new SharedStateObjectSamplerFactory<>();

        /**
         * Get an instance.
         *
         * @param <T> Type of sample
         * @return the factory
         */
        @SuppressWarnings("unchecked")
        static <T> SharedStateObjectSamplerFactory<T> instance() {
            return (SharedStateObjectSamplerFactory<T>) INSTANCE;
        }

        @Override
        public SharedStateObjectSampler<T> createSampler(DiscreteSampler discreteSampler,
                                                         List<SharedStateObjectSampler<T>> samplers) {
            // The input discrete sampler is assumed to be a SharedStateDiscreteSampler
            return new CompositeSharedStateObjectSampler<>(
                (SharedStateDiscreteSampler) discreteSampler, samplers);
        }

        /**
         * A composite object sampler with shared state support.
         *
         * <p>The source sampler for each sampler is chosen based on a user-defined
         * discrete probability distribution.
         *
         * @param <T> Type of sample
         */
        private static final class CompositeSharedStateObjectSampler<T>
                extends CompositeSampler<SharedStateObjectSampler<T>>
                implements SharedStateObjectSampler<T> {
            /**
             * @param discreteSampler Discrete sampler to choose the individual sampler to sample.
             * @param samplers Collection of samplers to be sampled from.
             */
            CompositeSharedStateObjectSampler(SharedStateDiscreteSampler discreteSampler,
                                              List<SharedStateObjectSampler<T>> samplers) {
                super(discreteSampler, samplers);
            }

            @Override
            public T sample() {
                return nextSampler().sample();
            }

            @Override
            public CompositeSharedStateObjectSampler<T> withUniformRandomProvider(UniformRandomProvider rng) {
                // Duplicate each sampler with the same source of randomness
                return new CompositeSharedStateObjectSampler<>(
                    ((SharedStateDiscreteSampler) this.discreteSampler).withUniformRandomProvider(rng),
                    copy(samplers, rng));
            }
        }
    }

    /**
     * A factory for creating a composite DiscreteSampler.
     */
    private static final class DiscreteSamplerFactory implements
            SamplerBuilder.SamplerFactory<DiscreteSampler> {
        /** The instance. */
        static final DiscreteSamplerFactory INSTANCE = new DiscreteSamplerFactory();

        @Override
        public DiscreteSampler createSampler(DiscreteSampler discreteSampler,
                                             List<DiscreteSampler> samplers) {
            return new CompositeDiscreteSampler(discreteSampler, samplers);
        }

        /**
         * A composite discrete sampler.
         */
        private static final class CompositeDiscreteSampler
                extends CompositeSampler<DiscreteSampler>
                implements DiscreteSampler {
            /**
             * @param discreteSampler Discrete sampler to choose the individual sampler to sample.
             * @param samplers Collection of samplers to be sampled from.
             */
            CompositeDiscreteSampler(DiscreteSampler discreteSampler,
                                     List<DiscreteSampler> samplers) {
                super(discreteSampler, samplers);
            }

            @Override
            public int sample() {
                return nextSampler().sample();
            }
        }
    }

    /**
     * A factory for creating a composite SharedStateDiscreteSampler.
     */
    private static final class SharedStateDiscreteSamplerFactory implements
            SamplerBuilder.SamplerFactory<SharedStateDiscreteSampler> {
        /** The instance. */
        static final SharedStateDiscreteSamplerFactory INSTANCE = new SharedStateDiscreteSamplerFactory();

        @Override
        public SharedStateDiscreteSampler createSampler(DiscreteSampler discreteSampler,
                                                        List<SharedStateDiscreteSampler> samplers) {
            // The input discrete sampler is assumed to be a SharedStateDiscreteSampler
            return new CompositeSharedStateDiscreteSampler(
                (SharedStateDiscreteSampler) discreteSampler, samplers);
        }

        /**
         * A composite discrete sampler with shared state support.
         */
        private static final class CompositeSharedStateDiscreteSampler
                extends CompositeSampler<SharedStateDiscreteSampler>
                implements SharedStateDiscreteSampler {
            /**
             * @param discreteSampler Discrete sampler to choose the individual sampler to sample.
             * @param samplers Collection of samplers to be sampled from.
             */
            CompositeSharedStateDiscreteSampler(SharedStateDiscreteSampler discreteSampler,
                                                List<SharedStateDiscreteSampler> samplers) {
                super(discreteSampler, samplers);
            }

            @Override
            public int sample() {
                return nextSampler().sample();
            }

            @Override
            public CompositeSharedStateDiscreteSampler withUniformRandomProvider(UniformRandomProvider rng) {
                // Duplicate each sampler with the same source of randomness
                return new CompositeSharedStateDiscreteSampler(
                    ((SharedStateDiscreteSampler) this.discreteSampler).withUniformRandomProvider(rng),
                    copy(samplers, rng));
            }
        }
    }

    /**
     * A factory for creating a composite ContinuousSampler.
     */
    private static final class ContinuousSamplerFactory implements
            SamplerBuilder.SamplerFactory<ContinuousSampler> {
        /** The instance. */
        static final ContinuousSamplerFactory INSTANCE = new ContinuousSamplerFactory();

        @Override
        public ContinuousSampler createSampler(DiscreteSampler discreteSampler,
                                               List<ContinuousSampler> samplers) {
            return new CompositeContinuousSampler(discreteSampler, samplers);
        }

        /**
         * A composite continuous sampler.
         */
        private static final class CompositeContinuousSampler
                extends CompositeSampler<ContinuousSampler>
                implements ContinuousSampler {
            /**
             * @param discreteSampler Continuous sampler to choose the individual sampler to sample.
             * @param samplers Collection of samplers to be sampled from.
             */
            CompositeContinuousSampler(DiscreteSampler discreteSampler,
                                       List<ContinuousSampler> samplers) {
                super(discreteSampler, samplers);
            }

            @Override
            public double sample() {
                return nextSampler().sample();
            }
        }
    }

    /**
     * A factory for creating a composite SharedStateContinuousSampler.
     */
    private static final class SharedStateContinuousSamplerFactory implements
            SamplerBuilder.SamplerFactory<SharedStateContinuousSampler> {
        /** The instance. */
        static final SharedStateContinuousSamplerFactory INSTANCE = new SharedStateContinuousSamplerFactory();

        @Override
        public SharedStateContinuousSampler createSampler(DiscreteSampler discreteSampler,
                                                          List<SharedStateContinuousSampler> samplers) {
            // The sampler is assumed to be a SharedStateContinuousSampler
            return new CompositeSharedStateContinuousSampler(
                (SharedStateDiscreteSampler) discreteSampler, samplers);
        }

        /**
         * A composite continuous sampler with shared state support.
         */
        private static final class CompositeSharedStateContinuousSampler
                extends CompositeSampler<SharedStateContinuousSampler>
                implements SharedStateContinuousSampler {
            /**
             * @param discreteSampler Continuous sampler to choose the individual sampler to sample.
             * @param samplers Collection of samplers to be sampled from.
             */
            CompositeSharedStateContinuousSampler(SharedStateDiscreteSampler discreteSampler,
                                                  List<SharedStateContinuousSampler> samplers) {
                super(discreteSampler, samplers);
            }

            @Override
            public double sample() {
                return nextSampler().sample();
            }

            @Override
            public CompositeSharedStateContinuousSampler withUniformRandomProvider(UniformRandomProvider rng) {
                // Duplicate each sampler with the same source of randomness
                return new CompositeSharedStateContinuousSampler(
                    ((SharedStateDiscreteSampler) this.discreteSampler).withUniformRandomProvider(rng),
                    copy(samplers, rng));
            }
        }
    }

    /**
     * A factory for creating a composite LongSampler.
     */
    private static final class LongSamplerFactory implements
            SamplerBuilder.SamplerFactory<LongSampler> {
        /** The instance. */
        static final LongSamplerFactory INSTANCE = new LongSamplerFactory();

        @Override
        public LongSampler createSampler(DiscreteSampler discreteSampler,
                                         List<LongSampler> samplers) {
            return new CompositeLongSampler(discreteSampler, samplers);
        }

        /**
         * A composite long sampler.
         */
        private static final class CompositeLongSampler
                extends CompositeSampler<LongSampler>
                implements LongSampler {
            /**
             * @param discreteSampler Long sampler to choose the individual sampler to sample.
             * @param samplers Collection of samplers to be sampled from.
             */
            CompositeLongSampler(DiscreteSampler discreteSampler,
                                 List<LongSampler> samplers) {
                super(discreteSampler, samplers);
            }

            @Override
            public long sample() {
                return nextSampler().sample();
            }
        }
    }

    /**
     * A factory for creating a composite SharedStateLongSampler.
     */
    private static final class SharedStateLongSamplerFactory implements
            SamplerBuilder.SamplerFactory<SharedStateLongSampler> {
        /** The instance. */
        static final SharedStateLongSamplerFactory INSTANCE = new SharedStateLongSamplerFactory();

        @Override
        public SharedStateLongSampler createSampler(DiscreteSampler discreteSampler,
                                                    List<SharedStateLongSampler> samplers) {
            // The input discrete sampler is assumed to be a SharedStateLongSampler
            return new CompositeSharedStateLongSampler(
                (SharedStateDiscreteSampler) discreteSampler, samplers);
        }

        /**
         * A composite long sampler with shared state support.
         */
        private static final class CompositeSharedStateLongSampler
                extends CompositeSampler<SharedStateLongSampler>
                implements SharedStateLongSampler {
            /**
             * @param discreteSampler Long sampler to choose the individual sampler to sample.
             * @param samplers Collection of samplers to be sampled from.
             */
            CompositeSharedStateLongSampler(SharedStateDiscreteSampler discreteSampler,
                                            List<SharedStateLongSampler> samplers) {
                super(discreteSampler, samplers);
            }

            @Override
            public long sample() {
                return nextSampler().sample();
            }

            @Override
            public CompositeSharedStateLongSampler withUniformRandomProvider(UniformRandomProvider rng) {
                // Duplicate each sampler with the same source of randomness
                return new CompositeSharedStateLongSampler(
                    ((SharedStateDiscreteSampler) this.discreteSampler).withUniformRandomProvider(rng),
                    copy(samplers, rng));
            }
        }
    }

    /** No public instances. */
    private CompositeSamplers() {}

    /**
     * Create a new builder for a composite {@link ObjectSampler}.
     *
     * <p>Note: If the compiler cannot infer the type parameter of the sampler it can be specified
     * within the diamond operator {@code <T>} preceding the call to
     * {@code newObjectSamplerBuilder()}, for example:
     *
     * <pre>{@code
     * CompositeSamplers.<double[]>newObjectSamplerBuilder()
     * }</pre>
     *
     * @param <T> Type of the sample.
     * @return the builder
     */
    public static <T> Builder<ObjectSampler<T>> newObjectSamplerBuilder() {
        final SamplerBuilder.SamplerFactory<ObjectSampler<T>> factory = ObjectSamplerFactory.instance();
        return new SamplerBuilder<>(
            SamplerBuilder.Specialisation.NONE, factory);
    }

    /**
     * Create a new builder for a composite {@link SharedStateObjectSampler}.
     *
     * <p>Note: If the compiler cannot infer the type parameter of the sampler it can be specified
     * within the diamond operator {@code <T>} preceding the call to
     * {@code newSharedStateObjectSamplerBuilder()}, for example:
     *
     * <pre>{@code
     * CompositeSamplers.<double[]>newSharedStateObjectSamplerBuilder()
     * }</pre>
     *
     * @param <T> Type of the sample.
     * @return the builder
     */
    public static <T> Builder<SharedStateObjectSampler<T>> newSharedStateObjectSamplerBuilder() {
        final SamplerBuilder.SamplerFactory<SharedStateObjectSampler<T>> factory =
            SharedStateObjectSamplerFactory.instance();
        return new SamplerBuilder<>(
            SamplerBuilder.Specialisation.SHARED_STATE_SAMPLER, factory);
    }

    /**
     * Create a new builder for a composite {@link DiscreteSampler}.
     *
     * @return the builder
     */
    public static Builder<DiscreteSampler> newDiscreteSamplerBuilder() {
        return new SamplerBuilder<>(
            SamplerBuilder.Specialisation.NONE, DiscreteSamplerFactory.INSTANCE);
    }

    /**
     * Create a new builder for a composite {@link SharedStateDiscreteSampler}.
     *
     * @return the builder
     */
    public static Builder<SharedStateDiscreteSampler> newSharedStateDiscreteSamplerBuilder() {
        return new SamplerBuilder<>(
            SamplerBuilder.Specialisation.SHARED_STATE_SAMPLER, SharedStateDiscreteSamplerFactory.INSTANCE);
    }

    /**
     * Create a new builder for a composite {@link ContinuousSampler}.
     *
     * @return the builder
     */
    public static Builder<ContinuousSampler> newContinuousSamplerBuilder() {
        return new SamplerBuilder<>(
            SamplerBuilder.Specialisation.NONE, ContinuousSamplerFactory.INSTANCE);
    }

    /**
     * Create a new builder for a composite {@link SharedStateContinuousSampler}.
     *
     * @return the builder
     */
    public static Builder<SharedStateContinuousSampler> newSharedStateContinuousSamplerBuilder() {
        return new SamplerBuilder<>(
            SamplerBuilder.Specialisation.SHARED_STATE_SAMPLER, SharedStateContinuousSamplerFactory.INSTANCE);
    }

    /**
     * Create a new builder for a composite {@link LongSampler}.
     *
     * @return the builder
     */
    public static Builder<LongSampler> newLongSamplerBuilder() {
        return new SamplerBuilder<>(
            SamplerBuilder.Specialisation.NONE, LongSamplerFactory.INSTANCE);
    }

    /**
     * Create a new builder for a composite {@link SharedStateLongSampler}.
     *
     * @return the builder
     */
    public static Builder<SharedStateLongSampler> newSharedStateLongSamplerBuilder() {
        return new SamplerBuilder<>(
            SamplerBuilder.Specialisation.SHARED_STATE_SAMPLER, SharedStateLongSamplerFactory.INSTANCE);
    }

    /**
     * Create a copy instance of each sampler in the list of samplers using the given
     * uniform random provider as the source of randomness.
     *
     * @param <T> the type of sampler
     * @param samplers Source to copy.
     * @param rng Generator of uniformly distributed random numbers.
     * @return the copy
     */
    private static <T extends SharedStateSampler<T>> List<T> copy(List<T> samplers,
                                                                  UniformRandomProvider rng) {
        final List<T> newSamplers = new ArrayList<>(samplers.size());
        for (final T s : samplers) {
            newSamplers.add(s.withUniformRandomProvider(rng));
        }
        return newSamplers;
    }
}
