/*
 * 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.prefix.tree;

import java.util.Arrays;
import org.apache.lucene.util.BytesRef;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;

/**
 * The base for the original two SPT's: Geohash and Quad. Don't subclass this for new SPTs.
 *
 * @lucene.internal
 */
abstract class LegacyPrefixTree extends SpatialPrefixTree {
  public LegacyPrefixTree(SpatialContext ctx, int maxLevels) {
    super(ctx, maxLevels);
  }

  @Override
  public double getDistanceForLevel(int level) {
    if (level < 1 || level > getMaxLevels())
      throw new IllegalArgumentException("Level must be in 1 to maxLevels range");
    // TODO cache for each level
    Cell cell = getCell(ctx.getWorldBounds().getCenter(), level);
    Rectangle bbox = cell.getShape().getBoundingBox();
    double width = bbox.getWidth();
    double height = bbox.getHeight();
    // Use standard cartesian hypotenuse. For geospatial, this answer is larger
    // than the correct one but it's okay to over-estimate.
    return Math.sqrt(width * width + height * height);
  }

  /** Returns the cell containing point {@code p} at the specified {@code level}. */
  protected abstract Cell getCell(Point p, int level);

  @Override
  public Cell readCell(BytesRef term, Cell scratch) {
    LegacyCell cell = (LegacyCell) scratch;
    if (cell == null) cell = (LegacyCell) getWorldCell();
    cell.readCell(term);
    return cell;
  }

  @Override
  public CellIterator getTreeCellIterator(Shape shape, int detailLevel) {
    if (!(shape instanceof Point)) return super.getTreeCellIterator(shape, detailLevel);

    // This specialization is here because the legacy implementations don't have a fast
    // implementation of
    // cell.getSubCells(point). It's fastest here to encode the full bytes for detailLevel, and
    // create
    // subcells from the bytesRef in a loop. This avoids an O(N^2) encode, and we have O(N) instead.

    Cell cell = getCell((Point) shape, detailLevel);
    assert cell instanceof LegacyCell;
    BytesRef fullBytes = cell.getTokenBytesNoLeaf(null);
    // fill in reverse order to be sorted
    Cell[] cells = new Cell[detailLevel];
    for (int i = 1; i < detailLevel; i++) {
      fullBytes.length = i;
      Cell parentCell = readCell(fullBytes, null);
      cells[i - 1] = parentCell;
    }
    cells[detailLevel - 1] = cell;
    return new FilterCellIterator(Arrays.asList(cells).iterator(), null); // null filter
  }
}
