package org.alicep.collect;

import static org.alicep.collect.ItemFactory.longs;
import static org.alicep.collect.ItemFactory.strings;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Supplier;

import org.alicep.collect.benchmark.BenchmarkRunner;
import org.alicep.collect.benchmark.BenchmarkRunner.Benchmark;
import org.alicep.collect.benchmark.BenchmarkRunner.Configuration;
import org.alicep.collect.benchmark.BenchmarkRunner.InterferenceWarning;
import org.junit.runner.RunWith;

import com.google.common.collect.ImmutableList;

@RunWith(BenchmarkRunner.class)
public class SmallSetPerformanceTests<T> {

  static class Config<T> {
    private final Supplier<Set<T>> setFactory;
    private final ItemFactory<T> itemFactory;

    public Config(
        Supplier<Set<T>> setFactory,
        ItemFactory<T> itemFactory) {
      this.setFactory = setFactory;
      this.itemFactory = itemFactory;
    }

    @Override
    public String toString() {
      String setType = setFactory.get().getClass().getSimpleName();
      String itemType = itemFactory.createItem(0).getClass().getSimpleName();
      return setType + "<" + itemType + ">";
    }
  }

  @Configuration
  public static final ImmutableList<Config<?>> CONFIGURATIONS = ImmutableList.of(
      new Config<>(HashSet::new, longs),
      new Config<>(LinkedHashSet::new, longs),
      new Config<>(ArraySet::new, longs),
      new Config<>(HashSet::new, strings),
      new Config<>(LinkedHashSet::new, strings),
      new Config<>(ArraySet::new, strings));

  private final Supplier<Set<T>> setFactory;
  private final Set<T> littleSet;

  @SuppressWarnings("unchecked")
  private final T[] items = (T[]) new Object[500];

  int i = 0;

  public SmallSetPerformanceTests(Config<T> config) {
    setFactory = config.setFactory;

    for (int i = 0; i < 500; ++i) {
      items[i] = config.itemFactory.createItem(i);
    }
    littleSet = setFactory.get();
    for (int i = 0; i < 6; i++) {
      littleSet.add(items[i]);
    }
  }

  @Benchmark("Create a 6-element set")
  public void create() {
    Set<T> set = setFactory.get();
    for (int i = 0; i < 6; ++i) {
      set.add(items[i]);
    }
  }

  @Benchmark("Iterate through a 6-element set")
  @InterferenceWarning  // Hitting java.lang.Iterable.forEach, which cannot be cloned
  public void iterate() {
    littleSet.forEach(e -> assertNotNull(e));
  }

  @Benchmark("Hit in a 6-element set")
  public void hit() {
    assertTrue(littleSet.contains(items[i]));
    if (++i == 6) i = 0;
  }

  @Benchmark("Miss in a 6-element set")
  public void miss() {
    assertFalse(littleSet.contains(items[i + 6]));
    if (++i + 6 == items.length) {
      i = 0;
    }
  }
}

@@ -1865,16 +1865,17 @@
 ject%5B500
+0
 %5D;%0A%0A  in
@@ -1999,19 +1999,28 @@
  0; i %3C 
-500
+items.length
 ; ++i) %7B

Create 5000, not 500, items