/*
 * 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;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.apache.lucene.spatial.bbox.BBoxStrategy;
import org.apache.lucene.spatial.prefix.RecursivePrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.TermQueryPrefixTreeStrategy;
import org.apache.lucene.spatial.prefix.tree.GeohashPrefixTree;
import org.apache.lucene.spatial.prefix.tree.PackedQuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.QuadPrefixTree;
import org.apache.lucene.spatial.prefix.tree.SpatialPrefixTree;
import org.apache.lucene.spatial.serialized.SerializedDVStrategy;
import org.apache.lucene.spatial.vector.PointVectorStrategy;
import org.apache.lucene.util.ArrayUtil;
import org.junit.Test;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeFactory;

public class DistanceStrategyTest extends StrategyTestCase {
  @ParametersFactory(argumentFormatting = "strategy=%s")
  public static Iterable<Object[]> parameters() {
    List<Object[]> ctorArgs = new ArrayList<>();

    SpatialContext ctx = SpatialContext.GEO;
    SpatialPrefixTree grid;
    SpatialStrategy strategy;

    grid = new QuadPrefixTree(ctx,25);
    strategy = new RecursivePrefixTreeStrategy(grid, "recursive_quad");
    ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});

    grid = new GeohashPrefixTree(ctx,12);
    strategy = new TermQueryPrefixTreeStrategy(grid, "termquery_geohash");
    ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});

    grid = new PackedQuadPrefixTree(ctx,25);
    strategy = new RecursivePrefixTreeStrategy(grid, "recursive_packedquad");
    ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});

    strategy = PointVectorStrategy.newInstance(ctx, "pointvector");
    ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});

//  Can't test this without un-inverting since PVS legacy config didn't have docValues.
//    However, note that Solr's tests use UninvertingReader and thus test this.
//    strategy = PointVectorStrategy.newLegacyInstance(ctx, "pointvector_legacy");
//    ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});

    strategy = BBoxStrategy.newInstance(ctx, "bbox");
    ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});

    strategy = new SerializedDVStrategy(ctx, "serialized");
    ctorArgs.add(new Object[]{strategy.getFieldName(), strategy});

    return ctorArgs;
  }

  public DistanceStrategyTest(String suiteName, SpatialStrategy strategy) {
    this.ctx = strategy.getSpatialContext();
    this.strategy = strategy;
  }

  @Test
  public void testDistanceOrder() throws IOException {
    ShapeFactory shapeFactory = ctx.getShapeFactory();
    adoc("100", shapeFactory.pointXY(2, 1));
    adoc("101", shapeFactory.pointXY(-1, 4));
    adoc("103", (Shape)null);//test score for nothing
    commit();
    //FYI distances are in docid order
    checkDistValueSource(shapeFactory.pointXY(4, 3), 2.8274937f, 5.0898066f, 180f);
    checkDistValueSource(shapeFactory.pointXY(0, 4), 3.6043684f, 0.9975641f, 180f);
  }

  @Test
  public void testRecipScore() throws IOException {
    Point p100 = ctx.getShapeFactory().pointXY(2.02, 0.98);
    adoc("100", p100);
    Point p101 = ctx.getShapeFactory().pointXY(-1.001, 4.001);
    adoc("101", p101);
    adoc("103", (Shape)null);//test score for nothing
    commit();

    double dist = ctx.getDistCalc().distance(p100, p101);
    Shape queryShape = ctx.makeCircle(2.01, 0.99, dist);
    checkValueSource(strategy.makeRecipDistanceValueSource(queryShape),
        new float[]{1.00f, 0.10f, 0f}, 0.09f);
  }

  void checkDistValueSource(Point pt, float... distances) throws IOException {
    float multiplier = random().nextFloat() * 100f;
    float[] dists2 = ArrayUtil.copyOfSubArray(distances, 0, distances.length);
    for (int i = 0; i < dists2.length; i++) {
      dists2[i] *= multiplier;
    }
    checkValueSource(strategy.makeDistanceValueSource(pt, multiplier), dists2, 1.0e-3f);
  }
}