package me.zombie_striker.qg.guns.utils;

import com.alessiodp.parties.api.Parties;
import com.alessiodp.parties.api.interfaces.PartiesAPI;
import com.alessiodp.parties.api.interfaces.Party;
import com.alessiodp.parties.api.interfaces.PartyPlayer;
import me.zombie_striker.customitemmanager.CustomBaseObject;
import me.zombie_striker.qg.QAMain;
import me.zombie_striker.qg.ammo.Ammo;
import me.zombie_striker.qg.api.QAHeadShotEvent;
import me.zombie_striker.qg.api.QAWeaponDamageBlockEvent;
import me.zombie_striker.qg.api.QAWeaponDamageEntityEvent;
import me.zombie_striker.qg.api.QualityArmory;
import me.zombie_striker.qg.armor.BulletProtectionUtil;
import me.zombie_striker.qg.boundingbox.AbstractBoundingBox;
import me.zombie_striker.qg.boundingbox.BoundingBoxManager;
import me.zombie_striker.qg.guns.Gun;
import me.zombie_striker.qg.handlers.*;
import org.bukkit.*;
import org.bukkit.attribute.Attribute;
import org.bukkit.attribute.AttributeModifier;
import org.bukkit.block.Block;
import org.bukkit.entity.*;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import org.bukkit.util.Vector;
import ru.beykerykt.lightapi.LightAPI;
import ru.beykerykt.lightapi.chunks.ChunkInfo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;

public class GunUtil {

	public static HashMap<UUID, BukkitTask> rapidfireshooters = new HashMap<>();
	public static HashMap<UUID, Double> highRecoilCounter = new HashMap<>();
	protected static HashMap<UUID, Location> AF_locs = new HashMap<>();
	protected static HashMap<UUID, BukkitTask> AF_tasks = new HashMap<>();

	public static void shootHandler(Gun g, Player p) {
		shootHandler(g, p, g.getBulletsPerShot());
	}

	public static void shootHandler(Gun g, Player p, int numberOfBullets) {
		double sway = g.getSway() * AimManager.getSway(g, p.getUniqueId());
		if (g.usesCustomProjctiles()) {
			for (int i = 0; i < numberOfBullets; i++) {
				Vector go = p.getLocation().getDirection().normalize();
				go.add(new Vector((Math.random() * 2 * sway) - sway, (Math.random() * 2 * sway) - sway,
						(Math.random() * 2 * sway) - sway)).normalize();
				Vector two = go.clone();// .multiply();
				g.getCustomProjectile().spawn(g, p.getEyeLocation(), p, two);
			}
		} else {
			shootInstantVector(g, p, sway, g.getDurabilityDamage(), g.getBulletsPerShot(), g.getMaxDistance());
		}
	}

	public static double getTargetedSolidMaxDistance(Vector v, Location start, double maxDistance) {
		Location test = start.clone();
		Block previous = null;
		for (double i = 0; i < maxDistance; i += v.length()) {
			if (test.getBlock() == previous) {
				previous = test.getBlock();
				test.add(v);
				continue;
			}
			if (test.getBlock().getType() != Material.AIR) {
				if (isSolid(test.getBlock(), test))
					return start.distance(test);
			}
			previous = test.getBlock();
			test.add(v);
		}
		return maxDistance;
	}

