package com.projectkorra.projectkorra.firebending.lightning;

import java.util.ArrayList;
import java.util.List;

import com.projectkorra.projectkorra.ability.CoreAbility;
import com.projectkorra.projectkorra.firebending.FireJet;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;

import com.projectkorra.projectkorra.Element;
import com.projectkorra.projectkorra.GeneralMethods;
import com.projectkorra.projectkorra.ProjectKorra;
import com.projectkorra.projectkorra.ability.LightningAbility;
import com.projectkorra.projectkorra.attribute.Attribute;
import com.projectkorra.projectkorra.util.DamageHandler;
import com.projectkorra.projectkorra.util.MovementHandler;

public class Lightning extends LightningAbility {

	public static enum State {
		START, STRIKE, MAINBOLT
	}

	private static final int POINT_GENERATION = 5;

	@Attribute("Charged")
	private boolean charged;
	private boolean hitWater;
	private boolean hitIce;
	private boolean selfHitWater;
	private boolean selfHitClose;
	private boolean allowOnFireJet;
	@Attribute("ArcOnIce")
	private boolean arcOnIce;
	private int waterArcs;
	@Attribute(Attribute.RANGE)
	private double range;
	@Attribute(Attribute.CHARGE_DURATION)
	private double chargeTime;
	@Attribute("SubArcChance")
	private double subArcChance;
	@Attribute(Attribute.DAMAGE)
	private double damage;
	@Attribute("MaxChainArcs")
	private double maxChainArcs;
	@Attribute("Chain" + Attribute.RANGE)
	private double chainRange;
	@Attribute("WaterArc" + Attribute.RANGE)
	private double waterArcRange;
	@Attribute("ChainArcChance")
	private double chainArcChance;
	@Attribute("StunChance")
	private double stunChance;
	@Attribute("Stun" + Attribute.DURATION)
	private double stunDuration;
	@Attribute("MaxArcAngle")
	private double maxArcAngle;
	private double particleRotation;
	private long time;
	@Attribute(Attribute.COOLDOWN)
	private long cooldown;
	private State state;
	private Location origin;
	private Location destination;
	private ArrayList<Entity> affectedEntities;
	private ArrayList<Arc> arcs;
	private ArrayList<BukkitRunnable> tasks;
	private ArrayList<Location> locations;

	public Lightning(final Player player) {
		super(player);

		if (!this.bPlayer.canBend(this)) {
			return;
		}
		if (hasAbility(player, Lightning.class)) {
			if (!getAbility(player, Lightning.class).isCharged()) {
				return;
			}
		}

		this.charged = false;
		this.hitWater = false;
		this.hitIce = false;
		this.time = System.currentTimeMillis();
		this.state = State.START;
		this.affectedEntities = new ArrayList<>();
		this.arcs = new ArrayList<>();
		this.tasks = new ArrayList<>();
		this.locations = new ArrayList<>();

		this.selfHitWater = getConfig().getBoolean("Abilities.Fire.Lightning.SelfHitWater");
		this.selfHitClose = getConfig().getBoolean("Abilities.Fire.Lightning.SelfHitClose");
		this.arcOnIce = getConfig().getBoolean("Abilities.Fire.Lightning.ArcOnIce");
		this.range = getConfig().getDouble("Abilities.Fire.Lightning.Range");
		this.damage = getConfig().getDouble("Abilities.Fire.Lightning.Damage");
		this.maxArcAngle = getConfig().getDouble("Abilities.Fire.Lightning.MaxArcAngle");
		this.subArcChance = getConfig().getDouble("Abilities.Fire.Lightning.SubArcChance");
		this.chainRange = getConfig().getDouble("Abilities.Fire.Lightning.ChainArcRange");
		this.chainArcChance = getConfig().getDouble("Abilities.Fire.Lightning.ChainArcChance");
		this.waterArcRange = getConfig().getDouble("Abilities.Fire.Lightning.WaterArcRange");
		this.stunChance = getConfig().getDouble("Abilities.Fire.Lightning.StunChance");
		this.stunDuration = getConfig().getDouble("Abilities.Fire.Lightning.StunDuration");
		this.maxChainArcs = getConfig().getInt("Abilities.Fire.Lightning.MaxChainArcs");
		this.waterArcs = getConfig().getInt("Abilities.Fire.Lightning.WaterArcs");
		this.chargeTime = getConfig().getLong("Abilities.Fire.Lightning.ChargeTime");
		this.cooldown = getConfig().getLong("Abilities.Fire.Lightning.Cooldown");
		this.allowOnFireJet = getConfig().getBoolean("Abilities.Fire.Lightning.AllowOnFireJet");

		this.range = this.getDayFactor(this.range);
		this.subArcChance = this.getDayFactor(this.subArcChance);
		this.damage = this.getDayFactor(this.damage);
		this.maxChainArcs = this.getDayFactor(this.maxChainArcs);
		this.chainArcChance = this.getDayFactor(this.chainArcChance);
		this.chainRange = this.getDayFactor(this.chainRange);
		this.waterArcRange = this.getDayFactor(this.waterArcRange);
		this.stunChance = this.getDayFactor(this.stunChance);
		this.stunDuration = this.getDayFactor(this.stunDuration);

		if (this.bPlayer.isAvatarState()) {
			this.chargeTime = getConfig().getLong("Abilities.Avatar.AvatarState.Fire.Lightning.ChargeTime");
			this.cooldown = getConfig().getLong("Abilities.Avatar.AvatarState.Fire.Lightning.Cooldown");
			this.damage = getConfig().getDouble("Abilities.Avatar.AvatarState.Fire.Lightning.Damage");
		}
		this.start();
	}

