package client.renderer;

import java.util.List;
import java.util.Map;

import org.lwjgl.opengl.GL11;

import client.Client;
import client.renderer.texture.Sprite;
import client.renderer.texture.TextureManager;
import client.renderer.texture.TextureMap;
import common.block.Block;
import common.block.Material;
import common.block.liquid.BlockLiquid;
import common.collect.Lists;
import common.collect.Maps;
import common.entity.Entity;
import common.init.BlockRegistry;
import common.init.Blocks;
import common.init.ItemRegistry;
import common.item.Item;
import common.item.consumable.ItemPotion;
import common.rng.Random;
import common.util.BlockPos;
import common.util.ExtMath;
import common.util.Facing;
import common.util.ParticleType;
import common.world.State;
import common.world.World;

public class EffectRenderer {
	private interface Effect {
		boolean onUpdate();
		void render(RenderBuffer rb, float partial, float rotX, float rotZ, float rotYZ, float rotXY, float rotXZ);
		int getLayer();
	}

	private abstract class Moveable implements Effect {
		protected double prevX;
		protected double prevY;
		protected double prevZ;
		protected double posX;
		protected double posY;
		protected double posZ;
		protected double motionX;
		protected double motionY;
		protected double motionZ;

		protected int age;
		protected int lifetime;
		protected float gravity;

		protected Moveable(double posXIn, double posYIn, double posZIn) {
			this.posX = this.prevX = posXIn;
			this.posY = this.prevY = posYIn;
			this.posZ = this.prevZ = posZIn;
			this.lifetime = (int)(4.0F / (rng.floatv() * 0.9F + 0.1F));
			this.age = 0;
		}

		public Moveable(double xCoordIn, double yCoordIn, double zCoordIn, double xSpeedIn, double ySpeedIn, double zSpeedIn) {
			this(xCoordIn, yCoordIn, zCoordIn);
			this.motionX = xSpeedIn + (Math.random() * 2.0D - 1.0D) * 0.4000000059604645D;
			this.motionY = ySpeedIn + (Math.random() * 2.0D - 1.0D) * 0.4000000059604645D;
			this.motionZ = zSpeedIn + (Math.random() * 2.0D - 1.0D) * 0.4000000059604645D;
			float f = (float)(Math.random() + Math.random() + 1.0D) * 0.15F;
			float f1 = ExtMath.sqrtd(this.motionX * this.motionX + this.motionY * this.motionY + this.motionZ * this.motionZ);
			this.motionX = this.motionX / (double)f1 * (double)f * 0.4000000059604645D;
			this.motionY = this.motionY / (double)f1 * (double)f * 0.4000000059604645D + 0.10000000149011612D;
			this.motionZ = this.motionZ / (double)f1 * (double)f * 0.4000000059604645D;
		}

		public final void move(double x, double y, double z) {
			this.posX += x;
			this.posY += y;
			this.posZ += z;
		}

		public int getBrightness(float partial) {
			BlockPos pos = new BlockPos(this.posX, this.posY, this.posZ);
			return world.isBlockLoaded(pos) ? world.getCombinedLight(pos, 0) : 0;
		}
	}

	private abstract class Particle extends Moveable {
		private int textureU;
		private int textureV;
		protected float scale;
		protected float red;
		protected float green;
		protected float blue;

		public Particle(double x, double y, double z, double xSpeed, double ySpeed, double zSpeed) {
			super(x, y, z, xSpeed, ySpeed, zSpeed);
			this.red = this.green = this.blue = 1.0F;
			this.scale = (rng.floatv() * 0.5F + 0.5F) * 2.0F;
		}

		protected void setScale(float partial) {
		}

		public final void render(RenderBuffer rb, float partial, float rotX, float rotZ, float rotYZ, float rotXY, float rotXZ) {
			this.setScale(partial);
			float u1 = (float)this.textureU / 8.0F;
			float u2 = u1 + 1.0f / 8.0f;
			float v1 = (float)this.textureV / 4.0F;
			float v2 = v1 + 1.0f / 4.0f;
			float scale = 0.1F * this.scale;
			float x = (float)(this.prevX + (this.posX - this.prevX) * (double)partial - interpPosX);
			float y = (float)(this.prevY + (this.posY - this.prevY) * (double)partial - interpPosY);
			float z = (float)(this.prevZ + (this.posZ - this.prevZ) * (double)partial - interpPosZ);
			int light = this.getBrightness(partial);
			int sky = light >> 16 & 65535;
			int block = light & 65535;
			rb.pos((double)(x - rotX * scale - rotXY * scale), (double)(y - rotZ * scale), (double)(z - rotYZ * scale - rotXZ * scale))
					.tex((double)u2, (double)v2).color(this.red, this.green, this.blue, 1.0f).lightmap(sky, block).endVertex();
			rb.pos((double)(x - rotX * scale + rotXY * scale), (double)(y + rotZ * scale), (double)(z - rotYZ * scale + rotXZ * scale))
					.tex((double)u2, (double)v1).color(this.red, this.green, this.blue, 1.0f).lightmap(sky, block).endVertex();
			rb.pos((double)(x + rotX * scale + rotXY * scale), (double)(y + rotZ * scale), (double)(z + rotYZ * scale + rotXZ * scale)).tex((double)u1, (double)v1)
					.color(this.red, this.green, this.blue, 1.0f).lightmap(sky, block).endVertex();
			rb.pos((double)(x + rotX * scale - rotXY * scale), (double)(y - rotZ * scale), (double)(z + rotYZ * scale - rotXZ * scale)).tex((double)u1, (double)v2)
					.color(this.red, this.green, this.blue, 1.0f).lightmap(sky, block).endVertex();
		}