	@SuppressWarnings("deprecation")
	public static void shootInstantVector(Gun g, Player p, double sway, double damage, int shots, int range) {
		boolean timingsReport = false;
		long time1 = System.currentTimeMillis();
		long time2 = 0;
		long time3 = 0;
		long time4point5 = 0;
		long time4 = 0;
		for (int i = 0; i < shots; i++) {
			Location start = p.getEyeLocation().clone();
			Vector normalizedDirection = p.getLocation().getDirection().normalize();
			normalizedDirection.add(new Vector((Math.random() * 2 * sway) - sway, (Math.random() * 2 * sway) - sway,
					(Math.random() * 2 * sway) - sway));
			normalizedDirection = normalizedDirection.normalize();
			Vector step = normalizedDirection.clone().multiply(QAMain.bulletStep);

			// Simple values to make it easier on the search
			// boolean posX = go.getX() > 0;
			// boolean posZ = go.getZ() > 0;
			Entity hitTarget = null;
			AbstractBoundingBox hitBox = null;

			Location bulletHitLoc = null;

			double maxDistance = getTargetedSolidMaxDistance(step, start, range);
			double maxEntityDistance = maxDistance;
			double maxEntityDistanceSquared = maxDistance * maxDistance;

			// double degreeVector = Math.atan2(normalizedDirection.getX(),
			// normalizedDirection.getZ());
			// if (degreeVector > Math.PI)
			// degreeVector = 2 * Math.PI - degreeVector;

			List<Location> blocksThatWillPLAYBreak = new ArrayList<>();
			List<Location> blocksThatWillBreak = new ArrayList<>();

			Location centerTest = start.clone().add(normalizedDirection.clone().multiply(maxDistance / 2));

			for (Entity e : centerTest.getWorld().getNearbyEntities(centerTest, maxDistance / 2, maxDistance / 2,
					maxDistance / 2)) {
				if (e instanceof Damageable) {
					if (QAMain.avoidTypes.contains(e.getType()))
						continue;

					if (e != p && e != p.getVehicle() && e != p.getPassenger()) {
						double entityDistanceSquared = e.getLocation().distanceSquared(start);
						if (entityDistanceSquared >= maxEntityDistanceSquared)
							continue;
						double entityDistance = e.getLocation().distance(start);

						AbstractBoundingBox box = BoundingBoxManager.getBoundingBox(e);

						Location bulletLocationTest = start.clone();
						// If the entity is close to the line of fire.
						if (e instanceof Player) {
							Player player = (Player) e;
							if (player.getGameMode() == GameMode.SPECTATOR) {
								continue;
							}
							if (QAMain.hasParties && (!QAMain.friendlyFire)) {
								try {
									PartiesAPI api = Parties.getApi();
									String partyName = api.getPartyPlayer(p.getUniqueId()).getPartyName();
									if (!partyName.isEmpty() && partyName.equalsIgnoreCase(api.getPartyPlayer(e.getUniqueId()).getPartyName())) {
										Party party = api.getParty(partyName);
										if (party != null && party.isFriendlyFireProtected()) {
											continue;
										}
									}
								} catch (Error | Exception e43) {

								}
							}
						}
						// Clear this to make sure
						double checkDistanceMax = box.maximumCheckingDistance(e);
						double startDistance = Math.max(entityDistance - (checkDistanceMax), 0);

						//Get it somewhere close to the entity
						if (startDistance > 0)
							bulletLocationTest.add(normalizedDirection.clone().multiply(startDistance));

						for (double testDistance = startDistance; testDistance < entityDistance + (checkDistanceMax); testDistance += step.length()) {
							bulletLocationTest.add(step);
							if (box.intersects(p, bulletLocationTest, e)) {
								bulletHitLoc = bulletLocationTest;
								maxEntityDistance = entityDistance;
								maxEntityDistanceSquared = entityDistanceSquared;
								hitTarget = e;
								hitBox = box;
								//headShot = box.allowsHeadshots() ? box.intersectsHead(bulletLocationTest, e) : false;
								break;
							}
						}
					}
				}
			}
			time2 = System.currentTimeMillis();


			if (hitTarget != null) {
				if (!(hitTarget instanceof Player) || QualityArmory.allowGunsInRegion(hitTarget.getLocation())) {

					boolean headshot = hitBox.allowsHeadshots() && hitBox.intersectsHead(bulletHitLoc, hitTarget);
					if (headshot) {
						QAMain.DEBUG("Headshot!");
						if (QAMain.headshotPling) {
							try {
								p.playSound(p.getLocation(), QAMain.headshot_sound, 2, 1);
								if (!QAMain.isVersionHigherThan(1, 9))
									try {
										p.playSound(p.getLocation(), Sound.valueOf("LAVA_POP"), 6, 1);
									} catch (Error | Exception h4) {
									}

							} catch (Error | Exception h4) {
								p.playSound(p.getLocation(), Sound.valueOf("LAVA_POP"), 1, 1);
							}
						}
					}

					boolean negateHeadshot = false;
					boolean bulletProtection = false;

					if (hitTarget instanceof Player) {
						bulletProtection = BulletProtectionUtil.stoppedBullet(p, bulletHitLoc, normalizedDirection);
						if (headshot) {
							negateHeadshot = BulletProtectionUtil.negatesHeadshot(p);
						}
					}

					double damageMAX = damage * (bulletProtection ? 0.1 : 1)
							* ((headshot && !negateHeadshot) ? (QAMain.HeadshotOneHit ? 50 * g.getHeadshotMultiplier() : g.getHeadshotMultiplier())
							: 1);


					QAWeaponDamageEntityEvent shootevent = new QAWeaponDamageEntityEvent(p, g, hitTarget, headshot,
							damage, bulletProtection);
					Bukkit.getPluginManager().callEvent(shootevent);
					if (!shootevent.isCanceled()) {
						if (headshot) {
							QAHeadShotEvent headshotevent = new QAHeadShotEvent(hitTarget, p, g);
							Bukkit.getPluginManager().callEvent(headshotevent);
							headshot = !headshotevent.isCanceled();
						}
						if (hitTarget instanceof Player) {
							Player player = (Player) hitTarget;
							if (!QAMain.enableArmorIgnore) {
								try {
									// damage = damage * ( 1 - min( 20, max( defensePoints / 5, defensePoints -
									// damage / ( toughness / 4 + 2 ) ) ) / 25 )
									double defensePoints = 0;
									double toughness = 0;
									for (ItemStack is : new ItemStack[]{player.getInventory().getHelmet(),
											player.getInventory().getChestplate(), player.getInventory().getLeggings(),
											player.getInventory().getBoots()}) {
										if (is != null) {
											if (!is.getItemMeta().getAttributeModifiers(Attribute.GENERIC_ARMOR)
													.isEmpty())
												for (AttributeModifier a : is.getItemMeta()
														.getAttributeModifiers(Attribute.GENERIC_ARMOR))
													defensePoints += a.getAmount();
											for (AttributeModifier a : is.getItemMeta()
													.getAttributeModifiers(Attribute.GENERIC_ARMOR_TOUGHNESS))
												toughness += a.getAmount();
										}
									}
									damageMAX = damageMAX * (1 - Math.min(20, Math.max(defensePoints / 5,
											defensePoints - damageMAX / (toughness / 4 + 2))) / 25);
								} catch (Error | Exception e5) {

								}
							}

							if (!bulletProtection) {
								BulletWoundHandler.bulletHit((Player) hitTarget,
										g.getAmmoType() == null ? 1 : g.getAmmoType().getPiercingDamage());
							} else {
								hitTarget.sendMessage(QAMain.S_BULLETPROOFSTOPPEDBLEEDING);
							}
						}

						if (hitTarget instanceof LivingEntity) {
							((LivingEntity) hitTarget).setNoDamageTicks(0);
							QAMain.DEBUG("Damaging entity " + hitTarget.getName() + " ( "
									+ ((LivingEntity) hitTarget).getHealth() + "/"
									+ ((LivingEntity) hitTarget).getMaxHealth() + " :" + damageMAX + " DAM)");
						}

						if (hitTarget instanceof Damageable) {
							((Damageable) hitTarget).damage(damageMAX, p);
						} else if (hitTarget instanceof EnderDragon) {
							((EnderDragon) hitTarget).damage(damageMAX, p);
						} else if (hitTarget instanceof EnderDragonPart) {
							((EnderDragonPart) hitTarget).damage(damageMAX, p);
						}

					} else {
						if (hitTarget instanceof LivingEntity) {
							QAMain.DEBUG("Damaging entity CANCELED " + hitTarget.getName() + " ( "
									+ ((LivingEntity) hitTarget).getHealth() + "/"
									+ ((LivingEntity) hitTarget).getMaxHealth() + " :" + damageMAX + " DAM)");
						} else {
							QAMain.DEBUG("Damaging entity CANCELED " + hitTarget.getType() + ".");
						}
					}

				}
			} else {
				QAMain.DEBUG("No enities hit.");
			}
			time3 = System.currentTimeMillis();
			if (QAMain.enableBulletTrails) {
				List<Player> nonheard = start.getWorld().getPlayers();
				nonheard.remove(p);
				if (g.useMuzzleSmoke())
					ParticleHandlers.spawnMuzzleSmoke(p, start.clone().add(step.clone().multiply(7)));
				double distSqrt = maxEntityDistance;
				Vector stepSmoke = normalizedDirection.clone().multiply(QAMain.smokeSpacing);
				for (double dist = 0; dist < distSqrt; dist += QAMain.smokeSpacing) {
					start.add(stepSmoke);

					if (start.getBlock().getType() != Material.AIR) {
						boolean solid = isSolid(start.getBlock(), start);
						QAWeaponDamageBlockEvent blockevent = new QAWeaponDamageBlockEvent(p, g, start.getBlock());
						Bukkit.getPluginManager().callEvent(blockevent);
						if (!blockevent.isCanceled()) {
							if ((solid || isBreakable(start.getBlock(), start)) && !blocksThatWillPLAYBreak.contains(
									new Location(start.getWorld(), start.getBlockX(), start.getBlockY(), start.getBlockZ()))) {
								blocksThatWillPLAYBreak.add(
										new Location(start.getWorld(), start.getBlockX(), start.getBlockY(), start.getBlockZ()));
							}
							if (QAMain.destructableBlocks.contains(start.getBlock().getType())) {
								blocksThatWillBreak.add(start);
							}
						}
					}

					try {
						int control = 3;
						if (dist % control == 0) {
							List<Player> heard = new ArrayList<>();
							for (Player p2 : nonheard) {
								if (p2.getLocation().distanceSquared(start) <= (control * control * 4)) {
									try {
										start.getWorld().playSound(start, Sound.BLOCK_DISPENSER_LAUNCH, 2, 3);
									} catch (Error e) {
										start.getWorld().playSound(start, Sound.valueOf("SHOOT_ARROW"), 2, 2);
									}
									heard.add(p2);
								}
							}
							for (Player p3 : heard) {
								nonheard.remove(p3);
							}
						}

					} catch (Error | Exception e53) {
						if (dist % 30 == 0) {
							try {
								start.getWorld().playSound(start, Sound.BLOCK_DISPENSER_LAUNCH, 2, 2);
								start.getWorld().playSound(start, Sound.BLOCK_FIRE_EXTINGUISH, 2, 2);
							} catch (Error e) {
								start.getWorld().playSound(start, Sound.valueOf("SHOOT_ARROW"), 2, 2);
								start.getWorld().playSound(start, Sound.valueOf("FIRE_IGNITE"), 2, 2);
							}
						}
					}
					ParticleHandlers.spawnGunParticles(g, start);
				}

				for (Location l : blocksThatWillBreak) {
					l.getBlock().breakNaturally();
				}
			}

			time4point5 = System.currentTimeMillis();
			// TODO: Do lights n stuff
			try {
				if (Bukkit.getPluginManager().getPlugin("LightAPI") != null) {
					if (p.getEyeLocation().getBlock().getLightLevel() < g.getLightOnShoot()) {
						final Location loc = p.getEyeLocation().clone();
						LightAPI.createLight(loc, g.getLightOnShoot(), false);
						for (ChunkInfo c : LightAPI.collectChunks(loc)) {
							LightAPI.updateChunk(c);
						}
						new BukkitRunnable() {

							@Override
							public void run() {
								LightAPI.deleteLight(loc, false);
								for (ChunkInfo c : LightAPI.collectChunks(loc)) {
									LightAPI.updateChunk(c);
								}
							}
						}.runTaskLater(QAMain.getInstance(), 3);
					}
				}
			} catch (Error | Exception e5) {
			}

			// Breaking texture
			if (QAMain.blockBreakTexture)
				for (@SuppressWarnings("unused")
						Location l : blocksThatWillPLAYBreak) {
					start.getWorld().playSound(start, SoundHandler.getSoundWhenShot(start.getBlock()), 2, 1);
					try {/*
					 * for (Player p2 : l.getWorld().getPlayers()) {
					 * com.comphenix.protocol.events.PacketContainer packet = new
					 * com.comphenix.protocol.events.PacketContainer(
					 * com.comphenix.protocol.Packets.Server.BLOCK_BREAK_ANIMATION);
					 * packet.getIntegers().write(0, p2.getEntityId());
					 * packet.getBlockPositionModifier().write(1, new
					 * com.comphenix.protocol.wrappers.BlockPosition(l.getBlockX(), l.getBlockY(),
					 * l.getBlockZ())); packet.getBytes().write(2, (byte) 4);
					 * com.comphenix.protocol.ProtocolLibrary.getProtocolManager().sendServerPacket(
					 * p2, packet); }
					 */
					} catch (Error | Exception e4) {
					}
				}
			time4 = System.currentTimeMillis();
		}
		if (timingsReport) {
			System.out.println("time1 = " + time1);
			System.out.println("time2 = " + time2);
			System.out.println("time3 = " + time3);
			System.out.println("time3.5 = " + time4point5);
			System.out.println("time4 = " + time4);
		}
	}

