import processing.core.PImage;

import java.util.List;
import java.util.Optional;
import java.util.function.BiPredicate;
import java.util.function.Predicate;

public class Fairy extends animatable implements Moving{
    public static final String FAIRY_KEY = "fairy";

    /**
     * Creates an Entity representing a(n) 'Fairy'.
     * The parameters provide a hint to data relevant for a(n) 'Fairy' class.
     *
     * @param id              A unique string identifier for the entity.
     *                        Typically, the constant 'Fairy' key, but can be anything.
     * @param position        The entity's x/y position in the world.
     * @param images          A list of images that represent an entity and its possible animation.
     * @param animationPeriod The time between when an entity's animation is scheduled and executed.
     * @param behaviorPeriod  The time between when an entity's activity is scheduled and executed.
     *
     * @return A new Entity object configured as a 'Fairy'.
     */
    public Fairy(String id, Point position, List<PImage> images, double animationPeriod, double behaviorPeriod) {
        super(id, position, images, animationPeriod, behaviorPeriod);
    }

    @Override
    public boolean moveTo(World world, Entity target, EventScheduler scheduler) {
        if (getPosition().adjacentTo(target.getPosition())) {
            return true; // Just return true so executeBehavior knows we arrived
        } else {
            Point nextPos = nextPosition(world, target.getPosition());
            if (!getPosition().equals(nextPos)) {
                world.moveEntity(scheduler, this, nextPos);
            }
            return false;
        }
    }

    public Point nextPosition(World world, Point destination) {
        // Differences between the destination and current position along each axis
        PathingStrategy pathingStrategy = new AStarPathingStrategy();

        Predicate<Point> canPassThrough = p -> world.inBounds(p) && !world.isOccupied(p);

        BiPredicate<Point, Point> withinReach = Point::adjacentTo;

        List<Point> path = pathingStrategy.computePath(
                getPosition(),
                destination,
                canPassThrough,
                withinReach,
                PathingStrategy.CARDINAL_NEIGHBORS
        );

        if (path.isEmpty()) {
            return getPosition();
        } else {
            return path.get(0);
        }

    }

    public void executeBehavior(World world, ImageLibrary imageLibrary, EventScheduler scheduler) {
        Optional<Entity> tiredUnicorn = world.findNearest(getPosition(), List.of(Unicorn.class));
        if (tiredUnicorn.isPresent() && ((Unicorn) tiredUnicorn.get()).getMagicEnergy() == 0) {
            if (moveTo(world, tiredUnicorn.get(), scheduler)) {
                ((Unicorn) tiredUnicorn.get()).setMagicEnergy(2);
            }

            scheduleBehavior(scheduler, world, imageLibrary);
        }
        else {

            Optional<Entity> fairyTarget = world.findNearest(getPosition(), List.of(Stump.class));

            if (fairyTarget.isPresent()) {
                if (moveTo(world, fairyTarget.get(), scheduler)) {
                    Point tgtPos = fairyTarget.get().getPosition();
                    String targetId = fairyTarget.get().getId();

                    world.removeEntity(scheduler, fairyTarget.get());

                    Sapling sapling = new Sapling(Sapling.SAPLING_KEY + "_" + targetId,
                            tgtPos,
                            imageLibrary.get(Sapling.SAPLING_KEY));

                    world.addEntity(sapling);
                    sapling.scheduleActions(scheduler, world, imageLibrary);
                }
            }
            scheduleBehavior(scheduler, world, imageLibrary);
        }
    }

    public void updateImage() {
        setImageIndex(getImageIndex() + 1);
    }


}