	/**
	 * Damages an entity, and may cause paralysis depending on the config.
	 *
	 * @param lent The LivingEntity that is being damaged
	 */
	public void electrocute(final LivingEntity lent) {
		playLightningbendingSound(lent.getLocation());
		playLightningbendingSound(this.player.getLocation());
		DamageHandler.damageEntity(lent, this.damage, this);

		if (Math.random() <= this.stunChance) {
			final MovementHandler mh = new MovementHandler(lent, this);
			mh.stopWithDuration((long) this.stunDuration, Element.LIGHTNING.getColor() + "* Electrocuted *");
		}
	}

	/**
	 * Checks if a block is transparent, also considers the ARC_ON_ICE config
	 * option.
	 *
	 * @param player the player that is viewing the block
	 * @param block the block
	 * @return true if the block is transparent
	 */
	private boolean isTransparentForLightning(final Player player, final Block block) {
		if (this.isTransparent(block)) {
			if (GeneralMethods.isRegionProtectedFromBuild(this, block.getLocation())) {
				return false;
			} else if (isIce(block)) {
				return this.arcOnIce;
			} else {
				return true;
			}
		}
		return false;
	}

	/**
	 * Progresses the instance of this ability by 1 tick. This is the heart of
	 * the ability, it checks if it needs to remove itself, and handles the
	 * initial Lightning Arc generation.
	 *
	 * Once all of the arcs have been created then this ability instance gets
	 * removed, but the BukkitRunnables continue until they remove themselves.
	 **/
	@Override
	public void progress() {
		if (this.player.isDead() || !this.player.isOnline()) {
			this.removeWithTasks();
			return;
		} else if (!this.bPlayer.canBendIgnoreCooldowns(this)) {
			this.remove();
			return;
		} else if (CoreAbility.hasAbility(player, FireJet.class) && !allowOnFireJet){
			this.removeWithTasks();
			return;
		}

		this.locations.clear();

		if (this.state == State.START) {
			if (this.bPlayer.isOnCooldown(this)) {
				this.remove();
				return;
			} else if (System.currentTimeMillis() - this.time > this.chargeTime) {
				this.charged = true;
			}

			if (this.charged) {
				if (this.player.isSneaking()) {
					final Location loc = this.player.getEyeLocation().add(this.player.getEyeLocation().getDirection().normalize().multiply(1.2));
					loc.add(0, 0.3, 0);
					playLightningbendingParticle(loc, 0.2F, 0.2F, 0.2F);
				} else {
					this.state = State.MAINBOLT;
					this.bPlayer.addCooldown(this);
					final Entity target = GeneralMethods.getTargetedEntity(this.player, this.range);
					this.origin = this.player.getEyeLocation();

					if (target != null) {
						this.destination = target.getLocation();
					} else {
						this.destination = this.player.getEyeLocation().add(this.player.getEyeLocation().getDirection().normalize().multiply(this.range));
					}
				}
			} else {
				if (!this.player.isSneaking()) {
					this.remove();
					return;
				}

				final Location localLocation1 = this.player.getLocation();
				final double d1 = 0.1570796326794897D;
				final double d2 = 0.06283185307179587D;
				final double d3 = 1.0D;
				final double d4 = 1.0D;
				final double d5 = d1 * this.particleRotation;
				final double d6 = d2 * this.particleRotation;
				final double d7 = localLocation1.getX() + d4 * Math.cos(d5);
				final double d8 = localLocation1.getZ() + d4 * Math.sin(d5);
				final double newY = (localLocation1.getY() + 1.0D + d4 * Math.cos(d6));
				final Location localLocation2 = new Location(this.player.getWorld(), d7, newY, d8);
				playLightningbendingParticle(localLocation2);
				this.particleRotation += 1.0D / d3;
			}

		} else if (this.state == State.MAINBOLT) {
			final Arc mainArc = new Arc(this.origin, this.destination);
			mainArc.generatePoints(POINT_GENERATION);
			this.arcs.add(mainArc);
			final ArrayList<Arc> subArcs = mainArc.generateArcs(this.subArcChance, this.range / 2.0, this.maxArcAngle);
			this.arcs.addAll(subArcs);
			this.state = State.STRIKE;
		} else if (this.state == State.STRIKE) {
			for (int i = 0; i < this.arcs.size(); i++) {
				final Arc arc = this.arcs.get(i);
				for (int j = 0; j < arc.getAnimationLocations().size() - 1; j++) {
					final Location iterLoc = arc.getAnimationLocations().get(j).getLocation().clone();
					final Location dest = arc.getAnimationLocations().get(j + 1).getLocation().clone();
					if (this.selfHitClose && this.player.getLocation().distanceSquared(iterLoc) < 9 && !this.isTransparentForLightning(this.player, iterLoc.getBlock()) && !this.affectedEntities.contains(this.player)) {
						this.affectedEntities.add(this.player);
						this.electrocute(this.player);
					}

					while (iterLoc.distanceSquared(dest) > 0.15 * 0.15) {
						final BukkitRunnable task = new LightningParticle(arc, iterLoc.clone(), this.selfHitWater, this.waterArcs);
						final double timer = arc.getAnimationLocations().get(j).getAnimCounter() / 2;
						task.runTaskTimer(ProjectKorra.plugin, (long) timer, 1);
						this.tasks.add(task);
						iterLoc.add(GeneralMethods.getDirection(iterLoc, dest).normalize().multiply(0.15));
					}
				}
				this.arcs.remove(i);
				i--;
			}
			if (this.tasks.size() == 0) {
				this.remove();
				return;
			}
		}
	}

