/*
 * Copyright (c) 2019 Martin Davis.
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * and Eclipse Distribution License v. 1.0 which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v20.html
 * and the Eclipse Distribution License is available at
 *
 * http://www.eclipse.org/org/documents/edl-v10.php.
 */
package org.locationtech.jts.operation.polygonize;

import java.util.List;

import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.index.SpatialIndex;
import org.locationtech.jts.index.strtree.STRtree;

/**
 * Assigns hole rings to shell rings 
 * during polygonization.
 * Uses spatial indexing to improve performance
 * of shell lookup.
 * 
 * @author mdavis
 *
 */
public class HoleAssigner 
{
  /**
   * Assigns hole rings to shell rings.
   * 
   * @param holes list of hole rings to assign
   * @param shells list of shell rings
   */
  public static void assignHolesToShells(List<EdgeRing> holes, List<EdgeRing> shells) {
    HoleAssigner assigner = new HoleAssigner(shells);
    assigner.assignHolesToShells(holes);
  }
  
  private List<EdgeRing> shells;
  private SpatialIndex shellIndex;
  
  /**
   * Creates a new hole assigner.
   * 
   * @param shells the shells to be assigned to
   */
  public HoleAssigner(List<EdgeRing> shells) {
    this.shells = shells;
    buildIndex();
  }
  
  private void buildIndex() {
    shellIndex = new STRtree();
    for (EdgeRing shell : shells) {
      shellIndex.insert(shell.getRing().getEnvelopeInternal(), shell);
    }
  }

  /**
   * Assigns holes to the shells.
   * 
   * @param holeList list of hole rings to assign
   */
  public void assignHolesToShells(List<EdgeRing> holeList)
  {
    for (EdgeRing holeER : holeList) {
      assignHoleToShell(holeER);
    }
  }
  
  private void assignHoleToShell(EdgeRing holeER)
  {
    EdgeRing shell = findShellContaining(holeER);
    if (shell != null) {
      shell.addHole(holeER);
    }
  }
  
  @SuppressWarnings("unchecked")
  private List<EdgeRing> queryOverlappingShells(Envelope ringEnv) {
    return (List<EdgeRing>) shellIndex.query(ringEnv);
  }
  
  /**
   * Find the innermost enclosing shell EdgeRing containing the argument EdgeRing, if any.
   * The innermost enclosing ring is the <i>smallest</i> enclosing ring.
   * The algorithm used depends on the fact that:
   * <br>
   *  ring A contains ring B if envelope(ring A) contains envelope(ring B)
   * <br>
   * This routine is only safe to use if the chosen point of the hole
   * is known to be properly contained in a shell
   * (which is guaranteed to be the case if the hole does not touch its shell)
   *
   * @return containing shell EdgeRing, if there is one
   * or null if no containing EdgeRing is found
   */
  private EdgeRing findShellContaining(EdgeRing testEr)
  {
    Envelope testEnv = testEr.getRing().getEnvelopeInternal();   
    List<EdgeRing> candidateShells = queryOverlappingShells(testEnv);
    return EdgeRing.findEdgeRingContaining(testEr, candidateShells);
  }
}