		public final int getLayer() {
			return 0;
		}

		protected final void setUV(int x, int y) {
			this.textureU = x;
			this.textureV = y;
		}
	}

	private class Aura extends Particle {
		private final boolean fullBright;

		protected Aura(double xCoordIn, double yCoordIn, double zCoordIn, boolean fullBright,
				int texture) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0D, 0.0D, 0.0D);
			float f = texture != 0 ? 1.0f : rng.floatv() * 0.1F + 0.2F;
			this.red = f;
			this.green = f;
			this.blue = f;
			this.setUV(texture != 0 ? 5 + texture : 0, texture != 0 ? 2 : 0);
			this.scale *= rng.floatv() * 0.6F + 0.5F;
			this.motionX *= 0.019999999552965164D;
			this.motionY *= 0.019999999552965164D;
			this.motionZ *= 0.019999999552965164D;
			this.lifetime = (int)(20.0D / (Math.random() * 0.8D + 0.2D));
			this.fullBright = fullBright;
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;
			this.move(this.motionX, this.motionY, this.motionZ);
			this.motionX *= 0.99D;
			this.motionY *= 0.99D;
			this.motionZ *= 0.99D;

			if(this.lifetime-- <= 0) {
				return true;
			}
			return false;
		}

		public int getBrightness(float partialTicks) {
			return this.fullBright ? 15728880 : super.getBrightness(partialTicks);
		}
	}

	private class Crit extends Particle {
		private final float baseScale;

		protected Crit(double xCoordIn, double yCoordIn, double zCoordIn) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0D, 0.0D, 0.0D);
			this.motionX *= 0.10000000149011612D;
			this.motionY *= 0.10000000149011612D;
			this.motionZ *= 0.10000000149011612D;
			this.motionX += (double)(rng.floatv() * 2.0F - 1.0F) * 0.4D;
			this.motionY += ((double)(rng.floatv() * 2.0F - 1.0F) + 0.2) * 0.4D;
			this.motionZ += (double)(rng.floatv() * 2.0F - 1.0F) * 0.4D;
			this.blue = (float)(Math.random() * 0.30000001192092896D + 0.6000000238418579D);
			this.red = this.blue * 0.3f;
			this.green = this.blue * 0.8f;
			this.scale *= 0.75F;
			this.baseScale = this.scale;
			this.lifetime = (int)(6.0D / (Math.random() * 0.8D + 0.6D));
			this.setUV(4, 2);
			this.onUpdate();
		}

		protected void setScale(float partialTicks) {
			float f = ((float)this.age + partialTicks) / (float)this.lifetime * 32.0F;
			f = ExtMath.clampf(f, 0.0F, 1.0F);
			this.scale = this.baseScale * f;
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;

			if(this.age++ >= this.lifetime) {
				return true;
			}

			this.move(this.motionX, this.motionY, this.motionZ);
			this.green = (float)((double)this.green * 0.96D);
			this.blue = (float)((double)this.blue * 0.9D);
			this.motionX *= 0.699999988079071D;
			this.motionY *= 0.699999988079071D;
			this.motionZ *= 0.699999988079071D;
			this.motionY -= 0.019999999552965164D;
			return false;
		}
	}

	private class Downfall extends Particle {
		protected Downfall(double xCoordIn, double yCoordIn, double zCoordIn, int texture) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0D, 0.0D, 0.0D);
			this.motionX *= 0.30000001192092896D;
			this.motionY = Math.random() * 0.20000000298023224D + 0.10000000149011612D;
			this.motionZ *= 0.30000001192092896D;
			this.red = 1.0F;
			this.green = 1.0F;
			this.blue = 1.0F;
			this.setUV(rng.zrange(4), 1 + texture);
			this.gravity = 0.06F;
			this.lifetime = (int)(8.0D / (Math.random() * 0.8D + 0.2D));
		}

		protected Downfall(double xCoordIn, double yCoordIn, double zCoordIn) {
			this(xCoordIn, yCoordIn, zCoordIn, 0);
			this.gravity = 0.04F;
			this.setUV(1 + rng.zrange(3), 1);
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;
			this.motionY -= (double)this.gravity;
			this.move(this.motionX, this.motionY, this.motionZ);
			this.motionX *= 0.9800000190734863D;
			this.motionY *= 0.9800000190734863D;
			this.motionZ *= 0.9800000190734863D;

			if(this.lifetime-- <= 0) {
				return true;
			}

			BlockPos blockpos = new BlockPos(this.posX, this.posY, this.posZ);
			State iblockstate = world.getState(blockpos);
			Block block = iblockstate.getBlock();
			block.setBlockBounds(world, blockpos);
			Material material = iblockstate.getBlock().getMaterial();

			if(material.isLiquid() || material.isSolid()) {
				double d0 = 0.0D;

				if(iblockstate.getBlock() instanceof BlockLiquid) {
					d0 = (double)(1.0F - BlockLiquid.getLiquidHeightPercent(((Integer)iblockstate.getValue(BlockLiquid.LEVEL)).intValue()));
				}
				else {
					d0 = block.getBlockBoundsMaxY();
				}

				double d1 = (double)ExtMath.floord(this.posY) + d0;

				if(this.posY < d1) {
					return true;
				}
			}
			return false;
		}
	}

	private class Dust extends Particle {
		private final float baseScale;

		protected Dust(double xCoordIn, double yCoordIn, double zCoordIn, int color) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0D, 0.0D, 0.0D);
			this.motionX *= 0.10000000149011612D;
			this.motionY *= 0.10000000149011612D;
			this.motionZ *= 0.10000000149011612D;
			float f = (float)Math.random() * 0.4F + 0.6F;
			this.red = ((float)(Math.random() * 0.20000000298023224D) + 0.8F) * ((float)((color >> 16) & 0xff) / 255.0f) * f;
			this.green = ((float)(Math.random() * 0.20000000298023224D) + 0.8F) * ((float)((color >> 8) & 0xff) / 255.0f) * f;
			this.blue = ((float)(Math.random() * 0.20000000298023224D) + 0.8F) * ((float)(color & 0xff) / 255.0f) * f;
			this.scale *= 0.75F;
			this.baseScale = this.scale;
			this.lifetime = (int)(8.0D / (Math.random() * 0.8D + 0.2D));
		}

		protected void setScale(float partialTicks) {
			float f = ((float)this.age + partialTicks) / (float)this.lifetime * 32.0F;
			f = ExtMath.clampf(f, 0.0F, 1.0F);
			this.scale = this.baseScale * f;
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;

			if(this.age++ >= this.lifetime) {
				return true;
			}

			this.setUV(Math.max(0, 7 - this.age * 8 / this.lifetime), 0);
			this.move(this.motionX, this.motionY, this.motionZ);

			if(this.posY == this.prevY) {
				this.motionX *= 1.1D;
				this.motionZ *= 1.1D;
			}

			this.motionX *= 0.9599999785423279D;
			this.motionY *= 0.9599999785423279D;
			this.motionZ *= 0.9599999785423279D;
			return false;
		}
	}

	private class Explosion extends Particle {
		protected Explosion(double xCoordIn, double yCoordIn, double zCoordIn) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0D, 0.0D, 0.0D);
			this.motionX = (Math.random() * 2.0D - 1.0D) * 0.05000000074505806D;
			this.motionY = (Math.random() * 2.0D - 1.0D) * 0.05000000074505806D;
			this.motionZ = (Math.random() * 2.0D - 1.0D) * 0.05000000074505806D;
			this.red = this.green = this.blue = rng.floatv() * 0.3F + 0.7F;
			this.scale = rng.floatv() * rng.floatv() * 6.0F + 1.0F;
			this.lifetime = (int)(16.0D / ((double)rng.floatv() * 0.8D + 0.2D)) + 2;
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;

			if(this.age++ >= this.lifetime) {
				return true;
			}

			this.setUV(Math.max(0, 7 - this.age * 8 / this.lifetime), 0);
			this.motionY += 0.004D;
			this.move(this.motionX, this.motionY, this.motionZ);
			this.motionX *= 0.8999999761581421D;
			this.motionY *= 0.8999999761581421D;
			this.motionZ *= 0.8999999761581421D;
			return false;
		}
	}

	private class Flame extends Particle {
		private final float baseScale;

		protected Flame(double xCoordIn, double yCoordIn, double zCoordIn) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0D, 0.0D, 0.0D);
			this.motionX = this.motionX * 0.009999999776482582D;
			this.motionY = this.motionY * 0.009999999776482582D;
			this.motionZ = this.motionZ * 0.009999999776482582D;
			this.posX += (double)((rng.floatv() - rng.floatv()) * 0.05F);
			this.posY += (double)((rng.floatv() - rng.floatv()) * 0.05F);
			this.posZ += (double)((rng.floatv() - rng.floatv()) * 0.05F);
			this.baseScale = this.scale;
			this.red = this.green = this.blue = 1.0F;
			this.lifetime = (int)(8.0D / (Math.random() * 0.8D + 0.2D)) + 4;
			this.setUV(6, 1);
		}

		protected void setScale(float partialTicks) {
			float f = ((float)this.age + partialTicks) / (float)this.lifetime;
			this.scale = this.baseScale * (1.0F - f * f * 0.5F);
		}

		public int getBrightness(float partialTicks) {
			float f = ((float)this.age + partialTicks) / (float)this.lifetime;
			f = ExtMath.clampf(f, 0.0F, 1.0F);
			int i = super.getBrightness(partialTicks);
			int j = i & 255;
			int k = i >> 16 & 255;
			j = j + (int)(f * 15.0F * 16.0F);

			if(j > 240) {
				j = 240;
			}

			return j | k << 16;
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;

			if(this.age++ >= this.lifetime) {
				return true;
			}

			this.move(this.motionX, this.motionY, this.motionZ);
			this.motionX *= 0.9599999785423279D;
			this.motionY *= 0.9599999785423279D;
			this.motionZ *= 0.9599999785423279D;
			return false;
		}
	}

	private class Heart extends Particle {
		private final float baseScale;

		protected Heart(double xCoordIn, double yCoordIn, double zCoordIn) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0D, 0.0D, 0.0D);
			this.motionX *= 0.009999999776482582D;
			this.motionY *= 0.009999999776482582D;
			this.motionZ *= 0.009999999776482582D;
			this.motionY += 0.1D;
			this.scale *= 0.75F;
			this.baseScale = this.scale;
			this.lifetime = 16;
			this.setUV(5, 2);
		}

		protected void setScale(float partialTicks) {
			float f = ((float)this.age + partialTicks) / (float)this.lifetime * 32.0F;
			f = ExtMath.clampf(f, 0.0F, 1.0F);
			this.scale = this.baseScale * f;
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;

			if(this.age++ >= this.lifetime) {
				return true;
			}

			this.move(this.motionX, this.motionY, this.motionZ);

			if(this.posY == this.prevY) {
				this.motionX *= 1.1D;
				this.motionZ *= 1.1D;
			}

			this.motionX *= 0.8600000143051147D;
			this.motionY *= 0.8600000143051147D;
			this.motionZ *= 0.8600000143051147D;
			return false;
		}
	}

	private class LavaPop extends Particle {
		private final float baseScale;

		protected LavaPop(double xCoordIn, double yCoordIn, double zCoordIn) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0D, 0.0D, 0.0D);
			this.motionX *= 0.800000011920929D;
			this.motionY *= 0.800000011920929D;
			this.motionZ *= 0.800000011920929D;
			this.motionY = (double)(rng.floatv() * 0.4F + 0.05F);
			this.red = this.green = this.blue = 1.0F;
			this.scale *= rng.floatv() * 2.0F + 0.2F;
			this.baseScale = this.scale;
			this.lifetime = (int)(16.0D / (Math.random() * 0.8D + 0.2D));
			this.setUV(7, 1);
		}

		public int getBrightness(float partialTicks) {
			float f = ((float)this.age + partialTicks) / (float)this.lifetime;
			f = ExtMath.clampf(f, 0.0F, 1.0F);
			int i = super.getBrightness(partialTicks);
			int j = 240;
			int k = i >> 16 & 255;
			return j | k << 16;
		}

		protected void setScale(float partialTicks) {
			float f = ((float)this.age + partialTicks) / (float)this.lifetime;
			this.scale = this.baseScale * (1.0F - f * f);
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;

			if(this.age++ >= this.lifetime) {
				return true;
			}

			float f = (float)this.age / (float)this.lifetime;

			if(rng.floatv() > f) {
				world.clientParticle(ParticleType.SMOKE, this.posX, this.posY, this.posZ);
			}

			this.motionY -= 0.03D;
			this.move(this.motionX, this.motionY, this.motionZ);
			this.motionX *= 0.9990000128746033D;
			this.motionY *= 0.9990000128746033D;
			this.motionZ *= 0.9990000128746033D;
			return false;
		}
	}

	private class Teleport extends Particle {
		private final float baseScale;
		private final double baseX;
		private final double baseY;
		private final double baseZ;

		protected Teleport(double xCoordIn, double yCoordIn, double zCoordIn) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0, 0.0, 0.0);
			this.motionX = (rng.floatv() - 0.5F) * 0.2F;
			this.motionY = (rng.floatv() - 0.5F) * 0.2F;
			this.motionZ = (rng.floatv() - 0.5F) * 0.2F;
			this.baseX = this.posX = xCoordIn;
			this.baseY = this.posY = yCoordIn;
			this.baseZ = this.posZ = zCoordIn;
			float f = rng.floatv() * 0.6F + 0.4F;
			this.baseScale = this.scale = rng.floatv() * 0.2F + 0.5F;
			this.red = this.green = this.blue = 1.0F * f;
			this.green *= 0.1F;
			this.red *= 0.2F;
			this.blue *= 0.25F;
			this.lifetime = (int)(Math.random() * 10.0D) + 40;
			this.setUV((int)(Math.random() * 8.0D), 0);
		}

		protected void setScale(float partialTicks) {
			float f = ((float)this.age + partialTicks) / (float)this.lifetime;
			f = 1.0F - f;
			f = f * f;
			f = 1.0F - f;
			this.scale = this.baseScale * f;
		}

		public int getBrightness(float partialTicks) {
			int i = super.getBrightness(partialTicks);
			float f = (float)this.age / (float)this.lifetime;
			f = f * f;
			f = f * f;
			int j = i & 255;
			int k = i >> 16 & 255;
			k = k + (int)(f * 15.0F * 16.0F);

			if(k > 240) {
				k = 240;
			}

			return j | k << 16;
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;
			float f = (float)this.age / (float)this.lifetime;
			f = -f + f * f * 2.0F;
			f = 1.0F - f;
			this.posX = this.baseX + this.motionX * (double)f;
			this.posY = this.baseY + this.motionY * (double)f + (double)(1.0F - f);
			this.posZ = this.baseZ + this.motionZ * (double)f;

			if(this.age++ >= this.lifetime) {
				return true;
			}
			return false;
		}
	}

	private class Smoke extends Particle {
		private final float baseScale;

		protected Smoke(double xCoordIn, double yCoordIn, double zCoordIn) {
			super(xCoordIn, yCoordIn, zCoordIn, 0.0D, 0.0D, 0.0D);
			this.motionX *= 0.10000000149011612D;
			this.motionY *= 0.10000000149011612D;
			this.motionZ *= 0.10000000149011612D;
			this.red = this.green = this.blue = (float)(Math.random() * 0.30000001192092896D);
			this.scale *= 0.75F;
			this.baseScale = this.scale;
			this.lifetime = (int)(8.0D / (Math.random() * 0.8D + 0.2D));
		}

		protected void setScale(float partialTicks) {
			float f = ((float)this.age + partialTicks) / (float)this.lifetime * 32.0F;
			f = ExtMath.clampf(f, 0.0F, 1.0F);
			this.scale = this.baseScale * f;
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;

			if(this.age++ >= this.lifetime) {
				return true;
			}

			this.setUV(Math.max(0, 7 - this.age * 8 / this.lifetime), 0);
			this.motionY += 0.004D;
			this.move(this.motionX, this.motionY, this.motionZ);

			if(this.posY == this.prevY) {
				this.motionX *= 1.1D;
				this.motionZ *= 1.1D;
			}

			this.motionX *= 0.9599999785423279D;
			this.motionY *= 0.9599999785423279D;
			this.motionZ *= 0.9599999785423279D;
			return false;
		}
	}

	private class Spell extends Particle {
		private final boolean effect;
		
		protected Spell(double xCoordIn, double yCoordIn, double zCoordIn, ItemPotion potion) {
			this(xCoordIn, yCoordIn, zCoordIn, potion, rng.doublev() * 4.0D, rng.doublev() * Math.PI * 2.0D);
		}

		private Spell(double xCoordIn, double yCoordIn, double zCoordIn, ItemPotion potion,
				double d22, double d23) {
			super(xCoordIn + Math.cos(d23) * d22 * 0.1, yCoordIn + 0.3, zCoordIn + Math.sin(d23) * d22 * 0.1, 0.5 - rng.doublev(),
					0.01 + rng.doublev() * 0.5, 0.5 - rng.doublev());
			this.motionY *= 0.20000000298023224D;

			if(Math.cos(d23) * d22 == 0.0D && Math.sin(d23) * d22 == 0.0D) {
				this.motionX *= 0.10000000149011612D;
				this.motionZ *= 0.10000000149011612D;
			}

			this.scale *= 0.75F;
			this.lifetime = (int)(8.0D / (Math.random() * 0.8D + 0.2D));

			float f = 1.0F;
			float f1 = 1.0F;
			float f2 = 1.0F;
			if(this.effect = potion.getEffect() != null) {
				int j1 = potion.getEffect().getPotion().getColor();
				f = (float)(j1 >> 16 & 255) / 255.0F;
				f1 = (float)(j1 >> 8 & 255) / 255.0F;
				f2 = (float)(j1 >> 0 & 255) / 255.0F;
			}
			else {
				this.setUV(1 + rng.zrange(3), 1);
			}
			this.motionX *= d22;
			this.motionY = (this.motionY - 0.10000000149011612D) * d22 + 0.10000000149011612D;
			this.motionZ *= d22;
			float f3 = 0.75F + rng.floatv() * 0.25F;
			this.red = f * f3;
			this.green = f1 * f3;
			this.blue = f2 * f3;
		}

		public boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;

			if(this.age++ >= this.lifetime) {
				return true;
			}

			if(this.effect) {
				this.setUV(Math.max(0, 7 - this.age * 8 / this.lifetime), 3);
				this.motionY += 0.004;
			}
			else {
				this.motionY -= 0.004;
			}
			this.move(this.motionX, this.motionY, this.motionZ);

			if(this.posY == this.prevY) {
				this.motionX *= 1.1D;
				this.motionZ *= 1.1D;
			}

			this.motionX *= 0.9599999785423279D;
			this.motionY *= 0.9599999785423279D;
			this.motionZ *= 0.9599999785423279D;
			return false;
		}
	}

	private class Icon extends Moveable {
		private final Sprite icon;
		private final float offsetX;
		private final float offsetY;
		private final float size;

		protected float red;
		protected float green;
		protected float blue;

		private Icon(double x, double y, double z, float size, Sprite icon) {
			super(x, y, z, 0.0, 0.0, 0.0);
			this.size = (rng.floatv() * 0.5F + 0.5F) * size * 0.1f;
			this.icon = icon;
			this.offsetX = rng.floatv() * 3.0F;
			this.offsetY = rng.floatv() * 3.0F;
		}

		protected Icon(double x, double y, double z, State state, boolean hit) {
			this(x, y, z, hit ? 0.6f : 1.0f, Client.CLIENT.renderer.getModelManager().getTexture(state));
			this.gravity = 1.0F;
			this.red = this.green = this.blue = 0.6F;
			if(hit) {
				this.motionX *= 0.2;
				this.motionY = (this.motionY - 0.1) * 0.2 + 0.1;
				this.motionZ *= 0.2;
			}
		}

		protected Icon(double x, double y, double z, Item item) {
			this(x, y, z, 1.0f, Client.CLIENT.getRenderItem().getItemModelMesher().getParticleIcon(item));
			this.red = this.green = this.blue = 1.0F;
			this.gravity = 1.0F;
			this.motionX *= 0.10000000149011612D;
			this.motionY *= 0.10000000149011612D;
			this.motionZ *= 0.10000000149011612D;
		}

		public final int getLayer() {
			return 1;
		}

		public final void render(RenderBuffer rb, float partial, float rotX, float rotZ, float rotYZ, float rotXY, float rotXZ) {
			float u1 = this.icon.getInterpolatedU((double)(this.offsetX * 4.0F));
			float u2 = this.icon.getInterpolatedU((double)((this.offsetX + 1.0F) * 4.0F));
			float v1 = this.icon.getInterpolatedV((double)(this.offsetY * 4.0F));
			float v2 = this.icon.getInterpolatedV((double)((this.offsetY + 1.0F) * 4.0F));
			float scale = this.size;
			float x = (float)(this.prevX + (this.posX - this.prevX) * (double)partial - interpPosX);
			float y = (float)(this.prevY + (this.posY - this.prevY) * (double)partial - interpPosY);
			float z = (float)(this.prevZ + (this.posZ - this.prevZ) * (double)partial - interpPosZ);
			int light = this.getBrightness(partial);
			int sky = light >> 16 & 65535;
			int block = light & 65535;
			rb.pos((double)(x - rotX * scale - rotXY * scale), (double)(y - rotZ * scale), (double)(z - rotYZ * scale - rotXZ * scale))
					.tex((double)u1, (double)v2).color(this.red, this.green, this.blue, 1.0F).lightmap(sky, block).endVertex();
			rb.pos((double)(x - rotX * scale + rotXY * scale), (double)(y + rotZ * scale), (double)(z - rotYZ * scale + rotXZ * scale))
					.tex((double)u1, (double)v1).color(this.red, this.green, this.blue, 1.0F).lightmap(sky, block).endVertex();
			rb.pos((double)(x + rotX * scale + rotXY * scale), (double)(y + rotZ * scale), (double)(z + rotYZ * scale + rotXZ * scale))
					.tex((double)u2, (double)v1).color(this.red, this.green, this.blue, 1.0F).lightmap(sky, block).endVertex();
			rb.pos((double)(x + rotX * scale - rotXY * scale), (double)(y - rotZ * scale), (double)(z + rotYZ * scale - rotXZ * scale))
					.tex((double)u2, (double)v2).color(this.red, this.green, this.blue, 1.0F).lightmap(sky, block).endVertex();
		}

		public final boolean onUpdate() {
			this.prevX = this.posX;
			this.prevY = this.posY;
			this.prevZ = this.posZ;
			if(this.age++ >= this.lifetime)
				return true;
			this.motionY -= 0.04D * (double)this.gravity;
			this.move(this.motionX, this.motionY, this.motionZ);
			this.motionX *= 0.9800000190734863D;
			this.motionY *= 0.9800000190734863D;
			this.motionZ *= 0.9800000190734863D;
			return false;
		}
	}

	private class Textured implements Effect {
		private static final VertexFormat VERTEX_FORMAT = (new VertexFormat()).addElement(DefaultVertexFormats.POSITION_3F)
				.addElement(DefaultVertexFormats.TEX_2F).addElement(DefaultVertexFormats.COLOR_4UB).addElement(DefaultVertexFormats.TEX_2S)
				.addElement(DefaultVertexFormats.NORMAL_3B).addElement(DefaultVertexFormats.PADDING_1B);

		private final String texture;
		private final int texWidth;
		private final int texHeight;
		private final float scale;
		private final float brightness;
		private final double posX;
		private final double posY;
		private final double posZ;

		private int age;
		private int lifetime;
		private TextureManager manager;

		protected Textured(TextureManager manager, double x, double y, double z, float scale, String texture, int width, int height) {
			this.manager = manager;
			this.lifetime = 6 + rng.zrange(4);
			this.brightness = rng.floatv() * 0.6F + 0.4F;
			this.scale = scale;
			this.texture = "textures/world/" + texture + ".png";
			this.texWidth = width;
			this.texHeight = height;
			this.posX = x;
			this.posY = y;
			this.posZ = z;
		}

		public final void render(RenderBuffer rb, float partial, float rotX, float rotZ, float rotYZ, float rotXY, float rotXZ) {
			int idx = (int)(((float)this.age + partial) * (float)(this.texWidth * this.texHeight - 1) / (float)this.lifetime);
			if(idx < this.texWidth * this.texHeight) {
				this.manager.bindTexture(this.texture);
				float u1 = (float)(idx % this.texWidth) / (float)this.texWidth;
				float u2 = u1 + 1.0f / (float)this.texWidth;
				float v1 = (float)(idx / this.texWidth) / (float)this.texHeight;
				float v2 = v1 + 1.0f / (float)this.texHeight;
				float scale = 2.0F * (1.0F - this.scale * 0.5F);
				float x = (float)(this.posX - interpPosX);
				float y = (float)(this.posY - interpPosY);
				float z = (float)(this.posZ - interpPosZ);
				GlState.color(1.0F, 1.0F, 1.0F, 1.0F);
				GlState.disableLighting();
				ItemRenderer.disableStandardItemLighting();
				rb.begin(GL11.GL_QUADS, VERTEX_FORMAT);
				rb.pos((double)(x - rotX * scale - rotXY * scale), (double)(y - rotZ * scale), (double)(z - rotYZ * scale - rotXZ * scale))
						.tex((double)u2, (double)v2).color(this.brightness, this.brightness, this.brightness, 1.0F).lightmap(0, 240)
						.normal(0.0F, 1.0F, 0.0F).endVertex();
				rb.pos((double)(x - rotX * scale + rotXY * scale), (double)(y + rotZ * scale), (double)(z - rotYZ * scale + rotXZ * scale))
						.tex((double)u2, (double)v1).color(this.brightness, this.brightness, this.brightness, 1.0F).lightmap(0, 240)
						.normal(0.0F, 1.0F, 0.0F).endVertex();
				rb.pos((double)(x + rotX * scale + rotXY * scale), (double)(y + rotZ * scale), (double)(z + rotYZ * scale + rotXZ * scale))
						.tex((double)u1, (double)v1).color(this.brightness, this.brightness, this.brightness, 1.0F).lightmap(0, 240)
						.normal(0.0F, 1.0F, 0.0F).endVertex();
				rb.pos((double)(x + rotX * scale - rotXY * scale), (double)(y - rotZ * scale), (double)(z + rotYZ * scale - rotXZ * scale))
						.tex((double)u1, (double)v2).color(this.brightness, this.brightness, this.brightness, 1.0F).lightmap(0, 240)
						.normal(0.0F, 1.0F, 0.0F).endVertex();
				Tessellator.draw();
				GlState.enableLighting();
			}
		}

		public final boolean onUpdate() {
			if(++this.age >= this.lifetime)
				return true;
			return false;
		}

		public final int getLayer() {
			return 2;
		}
	}

	private abstract class Spawner implements Effect {
		protected final int amount;
		protected final int lifetime;

		protected int age;

		protected Spawner(int amount, int lifetime) {
			this.amount = amount;
			this.lifetime = lifetime;
		}

		public final boolean onUpdate() {
			for(int z = 0; z < this.amount; z++) {
				this.spawnParticle();
			}
			if(++this.age >= this.lifetime)
				return true;
			return false;
		}

		public final int getLayer() {
			return 3;
		}

		public final void render(RenderBuffer rb, float partial, float rotX, float rotZ, float rotYZ, float rotXY, float rotXZ) {
		}

		protected abstract void spawnParticle();
	}

	private class HitSpawner extends Spawner {
		private final Entity entity;

		public HitSpawner(Entity entity) {
			super(16, 3);
			this.entity = entity;
			this.onUpdate();
		}

		public void spawnParticle() {
			double x = (double)(rng.floatv() * 2.0F - 1.0F);
			double y = (double)(rng.floatv() * 2.0F - 1.0F);
			double z = (double)(rng.floatv() * 2.0F - 1.0F);
			if(x * x + y * y + z * z <= 1.0D)
				world.clientParticle(ParticleType.CRIT, this.entity.posX + x * (double)this.entity.width / 4.0D,
						this.entity.getEntityBoundingBox().minY + (double)(this.entity.height / 2.0F) + y * (double)this.entity.height / 4.0D,
						this.entity.posZ + z * (double)this.entity.width / 4.0D);
		}
	}

	private class ExplosionSpawner extends Spawner {
		private final double posX;
		private final double posY;
		private final double posZ;

		protected ExplosionSpawner(double x, double y, double z) {
			super(6, 8);
			this.posX = x;
			this.posY = y;
			this.posZ = z;
		}

		public void spawnParticle() {
			double x = this.posX + (rng.doublev() - rng.doublev()) * 4.0D;
			double y = this.posY + (rng.doublev() - rng.doublev()) * 4.0D;
			double z = this.posZ + (rng.doublev() - rng.doublev()) * 4.0D;
			world.clientParticle(ParticleType.EXPLOSION_LARGE, x, y, z, (int)(100.0f * (float)this.age / (float)this.lifetime));
		}
	}

	private interface Creator {
		Effect create(double x, double y, double z, int data);
	}

	private static final String TEXTURE = "textures/world/particles.png";

	private final Random rng = new Random();
	private final List<Effect>[] layers = new List[4];
	private final TextureManager manager;
	private final Random rand = new Random();
	private final Map<ParticleType, Creator> types = Maps.<ParticleType, Creator>newEnumMap(ParticleType.class);

	private World world;
	private double interpPosX;
	private double interpPosY;
	private double interpPosZ;

	public EffectRenderer(TextureManager manager) {
		this.manager = manager;
		for(int i = 0; i < this.layers.length; ++i) {
			this.layers[i] = Lists.newArrayList();
		}
		this.registerEffects();
	}

	private void register(ParticleType type, Creator factory) {
		this.types.put(type, factory);
	}

	private void registerEffects() {
		this.register(ParticleType.EXPLOSION_NORMAL, (x, y, z, u) -> new Explosion(x, y, z));
		this.register(ParticleType.SPLASH, (x, y, z, u) -> new Downfall(x, y, z));
		this.register(ParticleType.WATER_DROP, (x, y, z, u) -> new Downfall(x, y, z, 0));
		this.register(ParticleType.DEPTH, (x, y, z, u) -> new Aura(x, y, z, true, 0));
		this.register(ParticleType.CRIT, (x, y, z, u) -> new Crit(x, y, z));
		this.register(ParticleType.SMOKE, (x, y, z, u) -> new Smoke(x, y, z));
		this.register(ParticleType.POTION, (x, y, z, id) -> new Spell(x, y, z, ItemPotion.getPotionItem(id)));
		this.register(ParticleType.GROW, (x, y, z, u) -> new Aura(x, y, z, false, 1));
		this.register(ParticleType.SPORE, (x, y, z, u) -> new Aura(x, y, z, false, 0));
		this.register(ParticleType.TELEPORT, (x, y, z, u) -> new Teleport(x, y, z));
		this.register(ParticleType.FLAME, (x, y, z, u) -> new Flame(x, y, z));
		this.register(ParticleType.LAVA, (x, y, z, u) -> new LavaPop(x, y, z));
		this.register(ParticleType.DUST, (x, y, z, color) -> new Dust(x, y, z, color));
		this.register(ParticleType.HEART, (x, y, z, u) -> new Heart(x, y, z));
		this.register(ParticleType.ITEM_CRACK, (x, y, z, id) -> {
			Item item = ItemRegistry.byId(id);
			return item == null ? null : new Icon(x, y, z, item);
		});
		this.register(ParticleType.BLOCK_CRACK, (x, y, z, id) -> {
			State state = BlockRegistry.byId(id);
			return state == null ? null : new Icon(x, y, z, state, false);
		});
		this.register(ParticleType.EXPLOSION_HUGE, (x, y, z, u) -> new ExplosionSpawner(x, y, z));
		this.register(ParticleType.EXPLOSION_LARGE,
				(x, y, z, scale) -> new Textured(this.manager, x, y, z, (float)scale / 100.0f, "explosion", 4, 4));
		this.register(ParticleType.HAIL_CORN, (x, y, z, u) -> new Downfall(x, y, z, 1));
	}

	public void setWorld(World world) {
		this.world = world;
		for(int i = 0; i < this.layers.length; ++i) {
			this.layers[i].clear();
		}
	}

	public void update() {
		for(int i = 0; i < this.layers.length; ++i) {
			List<Effect> layer = this.layers[i];
			List<Effect> list = Lists.<Effect>newArrayList();
			for(int n = 0; n < layer.size(); ++n) {
				Effect effect = layer.get(n);
				if(effect.onUpdate())
					list.add(effect);
			}
			layer.removeAll(list);
		}
	}

	public void render(Entity entity, float partial) {
		float f = MatrixState.getRotationX();
		float f1 = MatrixState.getRotationZ();
		float f2 = MatrixState.getRotationYZ();
		float f3 = MatrixState.getRotationXY();
		float f4 = MatrixState.getRotationXZ();
		this.interpPosX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * (double)partial;
		this.interpPosY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * (double)partial;
		this.interpPosZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * (double)partial;
		GlState.enableBlend();
		GlState.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
		GlState.alphaFunc(GL11.GL_GREATER, 0.003921569F);

		for(int i = 0; i < 2; ++i) {
			if(!this.layers[i].isEmpty()) {
				GlState.depthMask(true);

				switch(i) {
				case 0:
				default:
					this.manager.bindTexture(TEXTURE);
					break;

				case 1:
					this.manager.bindTexture(TextureMap.BLOCKS);
				}

				GlState.color(1.0F, 1.0F, 1.0F, 1.0F);
				RenderBuffer worldrenderer = Tessellator.getBuffer();
				worldrenderer.begin(GL11.GL_QUADS, DefaultVertexFormats.PARTICLE_POSITION_TEX_COLOR_LMAP);

				for(int k = 0; k < this.layers[i].size(); ++k) {
					this.layers[i].get(k).render(worldrenderer, partial, f, f4, f1, f2, f3);
				}

				Tessellator.draw();
			}
		}

		GlState.depthMask(true);
		GlState.disableBlend();
		GlState.alphaFunc(GL11.GL_GREATER, 0.1F);
	}

	public void renderTextured(Entity entity, float partial) {
		float f = 0.017453292F;
		float f1 = ExtMath.cos(entity.rotYaw * 0.017453292F);
		float f2 = ExtMath.sin(entity.rotYaw * 0.017453292F);
		float f3 = -f2 * ExtMath.sin(entity.rotPitch * 0.017453292F);
		float f4 = f1 * ExtMath.sin(entity.rotPitch * 0.017453292F);
		float f5 = ExtMath.cos(entity.rotPitch * 0.017453292F);

		List<Effect> list = this.layers[2];

		if(!list.isEmpty()) {
			RenderBuffer worldrenderer = Tessellator.getBuffer();

			for(int j = 0; j < list.size(); ++j) {
				list.get(j).render(worldrenderer, partial, f1, f5, f2, f3, f4);
			}
		}
	}

	private void add(Effect effect) {
		int i = effect.getLayer();
		if(this.layers[i].size() >= 4000)
			this.layers[i].remove(0);
		this.layers[i].add(effect);
	}

	public void spawnParticle(Entity entity, ParticleType type, double x, double y, double z, int data) {
		if(entity != null) {
			double dx = entity.posX - x;
			double dy = entity.posY - y;
			double dz = entity.posZ - z;
			if(type.isUnlimited() || dx * dx + dy * dy + dz * dz <= 256.0D) {
				Creator creator = this.types.get(type);
				if(creator != null) {
					Effect effect = creator.create(x, y, z, data);
					if(effect != null)
						this.add(effect);
				}
			}
		}
	}

	public void destroyBlock(BlockPos pos, State state) {
		if(state.getBlock() != Blocks.air) {
			state = state.getBlock().getState(state, this.world, pos);
			int i = 4;

			for(int j = 0; j < i; ++j) {
				for(int k = 0; k < i; ++k) {
					for(int l = 0; l < i; ++l) {
						double d0 = (double)pos.getX() + ((double)j + 0.5D) / (double)i;
						double d1 = (double)pos.getY() + ((double)k + 0.5D) / (double)i;
						double d2 = (double)pos.getZ() + ((double)l + 0.5D) / (double)i;
						this.add(new Icon(d0, d1, d2, state, false));
					}
				}
			}
		}
	}

	public void damageBlock(BlockPos pos, Facing side) {
		State iblockstate = this.world.getState(pos);
		Block block = iblockstate.getBlock();

		if(block != Blocks.air) {
			int i = pos.getX();
			int j = pos.getY();
			int k = pos.getZ();
			float f = 0.1F;
			double d0 = (double)i + this.rng.doublev() * (block.getBlockBoundsMaxX() - block.getBlockBoundsMinX() - (double)(f * 2.0F)) + (double)f
					+ block.getBlockBoundsMinX();
			double d1 = (double)j + this.rng.doublev() * (block.getBlockBoundsMaxY() - block.getBlockBoundsMinY() - (double)(f * 2.0F)) + (double)f
					+ block.getBlockBoundsMinY();
			double d2 = (double)k + this.rng.doublev() * (block.getBlockBoundsMaxZ() - block.getBlockBoundsMinZ() - (double)(f * 2.0F)) + (double)f
					+ block.getBlockBoundsMinZ();

			if(side == Facing.DOWN) {
				d1 = (double)j + block.getBlockBoundsMinY() - (double)f;
			}

			if(side == Facing.UP) {
				d1 = (double)j + block.getBlockBoundsMaxY() + (double)f;
			}

			if(side == Facing.NORTH) {
				d2 = (double)k + block.getBlockBoundsMinZ() - (double)f;
			}

			if(side == Facing.SOUTH) {
				d2 = (double)k + block.getBlockBoundsMaxZ() + (double)f;
			}

			if(side == Facing.WEST) {
				d0 = (double)i + block.getBlockBoundsMinX() - (double)f;
			}

			if(side == Facing.EAST) {
				d0 = (double)i + block.getBlockBoundsMaxX() + (double)f;
			}

			this.add(new Icon(d0, d1, d2, iblockstate, true));
		}
	}

	public void spawnCritParticles(Entity entity) {
		this.add(new HitSpawner(entity));
	}

	public int getParticleCount() {
		int i = 0;
		for(int j = 0; j < 3; ++j) {
			i += this.layers[j].size();
		}
		return i;
	}
}
