/*
 * 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 accord.utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.ToIntBiFunction;

import javax.annotation.Nullable;

import com.google.common.collect.Iterators;

import accord.api.Key;
import accord.api.RoutingKey;
import accord.impl.IntHashKey;
import accord.impl.IntKey;
import accord.impl.PrefixedIntHashKey;
import accord.local.Command;
import accord.local.Node;
import accord.local.RedundantBefore;
import accord.primitives.Ballot;
import accord.primitives.Deps;
import accord.primitives.KeyDeps;
import accord.primitives.Keys;
import accord.primitives.Range;
import accord.primitives.RangeDeps;
import accord.primitives.Ranges;
import accord.primitives.Routable.Domain;
import accord.primitives.RoutableKey;
import accord.primitives.Timestamp;
import accord.primitives.Txn;
import accord.primitives.TxnId;
import accord.primitives.TxnId.Cardinality;
import accord.topology.Shard;
import accord.topology.Topology;
import accord.utils.SortedArrays.SortedArrayList;
import org.agrona.collections.IntHashSet;

import static accord.primitives.TxnId.Cardinality.Any;
import static accord.utils.Utils.toArray;

public class AccordGens
{
    public static Gen.LongGen epochs()
    {
        return Gens.longs().between(0, Timestamp.MAX_EPOCH);
    }

    public static Gen.LongGen epochs(long min)
    {
        return Gens.longs().between(min, Timestamp.MAX_EPOCH);
    }

    public static Gen<Node.Id> nodes()
    {
        return nodes(nodeIdValues());
    }

    public static Gen.IntGen nodeIdValues()
    {
        return rs -> rs.nextInt(-1, Integer.MAX_VALUE) + 1;
    }

    public static Gen<Node.Id> nodes(Gen.IntGen nodes)
    {
        return nodes.map(Node.Id::new);
    }

    public static Gen.IntGen flags()
    {
        return rs -> rs.nextInt(0, 1 << 16);
    }

    public static Gen.LongGen hlcs()
    {
        return rs -> rs.nextLong(0, Long.MAX_VALUE);
    }

    public static Gen.LongGen hlcs(long min)
    {
        return rs -> rs.nextLong(0, Long.MAX_VALUE);
    }

    public static Gen<Timestamp> timestamps()
    {
        return timestamps(epochs()::nextLong, hlcs(), flags(), nodeIdValues());
    }

    public static Gen<Timestamp> timestamps(Gen.LongGen epochs, Gen.LongGen hlcs, Gen.IntGen flags, Gen.IntGen nodes)
    {
        return rs -> Timestamp.fromValues(epochs.nextLong(rs), hlcs.nextLong(rs), flags.nextInt(rs), new Node.Id(nodes.nextInt(rs)));
    }

    public static Gen<TxnId> txnIds()
    {
        return txnIds(Gens.enums().all(Txn.Kind.class));
    }

    public static Gen<TxnId> txnIds(Gen<Txn.Kind> kinds)
    {
        return txnIds(epochs(), hlcs(), nodeIdValues(), kinds);
    }

    public static Gen<TxnId> txnIds(Gen<Txn.Kind> kinds, Gen<Domain> domains)
    {
        return txnIds(epochs(), hlcs(), nodeIdValues(), kinds, domains, ignore -> Any);
    }

    public static Gen<TxnId> txnIds(Gen<Txn.Kind> kinds, Gen<Domain> domains, Gen<Cardinality> cardinalities)
    {
        return txnIds(epochs(), hlcs(), nodeIdValues(), kinds, domains, cardinalities);
    }

    public static Gen<TxnId> txnIds(Gen.LongGen epochs, Gen.LongGen hlcs, Gen.IntGen nodes)
    {
        return txnIds(epochs, hlcs, nodes, Gens.enums().all(Txn.Kind.class));
    }

    public static Gen<TxnId> txnIds(Gen.LongGen epochs, Gen.LongGen hlcs, Gen.IntGen nodes, Gen<Txn.Kind> kinds)
    {
        return txnIds(epochs, hlcs, nodes, kinds, Gens.enums().all(Domain.class), ignore -> Any);
    }

    public static Gen<TxnId> txnIds(Gen.LongGen epochs, Gen.LongGen hlcs, Gen.IntGen nodes, Gen<Txn.Kind> kinds, Gen<Domain> domains)
    {
        return txnIds(epochs, hlcs, nodes, kinds, domains, ignore -> Any);
    }

    public static Gen<TxnId> txnIds(Gen.LongGen epochs, Gen.LongGen hlcs, Gen.IntGen nodes, Gen<Txn.Kind> kinds, Gen<Domain> domains, Gen<Cardinality> cardinalities)
    {
        return rs -> {
            Txn.Kind kind = kinds.next(rs);
            Domain domain = kind.isSyncPoint() ? Domain.Range : domains.next(rs);
            Cardinality cardinality = domain == Domain.Range ? Any : cardinalities.next(rs);
            return new TxnId(epochs.nextLong(rs), hlcs.nextLong(rs), 0, kind, domain, cardinality, new Node.Id(nodes.nextInt(rs)));
        };
    }

    public static Gen<Ballot> ballot()
    {
        return ballot(epochs()::nextLong, hlcs(), flags(), nodeIdValues());
    }

    public static Gen<Ballot> ballot(Gen.LongGen epochs, Gen.LongGen hlcs, Gen.IntGen flags, Gen.IntGen nodes)
    {
        return rs -> Ballot.fromValues(epochs.nextLong(rs), hlcs.nextLong(rs), flags.nextInt(rs), new Node.Id(nodes.nextInt(rs)));
    }

    public static Gen<Key> intKeys()
    {
        return intKeys(RandomSource::nextInt);
    }

    public static Gen<Key> intKeys(Gen.IntGen keyGen)
    {
        return rs -> new IntKey.Raw(keyGen.nextInt(rs));
    }

    public static Gen<Key> intKeysInsideRanges(Ranges ranges)
    {
        return rs -> {
            Range range = ranges.get(rs.nextInt(0, ranges.size()));
            int start = intKey(range.start());
            int end = intKey(range.end());
            // end inclusive, so +1 the result to include end and exclude start
            return IntKey.key(rs.nextInt(start, end) + 1);
        };
    }

    public static Gen<Key> intKeysOutsideRanges(Ranges ranges)
    {
        return rs -> {
            for (int i = 0; i < 10; i++)
            {
                int index = rs.nextInt(0, ranges.size());
                Range first = index == 0 ? null : ranges.get(index - 1);
                Range second = ranges.get(index);
                // retries
                for (int j = 0; j < 10; j++)
                {
                    Key key = intKeyOutside(rs, first, second);
                    if (key == null)
                        continue;
                    return key;
                }
            }
            throw new AssertionError("Unable to find keys within the range " + ranges);
        };
    }

    private static Key intKeyOutside(RandomSource rs, @Nullable Range first, Range second)
    {
        int start;
        int end;
        if (first == null)
        {
            start = Integer.MIN_VALUE;
            end = intKey(second.start()); // start is not inclusive, so can use
        }
        else
        {
            start = intKey(first.end()) + 1; // end is inclusive, so +1 to skip
            end = intKey(second.start()); // start is not inclusive, so can use
        }
        if (start == end)
            return null;
        return IntKey.key(rs.nextInt(start, end + 1));
    }

    private static int intKey(RoutableKey key)
    {
        return ((IntKey.Routing) key).key;
    }

    public static Gen<IntKey.Routing> intRoutingKey()
    {
        return rs -> IntKey.routing(rs.nextInt());
    }

    public static Gen<Key> intHashKeys()
    {
        return rs -> IntHashKey.key(rs.nextInt());
    }

    public static Gen<Key> prefixedIntHashKey()
    {
        return prefixedIntHashKey(RandomSource::nextInt, rs -> rs.nextInt(PrefixedIntHashKey.MIN_KEY, Integer.MAX_VALUE));
    }

    public static Gen<Key> prefixedIntHashKey(Gen.IntGen prefixGen)
    {
        return prefixedIntHashKey(prefixGen, rs -> rs.nextInt(PrefixedIntHashKey.MIN_KEY, Integer.MAX_VALUE));
    }

    public static Gen<Key> prefixedIntHashKey(Gen.IntGen prefixGen, Gen.IntGen keyGen)
    {
        return rs -> {
            int prefix = prefixGen.nextInt(rs);
            int key = keyGen.nextInt(rs);
            int hash = PrefixedIntHashKey.hash(key);
            return PrefixedIntHashKey.key(prefix, key, hash);
        };
    }

    public static Gen<Key> prefixedIntHashKeyInsideRanges(Ranges ranges)
    {
        return rs -> {
            Range range = ranges.get(rs.nextInt(0, ranges.size()));
            PrefixedIntHashKey start = (PrefixedIntHashKey) range.start();
            PrefixedIntHashKey end = (PrefixedIntHashKey) range.end();
            // end inclusive, so +1 the result to include end and exclude start
            int hash = rs.nextInt(start.hash, end.hash) + 1;
            int key = CRCUtils.reverseCRC32LittleEnding(hash);
            PrefixedIntHashKey.Key ret = PrefixedIntHashKey.key(start.prefix, key, hash);
            // we have tests to make sure this doesn't fail... just a safety check
            assert ret.hash == hash;
            return ret;
        };
    }

    public static Gen<Key> keysInsideRanges(Ranges ranges)
    {
        Invariants.requireArgument(!ranges.isEmpty(), "Ranges empty");
        RoutingKey sample = ranges.get(0).end();
        if (sample instanceof PrefixedIntHashKey)
            return prefixedIntHashKeyInsideRanges(ranges);
        if (sample instanceof IntKey.Routing)
            return intKeysInsideRanges(ranges);
        throw new IllegalArgumentException("Unsupported key type " + sample.getClass() + "; supported = PrefixedIntHashKey, IntKey");
    }

    public static Gen<KeyDeps> keyDeps(Gen<? extends RoutingKey> keyGen)
    {
        Gen<Txn.Kind> kinds = Gens.pick(Txn.Kind.Write, Txn.Kind.Read);
        return keyDeps(keyGen, txnIds(kinds, ignore -> Domain.Key));
    }

    public static Gen<KeyDeps> directKeyDeps(Gen<? extends RoutingKey> keyGen)
    {
        Gen<Txn.Kind> kinds = Gens.pick(Txn.Kind.ExclusiveSyncPoint);
        return keyDeps(keyGen, txnIds(kinds, ignore -> Domain.Key));
    }

    public static Gen<KeyDeps> keyDeps(Gen<? extends RoutingKey> keyGen, Gen<TxnId> idGen)
    {
        double emptyProb = .2D;
        return rs -> {
            if (rs.decide(emptyProb)) return KeyDeps.NONE;
            Set<RoutingKey> seenKeys = new HashSet<>();
            Set<TxnId> seenTxn = new HashSet<>();
            Gen<? extends RoutingKey> uniqKeyGen = keyGen.filter(42, null, seenKeys::add);
            Gen<TxnId> uniqIdGen = idGen.filter(42, null, seenTxn::add);
            try (KeyDeps.Builder builder = KeyDeps.builder())
            {
                for (int i = 0, numKeys = rs.nextInt(1, 10); i < numKeys; i++)
                {
                    RoutingKey next = uniqKeyGen.next(rs);
                    if (next == null)
                        break;
                    builder.nextKey(next);
                    seenTxn.clear();
                    for (int j = 0, numTxn = rs.nextInt(1, 10); j < numTxn; j++)
                    {
                        TxnId txnId = uniqIdGen.next(rs);
                        if (txnId == null)
                            break;
                        builder.add(txnId);
                    }
                }
                return builder.build();
            }
        };
    }

    public static Gen<Shard> shards(Gen<Range> rangeGen, Gen<SortedArrayList<Node.Id>> nodesGen)
    {
        return rs -> {
            Range range = rangeGen.next(rs);
            SortedArrayList<Node.Id> nodes = nodesGen.next(rs);
            int maxFailures = (nodes.size() - 1) / 2;
            Set<Node.Id> fastPath = new HashSet<>();
            if (maxFailures == 0)
            {
                fastPath.addAll(nodes);
            }
            else
            {
                int minElectorate = nodes.size() - maxFailures;
                for (int i = 0, size = rs.nextInt(minElectorate, nodes.size()); i < size; i++)
                {
                    //noinspection StatementWithEmptyBody
                    while (!fastPath.add(nodes.get(rs.nextInt(nodes.size()))));
                }
            }
            Set<Node.Id> joining = new HashSet<>();
            for (int i = 0, size = rs.nextInt(nodes.size()); i < size; i++)
            {
                //noinspection StatementWithEmptyBody
                while (!joining.add(nodes.get(rs.nextInt(nodes.size()))));
            }
            return Shard.create(range, nodes, fastPath);
        };
    }

    public static Gen<Topology> topologys()
    {
        return topologys(epochs(), nodes());
    }

    public static Gen<Topology> topologys(Gen.LongGen epochGen)
    {
        return topologys(epochGen, nodes());
    }

    public static Gen<Topology> topologys(Gen.LongGen epochGen, Gen<Node.Id> nodeGen)
    {
        return topologys(epochGen, nodeGen, AccordGens::prefixedIntHashKeyRanges);
    }

    public static Gen<Topology> topologys(Gen.LongGen epochGen, Gen<Node.Id> nodeGen, RangesGenFactory rangesGenFactory)
    {
        return rs -> {
            long epoch = epochGen.nextLong(rs);
            float chance = rs.nextFloat();
            int rf;
            if (chance < 0.2f)      { rf = rs.nextInt(2, 9); }
            else if (chance < 0.4f) { rf = 3; }
            else if (chance < 0.7f) { rf = 5; }
            else if (chance < 0.8f) { rf = 7; }
            else                    { rf = 9; }
            Node.Id[] nodes = Utils.toArray(Gens.lists(nodeGen).unique().ofSizeBetween(rf, rf * 3).next(rs), Node.Id[]::new);
            Ranges ranges = rangesGenFactory.apply(nodes.length, rf).next(rs);

            int numElectorate = nodes.length + rf - 1;
            List<WrapAroundList<Node.Id>> electorates = new ArrayList<>(numElectorate);
            for (int i = 0; i < numElectorate; i++)
                electorates.add(new WrapAroundList<>(nodes, i % nodes.length, (i + rf) % nodes.length));

            final List<Shard> shards = new ArrayList<>();
            Set<Node.Id> noShard = new HashSet<>(Arrays.asList(nodes));
            for (int i = 0; i < ranges.size() ; ++i)
            {
                WrapAroundList<Node.Id> replicas = electorates.get(i % electorates.size());
                SortedArrayList<Node.Id> sortedIds = SortedArrayList.copyUnsorted(replicas, Node.Id[]::new);
                Range range = ranges.get(i);
                shards.add(shards(ignore -> range, ignore -> sortedIds).next(rs));
                replicas.forEach(noShard::remove);
            }
            if (!noShard.isEmpty())
                throw new AssertionError(String.format("The following electorates were found without a shard: %s", noShard));

            return new Topology(epoch, toArray(shards, Shard[]::new));
        };
    }

    public interface RangesGenFactory
    {
        Gen<Ranges> apply(int nodeCount, int rf);
    }

    public interface RangeFactory<T extends RoutingKey>
    {
        Range create(RandomSource rs, T a, T b);
    }

    public static Gen<Range> ranges(Gen<? extends RoutingKey> keyGen, BiFunction<? super RoutingKey, ? super RoutingKey, ? extends Range> factory)
    {
        return ranges(keyGen, (ignore, a, b) -> factory.apply(a, b));
    }

    public static Gen<Range> ranges(Gen<? extends RoutingKey> keyGen)
    {
        return ranges(keyGen, (rs, a, b) -> {
            return Range.of(a, b);
        });
    }

    public static <T extends RoutingKey> Gen<Range> ranges(Gen<T> keyGen, RangeFactory<T> factory)
    {
        List<T> keys = Arrays.asList(null, null);
        return rs -> {
            keys.set(0, keyGen.next(rs));
            // range doesn't allow a=b
            do keys.set(1, keyGen.next(rs));
            while (Objects.equals(keys.get(0), keys.get(1)));
            keys.sort(Comparator.naturalOrder());
            return factory.create(rs, keys.get(0), keys.get(1));
        };
    }

    public static <T extends RoutingKey> Gen<Range> prefixRanges(Gen<? extends Gen<? extends T>> keyGen)
    {
        List<T> keys = Arrays.asList(null, null);
        return rs -> {
            Gen<? extends T> gen = keyGen.next(rs);
            keys.set(0, gen.next(rs));
            // range doesn't allow a=b
            do keys.set(1, gen.next(rs));
            while (Objects.equals(keys.get(0), keys.get(1)));
            keys.sort(Comparator.naturalOrder());
            return Range.of(keys.get(0), keys.get(1));
        };
    }

    public static <T extends RoutingKey> Gen<Ranges> ranges(Gen.IntGen sizeGen, Gen<T> keyGen, RangeFactory<T> factory)
    {
        Gen<Range> rangeGen = ranges(keyGen, factory);
        return rs -> {
            int size = sizeGen.nextInt(rs);
            Range[] ranges = new Range[size];
            for (int i = 0; i < size; i++)
                ranges[i] = rangeGen.next(rs);
            return Ranges.of(ranges);
        };
    }

    public static <T extends RoutingKey> Gen<Ranges> prefixRanges(Gen.IntGen sizeGen, Gen<Gen<T>> keyGen)
    {
        Gen<Range> rangeGen = prefixRanges(keyGen);
        return rs -> {
            int size = sizeGen.nextInt(rs);
            Range[] ranges = new Range[size];
            for (int i = 0; i < size; i++)
                ranges[i] = rangeGen.next(rs);
            return Ranges.of(ranges);
        };
    }

    public static <T extends RoutingKey> Gen<Ranges> ranges(Gen.IntGen sizeGen, Gen<T> keyGen, BiFunction<? super T, ? super T, ? extends Range> factory)
    {
        return ranges(sizeGen, keyGen, (ignore, a, b) -> factory.apply(a, b));
    }

    public static Gen<Range> rangeInsideRange(Range range)
    {
        if (range.end() instanceof PrefixedIntHashKey)
            return prefixedIntHashKeyRangeInsideRange(range);
        throw new IllegalArgumentException("Unsupported type: " + range.start().getClass());
    }

    public static Gen<Ranges> rangesInsideRanges(Ranges ranges, ToIntBiFunction<RandomSource, Range> numSplits)
    {
        if (ranges.isEmpty())
            return ignore -> ranges;
        List<Gen<Range>> subsets = new ArrayList<>(ranges.size());
        ranges.forEach(r -> subsets.add(rangeInsideRange(r)));
        return rs -> {
            List<Range> result = new ArrayList<>(subsets.size());
            for (int i = 0; i < subsets.size(); i++)
            {
                Range range = ranges.get(i);
                int splits = numSplits.applyAsInt(rs, range);
                if (splits < 0) throw new IllegalArgumentException("numSplits is less than 0: given " + splits);
                if (splits == 0) continue;
                Gen<Range> gen = subsets.get(i);
                for (int s = 0; s < splits; s++)
                    result.add(gen.next(rs));
            }
            return Ranges.of(result.toArray(Range[]::new));
        };
    }

    public static Gen<Range> prefixedIntHashKeyRangeInsideRange(Range range)
    {
        if (!(range.end() instanceof PrefixedIntHashKey))
            throw new IllegalArgumentException("Only PrefixedIntHashKey supported; saw " + range.end().getClass());
        PrefixedIntHashKey start = (PrefixedIntHashKey) range.start();
        PrefixedIntHashKey end = (PrefixedIntHashKey) range.end();
        if (start.hash + 1 == end.hash)
        {
            // range is of size 1, so can not split into a smaller range...
            return ignore -> range;
        }
        return rs -> {
            int a = rs.nextInt(start.hash, end.hash);
            int b = rs.nextInt(start.hash, end.hash);
            while (a == b)
                b = rs.nextInt(start.hash, end.hash);
            if (a > b)
            {
                int tmp = a;
                a = b;
                b = tmp;
            }
            return PrefixedIntHashKey.range(start.prefix, a, b);
        };
    }

    public static Gen<Ranges> prefixedIntHashKeyRanges(int numNodes, int rf)
    {
        return rs -> {
            int numPrefixes = rs.nextInt(1, 10);
            List<Range> ranges = new ArrayList<>(numPrefixes * numNodes * rf);
            IntHashSet prefixes = new IntHashSet();
            for (int i = 0; i < numPrefixes; i++)
            {
                int prefix;
                //noinspection StatementWithEmptyBody
                while (!prefixes.add(prefix = rs.nextInt(0, 100)));
                ranges.addAll(Arrays.asList(PrefixedIntHashKey.ranges(prefix, numNodes * rf)));
            }
            ranges.sort(Range::compare);
            return Ranges.ofSortedAndDeoverlapped(Utils.toArray(ranges, Range[]::new));
        };
    }

    public static Gen<RangeDeps> rangeDeps(Gen<? extends Range> rangeGen)
    {
        Gen<Txn.Kind> kinds = Gens.pick(Txn.Kind.Write, Txn.Kind.Read, Txn.Kind.ExclusiveSyncPoint);
        return rangeDeps(rangeGen, txnIds(kinds, ignore -> Domain.Range));
    }

    public static Gen<RangeDeps> rangeDeps(Gen<? extends Range> rangeGen, Gen<TxnId> idGen)
    {
        double emptyProb = .2D;
        return rs -> {
            if (rs.decide(emptyProb)) return RangeDeps.NONE;
            RangeDeps.BuilderByRange builder = RangeDeps.builderByRange();
            List<? extends Range> uniqRanges = Gens.lists(rangeGen).uniqueBestEffort().ofSize(rs.nextInt(1, 10)).next(rs);
            for (Range range : uniqRanges)
            {
                builder.nextKey(range);
                for (int j = 0, numTxn = rs.nextInt(1, 10); j < numTxn; j++)
                    builder.add(idGen.next(rs));
            }
            return builder.build();
        };
    }

    public static Gen<Deps> deps(Gen<KeyDeps> keyDepsGen, Gen<RangeDeps> rangeDepsGen)
    {
        return rs -> new Deps(keyDepsGen.next(rs), rangeDepsGen.next(rs));
    }

    public static Gen<Deps> depsFromKey(Gen<? extends RoutingKey> keyGen, Gen<? extends Range> rangeGen, Gen<? extends RoutingKey> directKeyGen)
    {
        return deps(keyDeps(keyGen), rangeDeps(rangeGen));
    }

    public static Gen<Deps> depsFromKey(Gen<? extends RoutingKey> keyGen, Gen<? extends Range> rangeGen)
    {
        return depsFromKey(keyGen, rangeGen, keyGen);
    }

    public static Gen<Deps> depsFor(TxnId txnId, Txn txn)
    {
        Gen<KeyDeps> keyDepsGen;
        Gen<RangeDeps> rangeDepsGen;
        Gen<KeyDeps> directKeyDepsGen;
        switch (txnId.kind())
        {
            case Write:
            case Read:
            case EphemeralRead:
            {
                Gen<? extends RoutingKey> keyGen = Gens.pick(Iterators.toArray((((Keys) txn.keys()).toParticipants()).iterator(), RoutingKey.class));
                keyDepsGen = AccordGens.keyDeps(keyGen, AccordGens.txnIds(Gens.longs().between(0, txnId.epoch()),
                                                                          Gens.longs().between(0, txnId.hlc()),
                                                                          nodeIdValues(),
                                                                          Gens.pick(Txn.Kind.Write, Txn.Kind.Read),
                                                                          ignore -> Domain.Key));
                rangeDepsGen = i -> RangeDeps.NONE;
                directKeyDepsGen = i -> KeyDeps.NONE;
            }
            break;
            case ExclusiveSyncPoint:
            case VisibilitySyncPoint:
                //TODO (coverage, now):
                keyDepsGen = i -> KeyDeps.NONE;
                rangeDepsGen = i -> RangeDeps.NONE;
                directKeyDepsGen = i -> KeyDeps.NONE;
                break;
            default:throw new UnsupportedOperationException(txn.kind().name());
        }
        return AccordGens.deps(keyDepsGen, rangeDepsGen);
    }

    public static Gen<Command.WaitingOn> waitingOn(Gen<Deps> depsGen, Gen<Boolean> emptyGen,
                                                   Gen<Boolean> rangeSetGen,
                                                   Gen<Boolean> directKeySetGen,
                                                   Gen<Boolean> keySetGen)
    {
        return rs -> {
            Deps deps = depsGen.next(rs);
            if (deps.isEmpty()) return Command.WaitingOn.empty(Domain.Key);
            int size = deps.rangeDeps.txnIdCount() + deps.keyDeps.keys().size();
            LargeBitSet set = new LargeBitSet(size);
            int keyOffset = deps.rangeDeps.txnIdCount();
            for (int i = 0; i < size; i++)
            {
                Gen<Boolean> gen = i < keyOffset ? rangeSetGen : keySetGen;
                if (gen.next(rs)) set.set(i);
            }
            return new Command.WaitingOn(deps.keyDeps.keys(), deps.rangeDeps, new ImmutableBitSet(set), null);
        };
    }

    public static Gen<RedundantBefore> redundantBefore(Gen<Ranges> rangesGen,
                                                       BiFunction<RandomSource, Range, RedundantBefore.Bounds> entryGen)
    {
        return rs -> {
            Ranges ranges = rangesGen.next(rs);
            if (ranges.isEmpty()) return RedundantBefore.EMPTY;
            RedundantBefore.Builder builder = new RedundantBefore.Builder(ranges.size());
            ranges.forEach(r -> builder.append(r.start(), r.end(), entryGen.apply(rs, r)));
            return builder.build();
        };
    }
}