	public static void basicShoot(boolean offhand, Gun g, Player player, double acc) {
		basicShoot(offhand, g, player, acc, 1);
	}

	@SuppressWarnings("deprecation")
	public static void basicShoot(boolean offhand, final Gun g, final Player player, final double acc, int times) {
		long showdelay = ((int) (g.getDelayBetweenShotsInSeconds() * 1000));
		QAMain.DEBUG("About to shoot!");

		if (g.getChargingVal() != null && (g.getChargingVal().isCharging(player)
				|| (g.getReloadingingVal() != null && g.getReloadingingVal().isReloading(player))))
			return;

		if (g.getLastShotForGun().containsKey(player.getUniqueId())
				&& (System.currentTimeMillis() - g.getLastShotForGun().get(player.getUniqueId()) < showdelay)) {
			QAMain.DEBUG("Shooting canceled due to last shot being too soon.");
			return;
		}
		g.getLastShotForGun().put(player.getUniqueId(), System.currentTimeMillis());


		if (rapidfireshooters.containsKey(player.getUniqueId())) {
			QAMain.DEBUG("Shooting canceled due to rapid fire being enabled.");
			return;
		}

		ItemStack firstGunInstance = IronsightsHandler.getItemAiming(player);

		boolean regularshoot = true;

		if (g.getChargingVal() != null) {
			QAMain.DEBUG("Charging shoot debug: " + g.getName() + " = " + g.getChargingVal() == null ? "null"
					: g.getChargingVal().getName());
			regularshoot = g.getChargingVal().shoot(g, player, firstGunInstance);
		}

		if (regularshoot) {
			GunUtil.shootHandler(g, player);
			playShoot(g, player);
			if (QAMain.enableRecoil)
				addRecoil(player, g);
		}

		if (g.isAutomatic()) {
			rapidfireshooters.put(player.getUniqueId(), new BukkitRunnable() {
				int slotUsed = player.getInventory().getHeldItemSlot();

				@Override
				public void run() {
					if (g.getChargingVal() != null && g.getChargingVal().isCharging(player)) {
						return;
					}

					if ((!g.hasIronSights() || !IronsightsHandler.isAiming(player)) && ((player.isSneaking() == QAMain.enableSwapSingleShotOnAim))) {
						cancel();
						QAMain.DEBUG("Stopping Automatic Firing");
						rapidfireshooters.remove(player.getUniqueId());
						return;
					}

					ItemStack temp = IronsightsHandler.getItemAiming(player);

					if (QAMain.enableDurability && g.getDurabilityDamage(temp) <= 0) {
						player.playSound(player.getLocation(), WeaponSounds.METALHIT.getSoundName(), 1, 1);
						rapidfireshooters.remove(player.getUniqueId());
						QAMain.DEBUG("Canceld due to weapon durability = " + g.getDurabilityDamage(temp));
						cancel();
						return;
					}

					int amount = Gun.getAmount(temp);
					if ((player.isSneaking() == QAMain.enableSwapSingleShotOnAim) || slotUsed != player.getInventory().getHeldItemSlot() || amount <= 0) {
						rapidfireshooters.remove(player.getUniqueId()).cancel();
						return;
					}

					boolean regularshoot = true;
					if (g.getChargingVal() != null && (!g.getChargingVal().isCharging(player)
							&& (g.getReloadingingVal() == null || !g.getReloadingingVal().isReloading(player)))) {
						regularshoot = g.getChargingVal().shoot(g, player, temp);
						QAMain.DEBUG(
								"Charging (rapidfire) shoot debug: " + g.getName() + " = " + g.getChargingVal() == null
										? "null"
										: g.getChargingVal().getName());
					}

					if (regularshoot) {
						GunUtil.shootHandler(g, player);
						playShoot(g, player);
						if (QAMain.enableRecoil)
							addRecoil(player, g);
						// TODO: recoil
					}
					amount--;

					if (amount < 0)
						amount = 0;

					ItemMeta im = temp.getItemMeta();
					int slot;
					if (offhand) {
						slot = -1;
					} else {
						slot = player.getInventory().getHeldItemSlot();
					}
					Gun.updateAmmo(g, im, amount);
					temp.setItemMeta(im);
					if (slot == -1) {
						try {
							if (QualityArmory.isIronSights(player.getItemInHand())) {
								player.getInventory().setItemInOffHand(temp);
								QAMain.DEBUG("Sett Offhand because ironsights in main hand");
							} else {
								player.getInventory().setItemInHand(temp);
								QAMain.DEBUG("Set mainhand because ironsights not in main hand");
							}

						} catch (Error e) {
						}
					} else {
						ItemStack tempCheck = player.getInventory().getItem(slot);
						if (QualityArmory.isIronSights(tempCheck)) {
							CustomBaseObject tempBase = QualityArmory.getCustomItem(Update19OffhandChecker.getItemStackOFfhand(player));
							if (tempBase != null && tempBase == g) {
								Update19OffhandChecker.setOffhand(player, temp);
							}
						} else {
							player.getInventory().setItem(slot, temp);
						}
					}
					QualityArmory.sendHotbarGunAmmoCount(player, g, temp, false);
				}
			}.runTaskTimer(QAMain.getInstance(), 10 / g.getFireRate(), 10 / g.getFireRate()));
		}

		int amount = Gun.getAmount(firstGunInstance) - 1;

		if (amount < 0)
			amount = 0;

		int slot;
		if (offhand) {
			slot = -1;
		} else {
			slot = player.getInventory().getHeldItemSlot();
		}
		ItemMeta im = firstGunInstance.getItemMeta();
		Gun.updateAmmo(g, im, amount);
		firstGunInstance.setItemMeta(im);
		if (slot == -1) {
			try {
				if (QualityArmory.isIronSights(player.getItemInHand())) {
					player.getInventory().setItemInOffHand(firstGunInstance);
					QAMain.DEBUG("Sett Offhand because ironsights in main hand");
				} else {
					player.getInventory().setItemInHand(firstGunInstance);
					QAMain.DEBUG("Set mainhand because ironsights not in main hand");
				}
			} catch (Error e) {
			}
		} else {
			player.getInventory().setItem(slot, firstGunInstance);
		}
	}

