import processing.core.PImage;

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

public class Dude extends animatable implements Moving, Transforming{

    public static final String DUDE_KEY = "dude";

    private int resourceCount;

    private int resourceLimit;


    public boolean moveTo(World world, Entity target, EventScheduler scheduler) {
        if (getPosition().adjacentTo(target.getPosition())) {
            if (target instanceof Plant plant) {
                plant.setHealth(plant.getHealth() - 1);
            }
            return true;
        } 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) || world.getOccupant(p).orElse(null) instanceof Stump);

        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);
        }
    }

    /**
     * Returns a new 'Dude' type entity.
     * Constructor arguments provide hints to data necessary for a subclass.
     *
     * @param id              A unique string identifier for the entity.
     *                        Typically, the constant 'Dude' 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.
     * @param resourceCount   The number of resources held by the entity.
     * @param resourceLimit   The total number of resources the entity can hold.
     *
     * @return A new Entity object configured as a 'Dude'.
     */
    public Dude(String id, Point position, List<PImage> images, double animationPeriod, double behaviorPeriod, int resourceCount, int resourceLimit) {
        super( id, position, images, animationPeriod, behaviorPeriod) ;
        this.resourceCount= resourceCount;
        this.resourceLimit = resourceLimit;
    }

    public void executeBehavior(World world, ImageLibrary imageLibrary, EventScheduler scheduler) {
        Optional<Entity> dudeTarget = findDudeTarget(world);
        if (dudeTarget.isEmpty() || !moveTo(world, dudeTarget.get(), scheduler) || !transform(world, scheduler, imageLibrary)) {
            scheduleBehavior(scheduler, world, imageLibrary);
        }
    }

    public Optional<Entity> findDudeTarget(World world) {
        List<Class<?>> potentialTargets;

        if (resourceCount == resourceLimit) {
            potentialTargets = List.of(House.class);
        } else {
            potentialTargets = List.of(Tree.class, Sapling.class);
        }

        return world.findNearest(getPosition(), potentialTargets);
    }

    public boolean transform(World world, EventScheduler scheduler, ImageLibrary imageLibrary) {
        if (resourceCount < resourceLimit) {
            resourceCount += 1;
            if (resourceCount == resourceLimit) {
                Dude dude = new Dude(getId(), getPosition(), imageLibrary.get(DUDE_KEY + "_carry"), getAnimationPeriod(), getBehaviorPeriod(), resourceCount, resourceLimit);

                world.removeEntity(scheduler, this);

                world.addEntity(dude);
                dude.scheduleActions(scheduler, world, imageLibrary);

                return true;
            }
        } else {
            Dude dude = new Dude(getId(), getPosition(), imageLibrary.get(DUDE_KEY), getAnimationPeriod(), getBehaviorPeriod(), 0, resourceLimit);

            world.removeEntity(scheduler, this);

            world.addEntity(dude);
            dude.scheduleActions(scheduler, world, imageLibrary);

            return true;
        }

        return false;
    }

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

    }

}
