/*
 * 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.lucene.spatial.composite;

import static com.carrotsearch.randomizedtesting.RandomizedTest.randomBoolean;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomDouble;
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomIntBetween;

import java.io.IOException;
import org.apache.lucene.spatial.prefix.RandomSpatialOpStrategyTestCase;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.query.SpatialOperation;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.SpatialContextFactory;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.impl.RectangleImpl;

public class TestCompositeStrategy extends RandomSpatialOpStrategyTestCase {

  private SpatialPrefixTree grid;
  private RecursivePrefixTreeStrategy rptStrategy;

  private void setupQuadGrid(int maxLevels) {
    // non-geospatial makes this test a little easier (in gridSnap), and using boundary values 2^X
    // raises
    // the prospect of edge conditions we want to test, plus makes for simpler numbers (no
    // decimals).
    SpatialContextFactory factory = new SpatialContextFactory();
    factory.geo = false;
    factory.worldBounds = new RectangleImpl(0, 256, -128, 128, null);
    this.ctx = factory.newSpatialContext();
    // A fairly shallow grid
    if (maxLevels == -1) maxLevels = randomIntBetween(1, 8); // max 64k cells (4^8), also 256*256
    this.grid = new QuadPrefixTree(ctx, maxLevels);
    this.rptStrategy = newRPT();
  }

  private void setupGeohashGrid(int maxLevels) {
    this.ctx = SpatialContext.GEO;
    // A fairly shallow grid
    if (maxLevels == -1) maxLevels = randomIntBetween(1, 3); // max 16k cells (32^3)
    this.grid = new GeohashPrefixTree(ctx, maxLevels);
    this.rptStrategy = newRPT();
  }

  protected RecursivePrefixTreeStrategy newRPT() {
    final RecursivePrefixTreeStrategy rpt =
        new RecursivePrefixTreeStrategy(this.grid, getClass().getSimpleName() + "_rpt");
    rpt.setDistErrPct(0.10); // not too many cells
    return rpt;
  }

  @Test
  public void testOperations() throws IOException {
    // setup
    if (randomBoolean()) {
      setupQuadGrid(-1);
    } else {
      setupGeohashGrid(-1);
    }
    SerializedDVStrategy serializedDVStrategy =
        new SerializedDVStrategy(ctx, getClass().getSimpleName() + "_sdv");
    this.strategy =
        new CompositeSpatialStrategy(
            "composite_" + getClass().getSimpleName(), rptStrategy, serializedDVStrategy);

    // Do it!

    for (SpatialOperation pred : SpatialOperation.values()) {
      if (pred == SpatialOperation.BBoxIntersects || pred == SpatialOperation.BBoxWithin) {
        continue;
      }
      if (pred == SpatialOperation.IsDisjointTo) { // TODO
        continue;
      }
      testOperationRandomShapes(pred);
      deleteAll();
      commit();
    }
  }

  @Override
  protected Shape randomIndexedShape() {
    return randomShape();
  }

  @Override
  protected Shape randomQueryShape() {
    return randomShape();
  }

  private Shape randomShape() {
    return random().nextBoolean() ? randomCircle() : randomRectangle();
  }

  // TODO move up
  private Shape randomCircle() {
    final Point point = randomPoint();
    // TODO pick using gaussian
    double radius;
    if (ctx.isGeo()) {
      radius = randomDouble() * 100;
    } else {
      // find distance to closest edge
      final Rectangle worldBounds = ctx.getWorldBounds();
      double maxRad = point.getX() - worldBounds.getMinX();
      maxRad = Math.min(maxRad, worldBounds.getMaxX() - point.getX());
      maxRad = Math.min(maxRad, point.getY() - worldBounds.getMinY());
      maxRad = Math.min(maxRad, worldBounds.getMaxY() - point.getY());
      radius = randomDouble() * maxRad;
    }

    return ctx.makeCircle(point, radius);
  }
}
