import processing.core.PImage;

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

public class Unicorn extends animatable implements Moving, Transforming {
    private int magicEnergy;

    /**
     * Constructs an Entity with specified characteristics.
     * In the base program, this is not called directly.
     * Instead, the encapsulated 'create' methods are used to create specific types of entities.
     *
     * @param id              The entity's identifier.
     * @param position        The entity's x/y position in the world.
     * @param images          The entity's inanimate (singular) or animation (multiple) images.
     * @param animationPeriod The positive (non-zero) time delay between the entity's animations.
     * @param behaviorPeriod  The positive (non-zero) time delay between the entity's behaviors.
     */
    public Unicorn(String id, Point position, List<PImage> images, double animationPeriod, double behaviorPeriod) {
        super(id, position, images, animationPeriod, behaviorPeriod);
        this.magicEnergy = 2;
    }

    public int getMagicEnergy() {
        return magicEnergy;
    }

    public void setMagicEnergy(int magicEnergy) {
        this.magicEnergy = magicEnergy;
    }

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

    @Override
    public Point nextPosition(World world, Point destination) {
        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()) {
            // Logic if there is no path or at the destination
            return getPosition();
        } else {
            // Logic if there is a path
            return path.get(0);
        }
    }

    @Override
    public boolean transform(World world, EventScheduler scheduler, ImageLibrary imageLibrary) {
        Optional<Entity> target = world.findNearest(getPosition(), List.of(Dude.class));
        if (target.isPresent() && getPosition().adjacentTo(target.get().getPosition())) {
            Entity dude = target.get();
            Point pos = dude.getPosition();

            world.removeEntity(scheduler, dude);

            Princess princess = new Princess(dude.getId() + "_Princess", pos, imageLibrary.get("princess"), 0.25, 1.0);
            world.addEntity(princess);
            princess.scheduleActions(scheduler, world, imageLibrary);
            return true;
        }
        return false;
    }

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

    @Override
    public void executeBehavior(World world, ImageLibrary imageLibrary, EventScheduler scheduler) {
        Optional<Entity> dudeTarget = world.findNearest(getPosition(), List.of(Dude.class));
        if (dudeTarget.isEmpty()) {
            world.removeEntity(scheduler, this);
            return;
        }
        if (this.magicEnergy > 0) {
            if (moveTo(world, dudeTarget.get(), scheduler)) {
                if (transform(world, scheduler, imageLibrary)) {
                    this.magicEnergy--;
                }
            }
            scheduleBehavior(scheduler, world, imageLibrary);
        }
        else {
            scheduleBehavior(scheduler, world, imageLibrary);
        }
    }
}