	/**
	 * Removes the instance of this ability and cancels any current runnables
	 */
	public void removeWithTasks() {
		for (int i = 0; i < this.tasks.size(); i++) {
			this.tasks.get(i).cancel();
			i--;
		}
		this.remove();
	}

	/**
	 * Represents a Lightning Arc Point particle animation. This basically just
	 * holds a location and counts the amount of times that a particle has been
	 * animated.
	 **/
	public class AnimationLocation {
		private Location location;
		private int animationCounter;

		public AnimationLocation(final Location loc, final int animationCounter) {
			this.location = loc;
			this.animationCounter = animationCounter;
		}

		public int getAnimCounter() {
			return this.animationCounter;
		}

		public Location getLocation() {
			return this.location;
		}

		public void setAnimationCounter(final int animationCounter) {
			this.animationCounter = animationCounter;
		}

		public void setLocation(final Location location) {
			this.location = location;
		}
	}

	/**
	 * An Arc represents a Lightning arc for the specific ability. These Arcs
	 * contain a list of Particles that are used to display the entire arc. Arcs
	 * can also generate a list of subarcs that chain off of their own instance.
	 **/
	public class Arc {
		private int animationCounter;
		private Vector direction;
		private final ArrayList<Location> points;
		private final ArrayList<AnimationLocation> animationLocations;
		private final ArrayList<LightningParticle> particles;
		private final ArrayList<Arc> subArcs;

		public Arc(final Location startPoint, final Location endPoint) {
			this.points = new ArrayList<>();
			this.points.add(startPoint.clone());
			this.points.add(endPoint.clone());
			this.direction = GeneralMethods.getDirection(startPoint, endPoint);
			this.particles = new ArrayList<>();
			this.subArcs = new ArrayList<>();
			this.animationLocations = new ArrayList<>();
			this.animationCounter = 0;
		}

		/**
		 * Stops this Arc from further animating or doing damage.
		 */
		public void cancel() {
			for (int i = 0; i < this.particles.size(); i++) {
				this.particles.get(i).cancel();
			}

			for (final Arc subArc : this.subArcs) {
				subArc.cancel();
			}
		}

