package labs.applications;

import static org.junit.jupiter.api.Assertions.*;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Random;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;


/**
 * Correctness and performance tests for Applications.NeighborCounter
 *
 * @author Nathan Sprague
 *
 */
@TestMethodOrder(OrderAnnotation.class)
class NeighborCounterTest {

private Random gen;

  @BeforeEach
  void setUp() throws Exception {
    gen = new Random(100101);
  }

  @Test
  @Order(1)
  void testCorrectness() {

    Applications.NeighborCounter nc = new Applications.NeighborCounter();

    // Create a small grid of points
    for (double x = -.1; x < .11; x +=.1) {
      for (double y = -.1; y < .11; y +=.1) {
        nc.addPoint(x, y);
      }
    }

    assertEquals(0, nc.numNeighbors(5.0, 5.0, .1));

    assertEquals(1, nc.numNeighbors(0, 0, .09));
    assertEquals(9, nc.numNeighbors(0, 0, .15));
    assertEquals(5, nc.numNeighbors(0, 0, .14));

    assertEquals(1, nc.numNeighbors(.12, 0, .03));
    assertEquals(1, nc.numNeighbors(-.12, 0, .03));
    assertEquals(1, nc.numNeighbors(0, .12, .03));
    assertEquals(1, nc.numNeighbors(0, -.12, .03));

    assertEquals(4, nc.numNeighbors(.05, .05, .08));
    assertEquals(4, nc.numNeighbors(-.05, -.05, .08));

    assertEquals(0, nc.numNeighbors(.05, .05, .07));
    assertEquals(0, nc.numNeighbors(-.05, -.05, .07));

    nc.addPoint(.05, .05);
    assertEquals(1, nc.numNeighbors(.05, .05, .07));


  }

  /**
   * Generate random points.
   */
  public ArrayList<Point2D.Double> createPoints(int howMany) {

    ArrayList<Point2D.Double> result = new ArrayList<>();

    for (int i = 0; i < howMany; i++) {
      result.add(new Point2D.Double(gen.nextDouble(), gen.nextDouble()));
    }

    return result;
  }


  @Test
  @Order(2)
  void testPerformance() {

    int numPoints = 500000;
    int numQueries = 10000;
    long addTime = 0;
    long lookupTime = 0;

    ArrayList<Point2D.Double> points = createPoints(numPoints);
    ArrayList<Point2D.Double> queries = createPoints(numQueries);

    Applications.NeighborCounter nc = new Applications.NeighborCounter();

    System.out.println("TIMING ADDS...");

    long start = System.nanoTime();
    for (Point2D.Double point: points) {
      nc.addPoint(point.x, point.y);
    }
    addTime = System.nanoTime() - start;

    System.out.println("TIMING NEIGHBOR COUNTS...");
    start = System.nanoTime();
    for (Point2D.Double point: queries) {
      nc.numNeighbors(point.x, point.y, .001);
    }
    lookupTime = System.nanoTime() - start;


    System.out.printf("TOTAL TIME: %.2f seconds.\n", (addTime + lookupTime) / 1000000000.0);

    System.out.printf(
        "Add:        %8d operations performed in %4.2f seconds, %8.2f microseconds per call.\n",
        numPoints, addTime / 1000000000.0, addTime / 1000.0 / numPoints);

    System.out.printf(
        "Lookups: %8d operations performed in %4.2f seconds, %8.2f microseconds per call.\n",
       numQueries, lookupTime / 1000000000.0, lookupTime / 1000.0 / numQueries);
  }

}
