package interview;

import interviewkotlin.KotlinStringBuilderReplaceWithCodePoints;
import org.junit.BeforeClass;
import org.junit.Test;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.util.*;

public class RendererBenchmarkTest {
  private static List<Set<Entity>> entitiesList;
  private static String text = "Attend to hear 6 stellar #mobile #startups at #OF12 Entrepreneur Idol show 2day,  " +
          "http://t.co/HtzEMgAC @TiEcon @sv_entrepreneur @500!";


  @BeforeClass
  public static void setup() {
    entitiesList = createEntriesList();
  }

  @Test
  public void testClassic() throws Exception {
    Renderer renderer = new Classic();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testOptimizedClassic() throws Exception {
    Renderer renderer = new OptimizedClassic();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testOptimizedClassicWithCodePoints() throws Exception {
    Renderer renderer = new OptimizedClassicWithCodePoints();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testBinaryTree() throws Exception {
    Renderer renderer = new BinaryTree();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testLinearNodes() throws Exception {
    Renderer renderer = new LinkedListEntities();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testInsertionSort() throws Exception {
    Renderer renderer = new InsertionSort();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testBinarySearchSort() throws Exception {
    Renderer renderer = new BinarySearchSort();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testTree() throws Exception {
    Renderer renderer = new Tree();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testStringReplacement() throws Exception {
    Renderer renderer = new StringReplacement();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testStringBuilderReplace() throws Exception {
    Renderer renderer = new StringBuilderReplace();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testStringBuilderReplaceWithCodePoints() throws Exception {
    Renderer renderer = new StringBuilderReplaceWithCodePoints();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testKotlinStringBuilderReplaceWithCodePoints() throws Exception {
    Renderer renderer = new KotlinStringBuilderReplaceWithCodePoints();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testStringBuilderReplaceWithCodePointsSorted() throws Exception {
    Renderer renderer = new StringBuilderReplaceWithCodePointsAlreadySorted();
    benchSorted(renderer);
    memory(renderer);
  }

  @Test
  public void testHashMapScan() throws Exception {
    Renderer renderer = new HashMapScan();
    bench(renderer);
    memory(renderer);
  }

  @Test
  public void testArrayScan() throws Exception {
    Renderer renderer = new ArrayScan();
    bench(renderer);
    memory(renderer);
  }

  private void bench(Renderer renderer) {
    String text = "Attend to hear 6 stellar #mobile #startups at #OF12 Entrepreneur Idol show 2day,  " +
            "http://t.co/HtzEMgAC @TiEcon @sv_entrepreneur @500!";
    List<Set<Entity>> entitiesList = createEntriesList();

    {
      for (int j = 0; j < 10; j++) {
        for (int i = 0; i < 10000; i++) {
          renderer.render(text, entitiesList.get(i % 1000));
        }
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
          renderer.render(text, entitiesList.get(i % 1000));
        }
        System.out.println(renderer.getClass().getSimpleName() + ": " + (System.currentTimeMillis() - start) + " ns/op");
      }
    }
  }

  private void benchSorted(Renderer renderer) {
    String text = "Attend to hear 6 stellar #mobile #startups at #OF12 Entrepreneur Idol show 2day,  " +
            "http://t.co/HtzEMgAC @TiEcon @sv_entrepreneur @500!";
    List<Set<Entity>> entitiesList = createEntriesListSorted();

    {
      for (int j = 0; j < 5; j++) {
        for (int i = 0; i < 10000; i++) {
          renderer.render(text, entitiesList.get(i % 1000));
        }
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
          renderer.render(text, entitiesList.get(i % 1000));
        }
        System.out.println(renderer.getClass().getSimpleName() + ": " + (System.currentTimeMillis() - start) + " ns/op");
      }
    }
  }

  private void memory(Renderer renderer) {
    System.gc();
    MemoryMXBean mxbean = ManagementFactory.getMemoryMXBean();
    {
      long total = 0;
      for (int i = 0; i < 1000000; i++) {
        long start = mxbean.getHeapMemoryUsage().getUsed();
        renderer.render(text, entitiesList.get(i % 1000));
        long end = mxbean.getHeapMemoryUsage().getUsed();
        long diff = end - start;
        if (diff > 0) {
          total += diff;
        }
      }
      System.out.println("Memory: " + renderer.getClass().getSimpleName() + ": " + total / 1000000 + " bytes/op");
    }

  }

  private static List<Set<Entity>> createEntriesList() {
    Random r = new Random(938471093847L);
    List<Set<Entity>> entitiesList = new ArrayList<Set<Entity>>();
    for (int i = 0; i < 1000; i++) {
      Set<Entity> entities = new HashSet<Entity>();
      int total = r.nextInt(10);
      List<Integer> indices = new ArrayList<Integer>();
      for (int j = 0; j < total * 2; j++) {
        int next;
        while (indices.contains(next = r.nextInt(text.length()))) ;
        indices.add(next);
      }
      Collections.sort(indices);
      for (int j = 0; j < total * 2; j += 2) {
        int start = indices.get(j);
        int end = indices.get(j + 1);
        int length = end - start;
        StringBuilder sb = new StringBuilder(length * 2);
        for (int k = 0; k < length; k++) {
          sb.append("XX");
        }
        entities.add(new Entity(start, end, sb.toString()));
      }
      entitiesList.add(entities);
    }
    return entitiesList;
  }

  private static List<Set<Entity>> createEntriesListSorted() {
    Random r = new Random(938471093847L);
    List<Set<Entity>> entitiesList = new ArrayList<Set<Entity>>();
    for (int i = 0; i < 1000; i++) {
      Set<Entity> entities = new TreeSet<Entity>();
      int total = r.nextInt(10);
      List<Integer> indices = new ArrayList<Integer>();
      for (int j = 0; j < total * 2; j++) {
        int next;
        while (indices.contains(next = r.nextInt(text.length()))) ;
        indices.add(next);
      }
      Collections.sort(indices);
      for (int j = 0; j < total * 2; j += 2) {
        int start = indices.get(j);
        int end = indices.get(j + 1);
        int length = end - start;
        StringBuilder sb = new StringBuilder(length * 2);
        for (int k = 0; k < length; k++) {
          sb.append("XX");
        }
        entities.add(new Entity(start, end, sb.toString()));
      }
      entitiesList.add(entities);
    }
    return entitiesList;
  }
}