		/**
		 * Randomly generates subarcs off of this arc.
		 *
		 * @param chance The chance that an arc will be generated for each
		 *            specific point in the arc. Note: if you generate a lot of
		 *            points then chance will need to be lowered.
		 * @param range The length of each subarc.
		 *
		 **/
		public ArrayList<Arc> generateArcs(final double chance, final double range, final double maxArcAngle) {
			final ArrayList<Arc> arcs = new ArrayList<>();

			for (int i = 0; i < this.animationLocations.size(); i++) {
				if (Math.random() < chance) {
					final Location loc = this.animationLocations.get(i).getLocation();
					final double angle = (Math.random() - 0.5) * maxArcAngle * 2;
					final Vector dir = GeneralMethods.rotateXZ(this.direction.clone(), angle);
					final double randRange = (Math.random() * range) + (range / 3.0);

					final Location loc2 = loc.clone().add(dir.normalize().multiply(randRange));
					final Arc arc = new Arc(loc, loc2);

					this.subArcs.add(arc);
					arc.setAnimationCounter(this.animationLocations.get(i).getAnimCounter());
					arc.generatePoints(POINT_GENERATION);
					arcs.add(arc);
					arcs.addAll(arc.generateArcs(chance / 2.0, range / 2.0, maxArcAngle));
				}
			}
			return arcs;
		}

		/**
		 * Runs an arc generation algorithm by first creating two points, the
		 * starting point and the ending point. Next, it creates a point in the
		 * middle that has an offset relative to the beginning and end points.
		 * Now that the arc is split into 3 points, we continue this processes
		 * by generating middle points in the two halfs of this arc. This
		 * process continues the amount of times specified.
		 *
		 * @param times The amount of times that the arc will be split in half
		 *            causes O(n^2) complexity
		 **/
		public void generatePoints(final int times) {
			for (int i = 0; i < times; i++) {
				for (int j = 0; j < this.points.size() - 1; j += 2) {
					final Location loc1 = this.points.get(j);
					final Location loc2 = this.points.get(j + 1);
					double adjac = 0;
					if (loc1.getWorld().equals(loc2.getWorld())) {
						adjac = loc1.distance(loc2) / 2;
					}

					double angle = (Math.random() - 0.5) * Lightning.this.maxArcAngle;

					angle += angle >= 0 ? 10 : -10;

					final double radians = Math.toRadians(angle);
					final double hypot = adjac / Math.cos(radians);
					final Vector dir = GeneralMethods.rotateXZ(this.direction.clone(), angle);
					final Location newLoc = loc1.clone().add(dir.normalize().multiply(hypot));

					newLoc.add(0, (Math.random() - 0.5) / 2.0, 0);
					this.points.add(j + 1, newLoc);
				}
			}
			for (int i = 0; i < this.points.size(); i++) {
				this.animationLocations.add(new AnimationLocation(this.points.get(i), this.animationCounter));
				this.animationCounter++;
			}
		}

		public int getAnimationCounter() {
			return this.animationCounter;
		}

		public void setAnimationCounter(final int animationCounter) {
			this.animationCounter = animationCounter;
		}

		public Vector getDirection() {
			return this.direction;
		}

		public void setDirection(final Vector direction) {
			this.direction = direction;
		}

		public ArrayList<Location> getPoints() {
			return this.points;
		}

		public ArrayList<AnimationLocation> getAnimationLocations() {
			return this.animationLocations;
		}

		public ArrayList<LightningParticle> getParticles() {
			return this.particles;
		}

		public ArrayList<Arc> getSubArcs() {
			return this.subArcs;
		}

	}

	/**
	 * A Runnable Particle that continuously displays itself until it reaches a
	 * certain time limit.
	 *
	 * These LightningParticles do the actual checking for player collision and
	 * handle damaging any entities. These Runnables also check to see if they
	 * reach water, in which case they will generate subarcs to branch out.
	 **/
	public class LightningParticle extends BukkitRunnable {
		private boolean selfHitWater;
		private int count = 0;
		private int waterArcs;
		private Arc arc;
		private Location location;

		public LightningParticle(final Arc arc, final Location location, final boolean selfHitWater, final int waterArcs) {
			this.arc = arc;
			this.location = location;
			this.selfHitWater = selfHitWater;
			this.waterArcs = waterArcs;
			arc.particles.add(this);
		}

		/** Cancels this Runnable **/
		@Override
		public void cancel() {
			super.cancel();
			Lightning.this.tasks.remove(this);
		}

