package electroblob.wizardry.client.renderer;

import electroblob.wizardry.Wizardry;
import electroblob.wizardry.potion.PotionContainment;
import electroblob.wizardry.registry.WizardryPotions;
import electroblob.wizardry.util.WizardryUtilities;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.BufferBuilder;
import net.minecraft.client.renderer.GlStateManager;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.nbt.NBTUtil;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraftforge.client.event.RenderWorldLastEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import net.minecraftforge.fml.relauncher.Side;
import org.lwjgl.opengl.GL11;

@Mod.EventBusSubscriber(Side.CLIENT)
public class RenderContainmentField {

	private static final ResourceLocation[] textures = new ResourceLocation[8];

	private static final float ANIMATION_SPEED = 0.004f;
	private static final float FADE_DISTANCE_SQUARED = 15;

	static {
		for(int i=0; i<textures.length; i++){
			textures[i] = new ResourceLocation(Wizardry.MODID, "textures/entity/containment_field_" + i + ".png");
		}
	}

	@SubscribeEvent
	public static void onRenderWorldLastEvent(RenderWorldLastEvent event){

		EntityPlayer player = Minecraft.getMinecraft().player;

		if(player.isPotionActive(WizardryPotions.containment)){

			Vec3d centre = WizardryUtilities.getCentre(NBTUtil.getPosFromTag(player.getEntityData().getCompoundTag(PotionContainment.ENTITY_TAG)));
			float r = PotionContainment.getContainmentDistance(player.getActivePotionEffect(WizardryPotions.containment).getAmplifier());

			GlStateManager.pushMatrix();
			GlStateManager.enableBlend();
			GlStateManager.shadeModel(GL11.GL_SMOOTH); // Allows the vertex colours to produce a gradient
			GlStateManager.disableLighting();
			OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240);
			GlStateManager.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

			// Enables tiling (Also used for guardian beam, beacon beam and ender crystal beam)
			GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
			GlStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);

			// Translate the texture in the 2D space (like the creeper charge layer)
			GlStateManager.matrixMode(GL11.GL_TEXTURE);
			GlStateManager.loadIdentity();
			float distance = (player.ticksExisted + event.getPartialTicks()) * ANIMATION_SPEED;
			GlStateManager.translate(distance, distance, 0);
			GlStateManager.matrixMode(GL11.GL_MODELVIEW);

			GlStateManager.color(1, 1, 1, 1);

			Minecraft.getMinecraft().renderEngine.bindTexture(textures[(player.ticksExisted % (textures.length * 2))/2]);

			Tessellator tessellator = Tessellator.getInstance();

			// The aim here is to have only the bits of the containment field near the player be visible (if any).
			// There's probably a really neat, advanced way of doing this with OpenGL (stencil buffer...?) but in the
			// absence of OpenGL skills, I've devised a cheat which kinda achieves the effect we want:

			// - Split each face of the cube into four, where the split point is taken orthogonal to the player (the
			//   player is taken as (0, 0, 0) for simplicity, unlike most render classes where the entire thing is
			//   translated first)
			// - Then have the alpha value for each vertex change based on its distance from the player
			// - N.B. OpenGL renders quads as two triangles, with the 1st and 3rd vertices forming the line between them.
			//   This causes weirdness in the interpolation since the interpolation isn't taking the whole square into
			//   account. The order of the vertices below ensures that the seams between the triangles form an X shape
			//   on each of the faces, meaning that you always see a square shape appear rather than a random polygon.

			Vec3d relative = centre.subtract(player.getPositionEyes(event.getPartialTicks())).add(0, player.getEyeHeight(), 0);

			double x1 = relative.x - r;
			double y1 = relative.y - r;
			double z1 = relative.z - r;
			double x2 = relative.x + r;
			double y2 = relative.y + r;
			double z2 = relative.z + r;

			float alpha = Math.min(1, player.getActivePotionEffect(WizardryPotions.containment).getDuration() / 40f);

