import java.util.List;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

public interface PathingStrategy {
    /**
     * Return a list of points for a path from the start point to a point within
     * reach of the end point.
     * This path is only valid ("clear") when returned; it may be invalidated
     * movement of other entities.
     * The list neither includes the start nor end points.
     *
     * @param start the point to begin the search from
     * @param end the point to search for a point within reach of
     * @param canPassThrough a function that returns true if the given point is traversable
     * @param withinReach a function that returns true if both points are within reach of each other
     * @param potentialNeighbors a function that returns the neighbors of a given point, as a stream
     */
    List<Point> computePath(
            Point start,
            Point end,
            Predicate<Point> canPassThrough,
            BiPredicate<Point, Point> withinReach,
            Function<Point, Stream<Point>> potentialNeighbors
    );

    /**
     * A static Constant: it's a lambda function that returns neighbors of a given point as a stream.
     * Example Usage:
     * Stream<Point> neighbors = PathingStrategy.CARDINAL_NEIGHBORS.apply(new Point(0, 0));
     */
    static final Function<Point, Stream<Point>> CARDINAL_NEIGHBORS =
            point ->
                    Stream.<Point>builder()
                            .add(new Point(point.x, point.y - 1))
                            .add(new Point(point.x, point.y + 1))
                            .add(new Point(point.x - 1, point.y))
                            .add(new Point(point.x + 1, point.y))
                            .build();
}