		/**
		 * Animates the Location, checks for water/player collision and also
		 * deals with any chain subarcs.
		 */
		@Override
		public void run() {
			playLightningbendingParticle(this.location, 0F, 0F, 0F);
			this.count++;
			if (this.count > 5) {
				this.cancel();
			} else if (this.count == 1) {
				if (!Lightning.this.isTransparentForLightning(Lightning.this.player, this.location.getBlock())) {
					this.arc.cancel();
					return;
				}
				final Block block = this.location.getBlock();
				// We only want to consider this particle as part of the location on the its first tick, when it actually does the electrocution.
				// The later ticks are just for visual purposes.
				Lightning.this.locations.add(block.getLocation());

				// Handle Water electrocution.
				if (!Lightning.this.hitWater && (isWater(block) || (Lightning.this.arcOnIce && isIce(block)))) {
					Lightning.this.hitWater = true;
					if (isIce(block)) {
						Lightning.this.hitIce = true;
					}

					for (int i = 0; i < this.waterArcs; i++) {
						final Location origin = this.location.clone();
						origin.add(new Vector((Math.random() - 0.5) * 2, 0, (Math.random() - 0.5) * 2));
						Lightning.this.destination = origin.clone().add(new Vector((Math.random() - 0.5) * Lightning.this.waterArcRange, Math.random() - 0.7, (Math.random() - 0.5) * Lightning.this.waterArcRange));
						final Arc newArc = new Arc(origin, Lightning.this.destination);
						newArc.generatePoints(POINT_GENERATION);
						Lightning.this.arcs.add(newArc);
					}
				}

				for (final Entity entity : GeneralMethods.getEntitiesAroundPoint(this.location, 2.5)) {

					// If the player is in water we will electrocute them only if they are standing in water. If the lightning hit ice we can electrocute them all the time.
					if (entity.equals(Lightning.this.player) && !(this.selfHitWater && Lightning.this.hitWater && isWater(Lightning.this.player.getLocation().getBlock())) && !(this.selfHitWater && Lightning.this.hitIce)) {
						continue;
					}

					if (entity instanceof LivingEntity && !Lightning.this.affectedEntities.contains(entity)) {
						Lightning.this.affectedEntities.add(entity);
						final LivingEntity lent = (LivingEntity) entity;
						if (lent instanceof Player) {
							playLightningbendingSound(lent.getLocation());
							playLightningbendingSound(Lightning.this.player.getLocation());
							final Player p = (Player) lent;
							final Lightning light = getAbility(p, Lightning.class);
							if (light != null && light.state == State.START) {
								light.charged = true;
								Lightning.this.remove();
								return;
							}
						}

						Lightning.this.electrocute(lent);

						// Handle Chain Lightning.
						if (Lightning.this.maxChainArcs >= 1 && Math.random() <= Lightning.this.chainArcChance) {
							Lightning.this.maxChainArcs--;
							for (final Entity ent : GeneralMethods.getEntitiesAroundPoint(lent.getLocation(), Lightning.this.chainRange)) {
								if (!ent.equals(Lightning.this.player) && !ent.equals(lent) && ent instanceof LivingEntity && !Lightning.this.affectedEntities.contains(ent)) {
									Lightning.this.origin = lent.getLocation().add(0, 1, 0);
									Lightning.this.destination = ent.getLocation().add(0, 1, 0);
									final Arc newArc = new Arc(Lightning.this.origin, Lightning.this.destination);
									newArc.generatePoints(POINT_GENERATION);
									Lightning.this.arcs.add(newArc);
									this.cancel();
									return;
								}
							}
						}
					}
				}
			}
		}

		public boolean isSelfHitWater() {
			return this.selfHitWater;
		}

		public void setSelfHitWater(final boolean selfHitWater) {
			this.selfHitWater = selfHitWater;
		}

		public int getCount() {
			return this.count;
		}

		public void setCount(final int count) {
			this.count = count;
		}

		public int getWaterArcs() {
			return this.waterArcs;
		}

		public void setWaterArcs(final int waterArcs) {
			this.waterArcs = waterArcs;
		}

		public Arc getArc() {
			return this.arc;
		}

		public void setArc(final Arc arc) {
			this.arc = arc;
		}

		public Location getLocation() {
			return this.location;
		}

		public void setLocation(final Location location) {
			this.location = location;
		}
	}

	@Override
	public String getName() {
		return "Lightning";
	}

	@Override
	public Location getLocation() {
		return this.origin;
	}