			BufferBuilder buffer = tessellator.getBuffer();
			buffer.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX_COLOR);

			// The following code is mostly an incomprehensible mess of numbers, but unfortunately without a lot of
			// fancy vector stuff it has to be like this, and besides, this is faster!

			// Bottom
			drawVertex(buffer, x1, y1, z1, 0, 0, alpha);
			drawVertex(buffer, x1, y1, 0, 0, -(float)z1, alpha);
			drawVertex(buffer, 0, y1, 0, -(float)x1, -(float)z1, alpha);
			drawVertex(buffer, 0, y1, z1, -(float)x1, 0, alpha);

			drawVertex(buffer, 0, y1, 0, -(float)x1, -(float)z1, alpha);
			drawVertex(buffer, x2, y1, 0, 2*r, -(float)z1, alpha);
			drawVertex(buffer, x2, y1, z1, 2*r, 0, alpha);
			drawVertex(buffer, 0, y1, z1, -(float)x1, 0, alpha);

			drawVertex(buffer, x1, y1, z2, 0, 2*r, alpha);
			drawVertex(buffer, 0, y1, z2, -(float)x1, 2*r, alpha);
			drawVertex(buffer, 0, y1, 0, -(float)x1, -(float)z1, alpha);
			drawVertex(buffer, x1, y1, 0, 0, -(float)z1, alpha);

			drawVertex(buffer, 0, y1, 0, -(float)x1, -(float)z1, alpha);
			drawVertex(buffer, 0, y1, z2, -(float)x1, 2*r, alpha);
			drawVertex(buffer, x2, y1, z2, 2*r, 2*r, alpha);
			drawVertex(buffer, x2, y1, 0, 2*r, -(float)z1, alpha);

			// Top
			drawVertex(buffer, x1, y2, z1, 0, 0, alpha);
			drawVertex(buffer, 0, y2, z1, -(float)x1, 0, alpha);
			drawVertex(buffer, 0, y2, 0, -(float)x1, -(float)z1, alpha);
			drawVertex(buffer, x1, y2, 0, 0, -(float)z1, alpha);

			drawVertex(buffer, x2, y2, z1, 2*r, 0, alpha);
			drawVertex(buffer, x2, y2, 0, 2*r, -(float)z1, alpha);
			drawVertex(buffer, 0, y2, 0, -(float)x1, -(float)z1, alpha);
			drawVertex(buffer, 0, y2, z1, -(float)x1, 0, alpha);

			drawVertex(buffer, 0, y2, 0, -(float)x1, -(float)z1, alpha);
			drawVertex(buffer, 0, y2, z2, -(float)x1, 2*r, alpha);
			drawVertex(buffer, x1, y2, z2, 0, 2*r, alpha);
			drawVertex(buffer, x1, y2, 0, 0, -(float)z1, alpha);

			drawVertex(buffer, 0, y2, 0, -(float)x1, -(float)z1, alpha);
			drawVertex(buffer, x2, y2, 0, 2*r, -(float)z1, alpha);
			drawVertex(buffer, x2, y2, z2, 2*r, 2*r, alpha);
			drawVertex(buffer, 0, y2, z2, -(float)x1, 2*r, alpha);

			// North
			drawVertex(buffer, x1, y1, z1, 0, 0, alpha);
			drawVertex(buffer, 0, y1, z1, -(float)x1, 0, alpha);
			drawVertex(buffer, 0, 0, z1, -(float)x1, -(float)y1, alpha);
			drawVertex(buffer, x1, 0, z1, 0, -(float)y1, alpha);

			drawVertex(buffer, x2, y1, z1, 2*r, 0, alpha);
			drawVertex(buffer, x2, 0, z1, 2*r, -(float)y1, alpha);
			drawVertex(buffer, 0, 0, z1, -(float)x1, -(float)y1, alpha);
			drawVertex(buffer, 0, y1, z1, -(float)x1, 0, alpha);

			drawVertex(buffer, 0, 0, z1, -(float)x1, -(float)y1, alpha);
			drawVertex(buffer, 0, y2, z1, -(float)x1, 2*r, alpha);
			drawVertex(buffer, x1, y2, z1, 0, 2*r, alpha);
			drawVertex(buffer, x1, 0, z1, 0, -(float)y1, alpha);

			drawVertex(buffer, 0, 0, z1, -(float)x1, -(float)y1, alpha);
			drawVertex(buffer, x2, 0, z1, 2*r, -(float)y1, alpha);
			drawVertex(buffer, x2, y2, z1, 2*r, 2*r, alpha);
			drawVertex(buffer, 0, y2, z1, -(float)x1, 2*r, alpha);

			// South
			drawVertex(buffer, x1, y1, z2, 0, 0, alpha);
			drawVertex(buffer, x1, 0, z2, 0, -(float)y1, alpha);
			drawVertex(buffer, 0, 0, z2, -(float)x1, -(float)y1, alpha);
			drawVertex(buffer, 0, y1, z2, -(float)x1, 0, alpha);

			drawVertex(buffer, 0, 0, z2, -(float)x1, -(float)y1, alpha);
			drawVertex(buffer, x2, 0, z2, 2*r, -(float)y1, alpha);
			drawVertex(buffer, x2, y1, z2, 2*r, 0, alpha);
			drawVertex(buffer, 0, y1, z2, -(float)x1, 0, alpha);

			drawVertex(buffer, x1, y2, z2, 0, 2*r, alpha);
			drawVertex(buffer, 0, y2, z2, -(float)x1, 2*r, alpha);
			drawVertex(buffer, 0, 0, z2, -(float)x1, -(float)y1, alpha);
			drawVertex(buffer, x1, 0, z2, 0, -(float)y1, alpha);

			drawVertex(buffer, 0, 0, z2, -(float)x1, -(float)y1, alpha);
			drawVertex(buffer, 0, y2, z2, -(float)x1, 2*r, alpha);
			drawVertex(buffer, x2, y2, z2, 2*r, 2*r, alpha);
			drawVertex(buffer, x2, 0, z2, 2*r, -(float)y1, alpha);

			// West
			drawVertex(buffer, x1, y1, z1, 0, 0, alpha);
			drawVertex(buffer, x1, 0, z1, 0, -(float)y1, alpha);
			drawVertex(buffer, x1, 0, 0, -(float)z1, -(float)y1, alpha);
			drawVertex(buffer, x1, y1, 0, -(float)z1, 0, alpha);

			drawVertex(buffer, x1, 0, 0, -(float)z1, -(float)y1, alpha);
			drawVertex(buffer, x1, 0, z2, 2*r, -(float)y1, alpha);
			drawVertex(buffer, x1, y1, z2, 2*r, 0, alpha);
			drawVertex(buffer, x1, y1, 0, -(float)z1, 0, alpha);

			drawVertex(buffer, x1, y2, z1, 0, 2*r, alpha);
			drawVertex(buffer, x1, y2, 0, -(float)z1, 2*r, alpha);
			drawVertex(buffer, x1, 0, 0, -(float)z1, -(float)y1, alpha);
			drawVertex(buffer, x1, 0, z1, 0, -(float)y1, alpha);

			drawVertex(buffer, x1, 0, 0, -(float)z1, -(float)y1, alpha);
			drawVertex(buffer, x1, y2, 0, -(float)z1, 2*r, alpha);
			drawVertex(buffer, x1, y2, z2, 2*r, 2*r, alpha);
			drawVertex(buffer, x1, 0, z2, 2*r, -(float)y1, alpha);

			// East
			drawVertex(buffer, x2, y1, z1, 0, 0, alpha);
			drawVertex(buffer, x2, y1, 0, -(float)z1, 0, alpha);
			drawVertex(buffer, x2, 0, 0, -(float)z1, -(float)y1, alpha);
			drawVertex(buffer, x2, 0, z1, 0, -(float)y1, alpha);

			drawVertex(buffer, x2, y1, z2, 2*r, 0, alpha);
			drawVertex(buffer, x2, 0, z2, 2*r, -(float)y1, alpha);
			drawVertex(buffer, x2, 0, 0, -(float)z1, -(float)y1, alpha);
			drawVertex(buffer, x2, y1, 0, -(float)z1, 0, alpha);

			drawVertex(buffer, x2, 0, 0, -(float)z1, -(float)y1, alpha);
			drawVertex(buffer, x2, y2, 0, -(float)z1, 2*r, alpha);
			drawVertex(buffer, x2, y2, z1, 0, 2*r, alpha);
			drawVertex(buffer, x2, 0, z1, 0, -(float)y1, alpha);

			drawVertex(buffer, x2, 0, 0, -(float)z1, -(float)y1, alpha);
			drawVertex(buffer, x2, 0, z2, 2*r, -(float)y1, alpha);
			drawVertex(buffer, x2, y2, z2, 2*r, 2*r, alpha);
			drawVertex(buffer, x2, y2, 0, -(float)z1, 2*r, alpha);

			tessellator.draw();

			// Undoes the texture translation
			GlStateManager.matrixMode(GL11.GL_TEXTURE);
			GlStateManager.loadIdentity();
			GlStateManager.matrixMode(GL11.GL_MODELVIEW);

			GlStateManager.disableBlend();
			GlStateManager.enableTexture2D();
			GlStateManager.enableLighting();
			GlStateManager.disableRescaleNormal();
			GlStateManager.popMatrix();
		}
	}

	private static void drawVertex(BufferBuilder buffer, double x, double y, double z, float u, float v, float alpha){
		float f = MathHelper.clamp(1 - (float)(x*x + y*y + z*z) / FADE_DISTANCE_SQUARED, 0, 1);
		buffer.pos(x, y, z).tex(u, v).color(1, 1, 1, f * alpha).endVertex();
	}

	// Useful to keep this mapping in case I ever want to draw cubes again