	public static void playShoot(final Gun g, final Player player) {
		g.damageDurability(player);
		new BukkitRunnable() {
			@SuppressWarnings("deprecation")
			@Override
			public void run() {
				try {
					String soundname = null;
					if (g.getWeaponSounds().size() > 1) {
						soundname = g.getWeaponSounds()
								.get(ThreadLocalRandom.current().nextInt(g.getWeaponSounds().size()));
					} else {
						soundname = g.getWeaponSound();
					}
					player.getWorld().playSound(player.getLocation(), soundname, (float) g.getVolume(), 1);
					if (!QAMain.isVersionHigherThan(1, 9)) {
						try {
							player.getWorld().playSound(player.getLocation(), Sound.valueOf("CLICK"), 5, 1);
							player.getWorld().playSound(player.getLocation(), Sound.valueOf("WITHER_SHOOT"), 8, 2);
							player.getWorld().playSound(player.getLocation(), Sound.valueOf("EXPLODE"), 8, 2f);
						} catch (Error | Exception e3) {
							player.getWorld().playSound(player.getLocation(), Sound.valueOf("BLOCK_LEVER_CLICK"), 5, 1);
						}
					}
				} catch (Error e2) {
					player.getWorld().playSound(player.getLocation(), Sound.valueOf("CLICK"), 5, 1);
					player.getWorld().playSound(player.getLocation(), Sound.valueOf("WITHER_SHOOT"), 8, 2);
					player.getWorld().playSound(player.getLocation(), Sound.valueOf("EXPLODE"), 8, 2f);
				}

			}
		}.runTaskLater(QAMain.getInstance(), 1);
		// Simply delaying the sound by 1/20th of a second makes shooting so much more
		// immersive
	}

