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

/** Interface for visiting the nodes in a {@link BSPTree} or {@link BSPSubtree}.
 * @param <P> Point implementation type
 * @param <N> BSP tree node implementation type
 */
@FunctionalInterface
public interface BSPTreeVisitor<P extends Point<P>, N extends BSPTree.Node<P, N>> {

    /** Enum used to specify the order in which visitors should visit the nodes
     * in the tree.
     */
    enum Order {

        /** Indicates that the visitor should first visit the plus sub-tree, then
         * the minus sub-tree and then the current node.
         */
        PLUS_MINUS_NODE,

        /** Indicates that the visitor should first visit the plus sub-tree, then
         * the current node, and then the minus sub-tree.
         */
        PLUS_NODE_MINUS,

        /** Indicates that the visitor should first visit the minus sub-tree, then
         * the plus sub-tree, and then the current node.
         */
        MINUS_PLUS_NODE,

        /** Indicates that the visitor should first visit the minus sub-tree, then the
         * current node, and then the plus sub-tree.
         */
        MINUS_NODE_PLUS,

        /** Indicates that the visitor should first visit the current node, then the
         * plus sub-tree, and then the minus sub-tree.
         */
        NODE_PLUS_MINUS,

        /** Indicates that the visitor should first visit the current node, then the
         * minus sub-tree, and then the plus sub-tree.
         */
        NODE_MINUS_PLUS,

        /** Indicates that the visitor should not visit any of the nodes in this subtree. */
        NONE
    }

    /** Enum representing the result of a BSP tree node visit operation.
     */
    enum Result {

        /** Indicates that the visit operation should continue with the remaining nodes in
         * the BSP tree.
         */
        CONTINUE,

        /** Indicates that the visit operation should terminate and not visit any further
         * nodes in the tree.
         */
        TERMINATE
    }

    /** Visit a node in a BSP tree. This method is called for both internal nodes and
     * leaf nodes.
     * @param node the node being visited
     * @return the result of the visit operation
     */
    Result visit(N node);

    /** Determine the visit order for the given internal node. This is called for each
     * internal node before {@link #visit(BSPTree.Node)} is called. Returning null
     * or {@link Order#NONE}from this method skips the subtree rooted at the given node.
     * This method is not called on leaf nodes.
     * @param internalNode the internal node to determine the visit order for
     * @return the order that the subtree rooted at the given node should be visited
     */
    default Order visitOrder(final N internalNode) {
        return Order.NODE_MINUS_PLUS;
    }

    /** Abstract class for {@link BSPTreeVisitor} implementations that base their visit
     * ordering on a target point.
     * @param <P> Point implementation type
     * @param <N> BSP tree node implementation type
     */
    abstract class TargetPointVisitor<P extends Point<P>, N extends BSPTree.Node<P, N>>
        implements BSPTreeVisitor<P, N> {
        /** Point serving as the target of the traversal. */
        private final P target;

        /** Simple constructor.
         * @param target the point serving as the target for the tree traversal
         */
        protected TargetPointVisitor(final P target) {
            this.target = target;
        }

        /** Get the target point for the tree traversal.
         * @return the target point for the tree traversal
         */
        public P getTarget() {
            return target;
        }
    }

    /** {@link BSPTreeVisitor} base class that orders tree nodes so that nodes closest to the target point are
     * visited first. This is done by choosing {@link Order#MINUS_NODE_PLUS}
     * when the target point lies on the minus side of the node's cut hyperplane and {@link Order#PLUS_NODE_MINUS}
     * when it lies on the plus side. The order {@link Order#MINUS_NODE_PLUS} order is used when
     * the target point lies directly on the node's cut hyerplane and no child node is closer than the other.
     * @param <P> Point implementation type
     * @param <N> BSP tree node implementation type
     */
    abstract class ClosestFirstVisitor<P extends Point<P>, N extends BSPTree.Node<P, N>>
        extends TargetPointVisitor<P, N> {
        /** Simple constructor.
         * @param target the point serving as the target for the traversal
         */
        protected ClosestFirstVisitor(final P target) {
            super(target);
        }

        /** {@inheritDoc} */
        @Override
        public Order visitOrder(final N node) {
            if (node.getCutHyperplane().offset(getTarget()) > 0) {
                return Order.PLUS_NODE_MINUS;
            }
            return Order.MINUS_NODE_PLUS;
        }
    }

    /** {@link BSPTreeVisitor} base class that orders tree nodes so that nodes farthest from the target point
     * are traversed first. This is done by choosing {@link Order#PLUS_NODE_MINUS}
     * when the target point lies on the minus side of the node's cut hyperplane and {@link Order#MINUS_NODE_PLUS}
     * when it lies on the plus side. The order {@link Order#MINUS_NODE_PLUS} order is used when
     * the target point lies directly on the node's cut hyerplane and no child node is closer than the other.
     * @param <P> Point implementation type
     * @param <N> BSP tree node implementation type
     */
    abstract class FarthestFirstVisitor<P extends Point<P>, N extends BSPTree.Node<P, N>>
        extends TargetPointVisitor<P, N> {
        /** Simple constructor.
         * @param target the point serving as the target for the traversal
         */
        protected FarthestFirstVisitor(final P target) {
            super(target);
        }

        /** {@inheritDoc} */
        @Override
        public Order visitOrder(final N node) {
            if (node.getCutHyperplane().offset(getTarget()) < 0) {
                return Order.PLUS_NODE_MINUS;
            }
            return Order.MINUS_NODE_PLUS;
        }
    }
}
