/*
 * 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.commons.geometry.core.partitioning.bsp;

import org.apache.commons.geometry.core.Point;
import org.apache.commons.geometry.core.Transform;
import org.apache.commons.geometry.core.partitioning.Hyperplane;
import org.apache.commons.geometry.core.partitioning.HyperplaneConvexSubset;

/** Interface for Binary Space Partitioning (BSP) trees. BSP trees are spatial data
 * structures that recursively subdivide a space using hyperplanes. They can be used
 * for a wide variety of purposes, such as representing arbitrary polytopes, storing
 * data for fast spatial lookups, determining the correct rendering order for objects
 * in a 3D scene, and so on.
 *
 * <p>This interface contains a number of methods for extracting information from existing
 * trees, but it does not include methods for constructing trees or modifying tree structure.
 * This is due to the large number of possible use cases for BSP trees. Each use case is likely
 * to have its own specific methods and rules for tree construction, making it difficult to define
 * a single API at this level. Thus, it is left to implementation classes to define their own
 * API for tree construction and modification.</p>
 *
 * @param <P> Point implementation type
 * @param <N> Node implementation type
 * @see <a href="https://en.wikipedia.org/wiki/Binary_space_partitioning">Binary space partitioning</a>
 */
public interface BSPTree<P extends Point<P>, N extends BSPTree.Node<P, N>>
    extends BSPSubtree<P, N> {

    /** Enum specifying possible behaviors when a point used to locate a node
     * falls directly on the cut of an internal node.
     */
    enum FindNodeCutRule {

        /** Choose the minus child of the internal node and continue searching.
         * This behavior will result in a leaf node always being returned by the
         * node search.
         */
        MINUS,

        /** Choose the plus child of the internal node and continue searching.
         * This behavior will result in a leaf node always being returned by the
         * node search.
         */
        PLUS,

        /** Choose the internal node and stop searching. This behavior may result
         * in non-leaf nodes being returned by the node search.
         */
        NODE
    }

    /** Get the root node of the tree.
     * @return the root node of the tree
     */
    N getRoot();

    /** Find a node in this subtree containing the given point or its interior or boundary.
     * When a point lies directly on the cut of an internal node, the minus child of the
     * cut is chosen. This is equivalent to {@code subtree.findNode(pt, FindNodeCutRule.MINUS)}
     * and always returns a leaf node.
     * @param pt test point used to locate a node in the tree
     * @return leaf node containing the point on its interior or boundary
     * @see #findNode(Point, FindNodeCutRule)
     */
    default N findNode(final P pt) {
        return findNode(pt, FindNodeCutRule.MINUS);
    }

    /** Find a node in this subtree containing the given point on its interior or boundary. The
     * search should always return a leaf node except in the cases where the given point lies
     * exactly on the cut of an internal node. In this case, it is unclear whether
     * the search should continue with the minus child, the plus child, or end with the internal
     * node. The {@code cutRule} argument specifies what should happen in this case.
     * <ul>
     *      <li>{@link FindNodeCutRule#MINUS} - continue the search in the minus subtree</li>
     *      <li>{@link FindNodeCutRule#PLUS} - continue the search in the plus subtree</li>
     *      <li>{@link FindNodeCutRule#NODE} - stop the search and return the internal node</li>
     * </ul>
     * @param pt test point used to locate a node in the tree
     * @param cutRule value used to determine the search behavior when the test point lies
     *      exactly on the cut of an internal node
     * @return node containing the point on its interior or boundary
     * @see #findNode(Point)
     */
    N findNode(P pt, FindNodeCutRule cutRule);

    /** Make the current instance a deep copy of the argument.
     * @param src the tree to copy
     */
    void copy(BSPTree<P, N> src);

    /** Set this instance to the region represented by the given node. The
     * given node could have come from this tree or a different tree.
     * @param node the node to extract
     */
    void extract(N node);

    /** Transform this tree. Each cut in the tree is transformed in place using
     * the given {@link Transform}.
     * @param transform the transform to apply
     */
    void transform(Transform<P> transform);

    /** Interface for Binary Space Partitioning (BSP) tree nodes.
     * @param <P> Point type
     * @param <N> BSP tree node implementation type
     */
    interface Node<P extends Point<P>, N extends Node<P, N>> extends BSPSubtree<P, N> {

        /** Get the {@link BSPTree} that owns the node.
         * @return the owning tree
         */
        BSPTree<P, N> getTree();

        /** Get the depth of the node in the tree. The root node of the tree
         * has a depth of 0.
         * @return the depth of the node in the tree
         */
        int depth();

        /** Get the parent of the node. This will be null if the node is the
         * root of the tree.
         * @return the parent node for this instance
         */
        N getParent();

        /** Get the cut for the node. This is a hyperplane convex subset that splits
         * the region for the cell into two disjoint regions, namely the plus and
         * minus regions. This will be null for leaf nodes.
         * @see #getPlus()
         * @see #getMinus()
         * @return the cut for the cell
         * @see #getCutHyperplane()
         */
        HyperplaneConvexSubset<P> getCut();

        /** Get the hyperplane containing the node cut, if it exists.
         * @return the hyperplane containing the node cut, or null if
         *      the node does not have a cut
         * @see #getCut()
         */
        Hyperplane<P> getCutHyperplane();

        /** Get the node for the minus region of the cell. This will be null if the
         * node has not been cut, ie if it is a leaf node.
         * @return the node for the minus region of the cell
         */
        N getMinus();

        /** Get the node for the plus region of the cell. This will be null if the
         * node has not been cut, ie if it is a leaf node.
         * @return the node for the plus region of the cell
         */
        N getPlus();

        /** Return true if the node is a leaf node, meaning that it has no
         * binary partitioner (aka "cut") and therefore no child nodes.
         * @return true if the node is a leaf node
         */
        boolean isLeaf();

        /** Return true if the node is an internal node, meaning that is
         * has a binary partitioner (aka "cut") and therefore two child nodes.
         * @return true if the node is an internal node
         */
        boolean isInternal();

        /** Return true if the node has a parent and is the parent's minus
         * child.
         * @return true if the node is the minus child of its parent
         */
        boolean isMinus();

        /** Return true if the node has a parent and is the parent's plus
         * child.
         * @return true if the node is the plus child of its parent
         */
        boolean isPlus();

        /** Trim the given hyperplane subset to the region defined by this node by cutting
         * the argument with the cut hyperplanes (binary partitioners) of all parent nodes
         * up to the root. Null is returned if the hyperplane subset lies outside of the region
         * defined by the node.
         * @param sub the hyperplane subset to trim
         * @return the trimmed hyperplane subset or null if no part of the argument lies
         *      within the node's region
         */
        HyperplaneConvexSubset<P> trim(HyperplaneConvexSubset<P> sub);
    }
}