	public static boolean hasAmmo(Player player, Gun g) {
		return QualityArmory.getAmmoInInventory(player, g.getAmmoType()) > 0;
	}

	public static void basicReload(final Gun g, final Player player, boolean doNotRemoveAmmo) {
		basicReload(g, player, doNotRemoveAmmo, 1.5);
	}

	public static void basicReload(final Gun g, final Player player, boolean doNotRemoveAmmo, double seconds) {

		final ItemStack temp = player.getInventory().getItemInHand();
		ItemMeta im = temp.getItemMeta();

		if (Gun.getAmount(temp) == g.getMaxBullets()) {
			return;
		}
		if (im == null || !im.hasDisplayName())
			return;
		if (im.getLore() != null && im.getDisplayName().contains(QAMain.S_RELOADING_MESSAGE)) {

		} else {
			try {
				player.getWorld().playSound(player.getLocation(), WeaponSounds.RELOAD_MAG_OUT.getSoundName(), 1, 1f);
			} catch (Error e2) {
				try {
					player.getWorld().playSound(player.getLocation(), Sound.valueOf("CLICK"), 5, 1);
				} catch (Error | Exception e3) {
					player.getWorld().playSound(player.getLocation(), Sound.valueOf("BLOCK_LEVER_CLICK"), 5, 1);
				}
			}
			final int slot = player.getInventory().getHeldItemSlot();

			Ammo ammo = g.getAmmoType();

			final int initialAmount = Gun.getAmount(temp);
			final int reloadAmount = doNotRemoveAmmo ? g.getMaxBullets()
					: Math.min(g.getMaxBullets(), initialAmount + QualityArmory.getAmmoInInventory(player, ammo));
			final int subtractAmount = reloadAmount - initialAmount;
			if (!doNotRemoveAmmo)
				QualityArmory.removeAmmoFromInventory(player, ammo, subtractAmount);

			if (g.getReloadingingVal() != null) {
				seconds = g.getReloadingingVal().reload(player, g, subtractAmount);
			}
			QAMain.toggleNightvision(player, g, false);
			//Gun.updateAmmo(g, im, initialAmount);
			im.setDisplayName(g.getDisplayName() + QAMain.S_RELOADING_MESSAGE);
			temp.setItemMeta(im);
			player.getInventory().setItem(slot, temp);

			new GunRefillerRunnable(player, temp, g, slot, initialAmount, reloadAmount, seconds);

		}

	}