//			Vec3d[] vertices = WizardryUtilities.getVertices(entity.getEntityBoundingBox().offset(entity.getPositionVector().scale(-1)));
//
//			// Outside
//			drawFace(tessellator, vertices[0], vertices[1], vertices[3], vertices[2], u1, v1, u2, v2); // Bottom
//			drawFace(tessellator, vertices[6], vertices[7], vertices[2], vertices[3], u1, v1, u2, v2); // South
//			drawFace(tessellator, vertices[5], vertices[6], vertices[1], vertices[2], u1, v1, u2, v2); // East
//			drawFace(tessellator, vertices[4], vertices[5], vertices[0], vertices[1], u1, v1, u2, v2); // North
//			drawFace(tessellator, vertices[7], vertices[4], vertices[3], vertices[0], u1, v1, u2, v2); // West
//			drawFace(tessellator, vertices[5], vertices[4], vertices[6], vertices[7], u1, v1, u2, v2); // Top
//
//			// Inside
//			drawFace(tessellator, vertices[1], vertices[0], vertices[2], vertices[3], u1, v1, u2, v2); // Bottom
//			drawFace(tessellator, vertices[7], vertices[6], vertices[3], vertices[2], u1, v1, u2, v2); // South
//			drawFace(tessellator, vertices[6], vertices[5], vertices[2], vertices[1], u1, v1, u2, v2); // East
//			drawFace(tessellator, vertices[5], vertices[4], vertices[1], vertices[0], u1, v1, u2, v2); // North
//			drawFace(tessellator, vertices[4], vertices[7], vertices[0], vertices[3], u1, v1, u2, v2); // West
//			drawFace(tessellator, vertices[4], vertices[5], vertices[7], vertices[6], u1, v1, u2, v2); // Top

}