	@Override
	public long getCooldown() {
		return this.cooldown;
	}

	@Override
	public boolean isSneakAbility() {
		return true;
	}

	@Override
	public boolean isHarmlessAbility() {
		return false;
	}

	@Override
	public boolean isCollidable() {
		return this.arcs.size() > 0;
	}

	@Override
	public List<Location> getLocations() {
		return this.locations;
	}

	public boolean isCharged() {
		return this.charged;
	}

	public void setCharged(final boolean charged) {
		this.charged = charged;
	}

	public boolean isHitWater() {
		return this.hitWater;
	}

	public void setHitWater(final boolean hitWater) {
		this.hitWater = hitWater;
	}

	public boolean isHitIce() {
		return this.hitIce;
	}

	public void setHitIce(final boolean hitIce) {
		this.hitIce = hitIce;
	}

	public boolean isSelfHitWater() {
		return this.selfHitWater;
	}

	public void setSelfHitWater(final boolean selfHitWater) {
		this.selfHitWater = selfHitWater;
	}

	public boolean isSelfHitClose() {
		return this.selfHitClose;
	}

	public void setSelfHitClose(final boolean selfHitClose) {
		this.selfHitClose = selfHitClose;
	}

	public boolean isArcOnIce() {
		return this.arcOnIce;
	}

	public void setArcOnIce(final boolean arcOnIce) {
		this.arcOnIce = arcOnIce;
	}

	public int getWaterArcs() {
		return this.waterArcs;
	}

	public void setWaterArcs(final int waterArcs) {
		this.waterArcs = waterArcs;
	}

	public double getRange() {
		return this.range;
	}

	public void setRange(final double range) {
		this.range = range;
	}

	public double getChargeTime() {
		return this.chargeTime;
	}

	public void setChargeTime(final double chargeTime) {
		this.chargeTime = chargeTime;
	}

	public double getSubArcChance() {
		return this.subArcChance;
	}

	public void setSubArcChance(final double subArcChance) {
		this.subArcChance = subArcChance;
	}

	public double getDamage() {
		return this.damage;
	}

	public void setDamage(final double damage) {
		this.damage = damage;
	}

	public double getMaxChainArcs() {
		return this.maxChainArcs;
	}

	public void setMaxChainArcs(final double maxChainArcs) {
		this.maxChainArcs = maxChainArcs;
	}

	public double getChainRange() {
		return this.chainRange;
	}

	public void setChainRange(final double chainRange) {
		this.chainRange = chainRange;
	}

	public double getWaterArcRange() {
		return this.waterArcRange;
	}

	public void setWaterArcRange(final double waterArcRange) {
		this.waterArcRange = waterArcRange;
	}

	public double getChainArcChance() {
		return this.chainArcChance;
	}

	public void setChainArcChance(final double chainArcChance) {
		this.chainArcChance = chainArcChance;
	}

	public double getStunChance() {
		return this.stunChance;
	}

	public void setStunChance(final double stunChance) {
		this.stunChance = stunChance;
	}

	public double getStunDuration() {
		return this.stunDuration;
	}

	public void setStunDuration(final double stunDuration) {
		this.stunDuration = stunDuration;
	}

	public double getMaxArcAngle() {
		return this.maxArcAngle;
	}

	public void setMaxArcAngle(final double maxArcAngle) {
		this.maxArcAngle = maxArcAngle;
	}

	public double getParticleRotation() {
		return this.particleRotation;
	}

	public void setParticleRotation(final double particleRotation) {
		this.particleRotation = particleRotation;
	}

	public long getTime() {
		return this.time;
	}

	public void setTime(final long time) {
		this.time = time;
	}

	public State getState() {
		return this.state;
	}

	public void setState(final State state) {
		this.state = state;
	}

	public Location getOrigin() {
		return this.origin;
	}

	public void setOrigin(final Location origin) {
		this.origin = origin;
	}

	public Location getDestination() {
		return this.destination;
	}

	public void setDestination(final Location destination) {
		this.destination = destination;
	}

	public static int getPointGeneration() {
		return POINT_GENERATION;
	}

	public ArrayList<Entity> getAffectedEntities() {
		return this.affectedEntities;
	}

	public ArrayList<Arc> getArcs() {
		return this.arcs;
	}

	public ArrayList<BukkitRunnable> getTasks() {
		return this.tasks;
	}

	public void setCooldown(final long cooldown) {
		this.cooldown = cooldown;
	}

}