	public static void addRecoil(final Player player, final Gun g) {
		if (g.getRecoil() == 0)
			return;
		if (g.getFireRate() >= 4) {
			if (highRecoilCounter.containsKey(player.getUniqueId())) {
				highRecoilCounter.put(player.getUniqueId(),
						highRecoilCounter.get(player.getUniqueId()) + g.getRecoil());
			} else {
				highRecoilCounter.put(player.getUniqueId(), g.getRecoil());
				new BukkitRunnable() {
					@Override
					public void run() {
						if (QAMain.hasProtocolLib && QAMain.isVersionHigherThan(1, 13) && !QAMain.hasViaVersion) {
							addRecoilWithProtocolLib(player, g, true);
						} else
							addRecoilWithTeleport(player, g, true);
					}
				}.runTaskLater(QAMain.getInstance(), 3);
			}
		} else {
			if (QAMain.hasProtocolLib && QAMain.isVersionHigherThan(1, 13)) {
				addRecoilWithProtocolLib(player, g, false);
			} else
				addRecoilWithTeleport(player, g, false);
		}
	}

	private static void addRecoilWithProtocolLib(Player player, Gun g, boolean useHighRecoil) {
		Vector newDir = player.getLocation().getDirection();
		newDir.normalize()
				.setY(newDir.getY()
						+ ((useHighRecoil ? highRecoilCounter.get(player.getUniqueId()) : g.getRecoil()) / 30))
				.multiply(20);
		if (useHighRecoil)
			highRecoilCounter.remove(player.getUniqueId());
		ProtocolLibHandler.sendYawChange(player, newDir);
	}

	private static void addRecoilWithTeleport(Player player, Gun g, boolean useHighRecoil) {
		Location tempCur = (QAMain.recoilHelperMovedLocation.get(player.getUniqueId()));
		final Location current;
		if (tempCur == null) {
			current = player.getLocation();
		} else {
			current = tempCur;
		}
		Vector movementOffset = player.getVelocity().multiply(0.2);
		if (movementOffset.getY() > -0.1 && movementOffset.getY() < 0)
			movementOffset.setY(0);
		current.add(movementOffset);
		current.setPitch((float) (current.getPitch()
				- (useHighRecoil ? highRecoilCounter.get(player.getUniqueId()) : g.getRecoil())));
		if (useHighRecoil)
			highRecoilCounter.remove(player.getUniqueId());
		Vector temp = player.getVelocity();
		// player.getLocation().setDirection(vector);
		player.teleport(current);
		player.setVelocity(temp);
	}

	public static boolean isBreakable(Block b, Location l) {
		if (b.getType().name().contains("GLASS"))
			return true;
		return false;

	}

	@SuppressWarnings("deprecation")
	public static boolean isSolid(Block b, Location l) {
		return BlockCollisionUtil.isSolid(b, l);
	}
}