package de.slikey.effectlib;


import org.apache.commons.lang.Validate;
import org.bukkit.Location;
import org.bukkit.entity.Entity;

import java.lang.ref.WeakReference;

public abstract class Effect implements Runnable {

	/**
	 * Handles the type, the effect is played.
	 * 
	 * @see {@link de.slikey.effectlib.EffectType}
	 */
	public EffectType type = EffectType.INSTANT;

	/**
	 * Delay to wait for delayed effects.
	 * 
	 * @see {@link de.slikey.effectlib.EffectType}
	 */
	public int delay = 0;

	/**
	 * Interval to wait for repeating effects.
	 * 
	 * @see {@link de.slikey.effectlib.EffectType}
	 */
	public int period = 1;

	/**
	 * Amount of repititions to do.
	 * Set this to -1 for an infinite effect
	 * 
	 * @see {@link de.slikey.effectlib.EffectType}
	 */
	public int iterations = 0;

	/**
	 * Callback to run, after effect is done.
	 * 
	 * @see {@link java.lang.Runnable}
	 */
	public Runnable callback = null;

	/**
	 * Display particles to players within this radius. Squared radius for
	 * performance reasons.
	 */
	public float visibleRange = 32;

    /**
     * The interval at which we will update the cached Entity Location.
     * This value is specified in milliseconds.
     *
     * A value of 0 indicates no caching should be done- this may be
     * expensive.
     */
    public int locationUpdateInterval = 250;

    private Location location = null;
    private WeakReference<Entity> entity = new WeakReference<Entity>(null);
    private Location target = null;
    private WeakReference<Entity> targetEntity = new WeakReference<Entity>(null);
    private long lastLocationUpdate = 0;
    private long lastTargetUpdate = 0;

	private boolean done = false;
	private final EffectManager effectManager;

	public Effect(EffectManager effectManager) {
        Validate.notNull(effectManager, "EffectManager cannot be null!");
		this.effectManager = effectManager;
	}

    public final void cancel() {
        cancel(true);
    }

	public final void cancel(boolean callback) {
		if (callback)
			done();
		else
			done = true;
	}

	private void done() {
		done = true;
		effectManager.done(this);
	}

	public final boolean isDone() {
		return done;
	}

	public abstract void onRun();

	@Override
	public final void run() {
        if (!isValid()) {
            cancel();
            return;
        }
		if (done)
			return;
		onRun();

		if (type == EffectType.REPEATING) {
			if (iterations == -1)
				return;
			iterations--;
			if (iterations < 1)
				done();
		} else {
			done();
		}
	}

    protected boolean isValid() {
        // Check for a valid Location
        return getLocation() != null;
    }

	public final void start() {
		effectManager.start(this);
	}

	public final void infinite() {
		type = EffectType.REPEATING;
		iterations = -1;
	}

    /**
     * Extending Effect classes can use this to determine the Entity this
     * Effect is centered upon.
     *
     * This may return null, even for an Effect that was set with an Entity,
     * if the Entity gets GC'd.
     */
    public Entity getEntity()
    {
        return this.entity.get();
    }

    /**
     * Extending Effect classes can use this to determine the Entity this
     * Effect is targeted upon. This is probably a very rare case, such as
     * an Effect that "links" two Entities together somehow. (Idea!)
     *
     * This may return null, even for an Effect that was set with a target Entity,
     * if the Entity gets GC'd.
     */
    public Entity getTargetEntity()
    {
        return this.targetEntity.get();
    }

    /**
     * Extending Effect classes should use this method to obtain the
     * current "root" Location of the effect.
     *
     * This method will not return null when called from onRun. Effects
     * with invalid locations will be cancelled.
     */
    public Location getLocation()
    {
        Entity entityReference = entity.get();
        if (entityReference != null) {
            long now = System.currentTimeMillis();
            if (locationUpdateInterval == 0 || lastLocationUpdate == 0 || lastLocationUpdate + locationUpdateInterval > now) {
                location = entityReference.getLocation();
            }
        }

        return location;
    }

    /**
     * Extending Effect classes should use this method to obtain the
     * current "target" Location of the effect.
     *
     * Unlike getLocation, this may return null.
     */
    public Location getTarget()
    {
        Entity entityReference = targetEntity.get();
        if (entityReference != null) {
            long now = System.currentTimeMillis();
            if (locationUpdateInterval == 0 || lastTargetUpdate == 0 || lastTargetUpdate + locationUpdateInterval > now) {
                target = entityReference.getLocation();
            }
        }

        return target;
    }

    /**
     * Set the Entity this Effect is centered on.
     */
    public void setEntity(Entity entity) {
        this.entity = new WeakReference<Entity>(entity);
    }

    /**
     * Set the Entity this Effect is targeting.
     */
    public void setTargetEntity(Entity entity) {
        this.targetEntity = new WeakReference<Entity>(entity);
    }

    /**
     * Set the Location this Effect is centered on.
     */
    public void setLocation(Location location) {
        this.location = location.clone();
    }

    /**
     * Set the Location this Effect is targeting.
     */
    public void setTarget(Location location) {
        this.target = location.clone();
    }
}


@@ -125,16 +125,47 @@
 .Entity;
+%0Aimport org.bukkit.util.Vector;
 %0A%0Aimport
@@ -1370,11 +1370,182 @@
 l = 
-250
+100;%0A%0A    /**%0A     * If true, and a %22target%22 Location or Entity is set, the two Locations%0A     * will orient to face one another.%0A     */%0A    public boolean autoOrient = true
 ;%0A%0A 
@@ -2461,23 +2461,24 @@
    if (!
-isV
+v
 alid
+ate
 ()) %7B%0A  
@@ -2745,15 +2745,16 @@
 ean 
-isV
+v
 alid
+ate
 () %7B
@@ -2804,36 +2804,429 @@
     
-return getLocation() != null
+Location location = getLocation();%0A        if (location == null) return false;%0A        if (autoOrient) %7B%0A            Location target = getTarget();%0A            if (target != null) %7B%0A                Vector direction = target.toVector().subtract(location.toVector());%0A                location.setDirection(direction);%0A                target.setDirection(direction.multiply(-1));%0A            %7D%0A        %7D%0A%0A        return true
 ;%0A  

Add autoOrient option