package com.hbm.render.entity.effect;

import java.util.Random;

import org.lwjgl.opengl.GL11;

import com.hbm.entity.effect.EntityNukeCloudSmall;
import com.hbm.entity.effect.EntityNukeCloudSmall.Cloudlet;
import com.hbm.lib.RefStrings;
import com.hbm.main.ResourceManager;
import com.hbm.render.loader.HFRWavefrontObject;

import net.minecraft.client.renderer.ActiveRenderInfo;
import net.minecraft.client.renderer.OpenGlHelper;
import net.minecraft.client.renderer.RenderHelper;
import net.minecraft.client.renderer.Tessellator;
import net.minecraft.client.renderer.entity.Render;
import net.minecraft.entity.Entity;
import net.minecraft.util.MathHelper;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.Vec3;
import net.minecraftforge.client.model.IModelCustom;

public class RenderSmallNukeMK4 extends Render {

	public static final IModelCustom mush = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/effect/mush.obj"));
	public static final IModelCustom shockwave = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/effect/ring_roller.obj"));
	public static final IModelCustom thinring = new HFRWavefrontObject(new ResourceLocation(RefStrings.MODID, "models/effect/ring_thin.obj"));
	private static final ResourceLocation cloudlet = new ResourceLocation(RefStrings.MODID + ":textures/particle/particle_base.png");

	/*
	 *     //      //  //////  //////  //    //
	 *    ////  ////  //  //    //    ////  //
	 *   //  //  //  //////    //    //  ////
	 *  //      //  //  //    //    //    //
	 * //      //  //  //  //////  //    //
	 */
	
	/**
	 * Look how nice and clean this is!
	 */
	@Override
	public void doRender(Entity entity, double x, double y, double z, float f0, float interp) {
		
		GL11.glPushMatrix();
        GL11.glTranslated(x, y, z);
        
        EntityNukeCloudSmall cloud = (EntityNukeCloudSmall)entity;
        
        mushWrapper(cloud, interp);
        cloudletWrapper(cloud, interp);
        flashWrapper(cloud, interp);
        
		GL11.glPopMatrix();
	}

	@Override
	protected ResourceLocation getEntityTexture(Entity entity) {
		return null;
	}
	
	/*
	 *     //      //  //////  //////  //////  //////  //////  //////  //////
	 *    //      //  //  //  //  //  //  //  //  //  //      //  //  //
	 *   //  //  //  ////    //////  //////  //////  ////    ////    //////
	 *  ////  ////  //  //  //  //  //      //      //      //  //      //
	 * //      //  //  //  //  //  //      //      //////  //  //  //////
	 */
	
	/**
	 * Wrapper for the initial flash
	 * Caps the rendering at 60 ticks and sets the alpha function
	 * @param cloud
	 * @param interp
	 */
	private void flashWrapper(EntityNukeCloudSmall cloud, float interp) {
        
        if(cloud.age < 60) {

    		GL11.glPushMatrix();
    		//Function [0, 1] that determines the scale and intensity (inverse!) of the flash
        	double scale = (cloud.ticksExisted + interp) / 60D;
        	GL11.glAlphaFunc(GL11.GL_GEQUAL, 0.0F);
        	
        	//Euler function to slow down the scale as it progresses
        	//Makes it start fast and the fade-out is nice and smooth
        	scale = scale * Math.pow(Math.E, -scale) * 2.717391304D;
        	
        	renderFlash(scale);
            GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F);
    		GL11.glPopMatrix();
        }
	}
	
	@Deprecated
	private void shockwaveWrapper(EntityNukeCloudSmall cloud, float interp) {
        
        if(cloud.age < 300) {

            GL11.glEnable(GL11.GL_LIGHTING);
            GL11.glDisable(GL11.GL_CULL_FACE);
    		
    		GL11.glShadeModel(GL11.GL_SMOOTH);
            
        	renderShockwave((cloud.age + interp) * 0.5D);
            
    		GL11.glShadeModel(GL11.GL_FLAT);
        }
	}
	
	/**
	 * Wrapper for the entire mush (head + stem)
	 * Renders the entire thing twice to allow for smooth color gradients
	 * @param cloud
	 * @param interp
	 */
	private void mushWrapper(EntityNukeCloudSmall cloud, float interp) {
		
    	float size = cloud.getDataWatcher().getWatchableObjectFloat(18) * 5;
		
		GL11.glPushMatrix();
		
		GL11.glScalef(size, size, size);
		
		boolean balefire = cloud.getDataWatcher().getWatchableObjectByte(19) == 1;
		
		if(balefire)
			bindTexture(ResourceManager.balefire);
		else
			bindTexture(ResourceManager.fireball);

		GL11.glDisable(GL11.GL_CULL_FACE);
		GL11.glDisable(GL11.GL_TEXTURE_2D);
		GL11.glDisable(GL11.GL_LIGHTING);
		
		//Float [0, 1] for the initial solid-colored layer fade-in
		float func = MathHelper.clamp_float((cloud.ticksExisted + interp) * 0.0075F, 0, 1);
		//Function that determines how high the cloud has risen. The values are the results of trial and error and i forgot what they mean
		double height = Math.max(20 - 30 * 20 / ((((cloud.ticksExisted + interp) * 0.5) - 60 * 0.1) + 1), 0);
		
		if(balefire)
			GL11.glColor4f(1.0F - (1.0F - 0.64F) * func, 1.0F, 1.0F - (1.0F - 0.5F) * func, 1F);
		else
			GL11.glColor4f(1.0F, 1.0F - (1.0F - 0.7F) * func, 1.0F - (1.0F - 0.48F) * func, 1F);
		
        renderMushHead(cloud.ticksExisted + interp, height);
        renderMushStem(cloud.ticksExisted + interp, height);
		GL11.glEnable(GL11.GL_LIGHTING);
		GL11.glEnable(GL11.GL_TEXTURE_2D);

		//Float [0.75, 0] That determines the occupancy of the texture layer
		float texAlpha = func * 0.875F;
		
		GL11.glColor4f(1F, 1F, 1F, texAlpha);
		//Sets blend to "how you'd expect it" mode
        OpenGlHelper.glBlendFunc(770, 771, 1, 0);
		GL11.glEnable(GL11.GL_BLEND);
		
		//And now we fuck with texture transformations
        GL11.glMatrixMode(GL11.GL_TEXTURE);
        GL11.glLoadIdentity();
        
        GL11.glTranslated(0, -(cloud.ticksExisted + interp) * 0.035, 0);
        
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        
        GL11.glPushMatrix();
        	//It's the thing that makes glow-in-the-dark work
	        GL11.glPushAttrib(GL11.GL_LIGHTING_BIT);
	        OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240F, 240F);
	        renderMushHead(cloud.ticksExisted + interp, height);
	        renderMushStem(cloud.ticksExisted + interp, height);
	        GL11.glEnable(GL11.GL_LIGHTING);
	        GL11.glPopAttrib();
        GL11.glPopMatrix();

        //Clean this up otherwise the game becomes one-dimensional
        GL11.glMatrixMode(GL11.GL_TEXTURE);
        GL11.glLoadIdentity();
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        
		GL11.glDisable(GL11.GL_BLEND);
		GL11.glEnable(GL11.GL_CULL_FACE);
        
		GL11.glPopMatrix();
	}
	
	/**
	 * Adds all cloudlets to the tessellator and then draws them
	 * @param cloud
	 * @param interp
	 */
	private void cloudletWrapper(EntityNukeCloudSmall cloud, float interp) {

		GL11.glPushMatrix();
		GL11.glEnable(GL11.GL_BLEND);
		//To prevent particles cutting off before fully fading out
    	GL11.glAlphaFunc(GL11.GL_GEQUAL, 0.01F);
        OpenGlHelper.glBlendFunc(770, 771, 1, 0);
        RenderHelper.disableStandardItemLighting();
        GL11.glDisable(GL11.GL_ALPHA_TEST);
		GL11.glDepthMask(false);
    	
    	bindTexture(cloudlet);

		Tessellator tess = Tessellator.instance;
		tess.startDrawingQuads();
		
    	for(Cloudlet cloudlet : cloud.cloudlets) {
    		float scale = cloud.age + interp - cloudlet.age;
    		tessellateCloudlet(tess, cloudlet.posX, cloudlet.posY - cloud.posY + 2, cloudlet.posZ, scale, cloud.getDataWatcher().getWatchableObjectByte(19));
    	}
    	
    	/*Random rand = new Random(cloud.getEntityId());
    	float size = cloud.getDataWatcher().getWatchableObjectFloat(18);
    	
    	for(int i = 0; i < 300 * size; i++) {
    		
    		float scale = size * 10;
    		Vec3 vec = Vec3.createVectorHelper(rand.nextGaussian() * scale, 0, rand.nextGaussian() * scale);
    		
    		tessellateCloudlet(tess, vec.xCoord, (scale - vec.lengthVector()) * rand.nextDouble() * 0.5, vec.zCoord - 10, (float)(cloud.age * cloud.cloudletLife) / cloud.maxAge, cloud.getDataWatcher().getWatchableObjectByte(19));
    	}*/
    	
		tess.draw();

		GL11.glDepthMask(true);
        GL11.glEnable(GL11.GL_ALPHA_TEST);
        RenderHelper.enableStandardItemLighting();
        GL11.glAlphaFunc(GL11.GL_GREATER, 0.1F);
		GL11.glDisable(GL11.GL_BLEND);
		GL11.glPopMatrix();
	}
	
	/*
	 *     //////  //////  //    //  ////    //////  //////  //////  //////  //////
	 *    //  //  //      ////  //  //  //  //      //  //  //      //  //  //
	 *   ////    ////    //  ////  //  //  ////    ////    ////    ////    //////
	 *  //  //  //      //    //  //  //  //      //  //  //      //  //      //
	 * //  //  //////  //    //  ////    //////  //  //  //////  //  //  //////
	 */

	/**
	 * Once again the recycled ender dragon death animation
	 * It worked so well the last 14 times, let's go for 15
	 * @param intensity Double [0, 1] that determines scale and alpha
	 */
	private void renderFlash(double intensity) {
    	
    	GL11.glScalef(0.2F, 0.2F, 0.2F);
    	
    	double inverse = 1.0D - intensity;
    	
        Tessellator tessellator = Tessellator.instance;
		RenderHelper.disableStandardItemLighting();

        Random random = new Random(432L);
        GL11.glDisable(GL11.GL_TEXTURE_2D);
        GL11.glShadeModel(GL11.GL_SMOOTH);
        GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
        GL11.glDisable(GL11.GL_ALPHA_TEST);
        GL11.glEnable(GL11.GL_CULL_FACE);
        GL11.glDepthMask(false);
        
        GL11.glPushMatrix();
        
        float scale = 100;
        
        for(int i = 0; i < 300; i++) {
        	
            GL11.glRotatef(random.nextFloat() * 360.0F, 1.0F, 0.0F, 0.0F);
            GL11.glRotatef(random.nextFloat() * 360.0F, 0.0F, 1.0F, 0.0F);
            GL11.glRotatef(random.nextFloat() * 360.0F, 0.0F, 0.0F, 1.0F);
            GL11.glRotatef(random.nextFloat() * 360.0F, 1.0F, 0.0F, 0.0F);
            GL11.glRotatef(random.nextFloat() * 360.0F, 0.0F, 1.0F, 0.0F);
            
            float vert1 = (random.nextFloat() * 20.0F + 5.0F + 1 * 10.0F) * (float)(intensity * scale);
            float vert2 = (random.nextFloat() * 2.0F + 1.0F + 1 * 2.0F) * (float)(intensity * scale);
            
            tessellator.startDrawing(6);
            
            tessellator.setColorRGBA_F(1.0F, 1.0F, 1.0F, (float) inverse);
            tessellator.addVertex(0.0D, 0.0D, 0.0D);
            tessellator.setColorRGBA_F(1.0F, 1.0F, 1.0F, 0.0F);
            tessellator.addVertex(-0.866D * vert2, vert1, -0.5F * vert2);
            tessellator.addVertex(0.866D * vert2, vert1, -0.5F * vert2);
            tessellator.addVertex(0.0D, vert1, 1.0F * vert2);
            tessellator.addVertex(-0.866D * vert2, vert1, -0.5F * vert2);
            tessellator.draw();
        }

        GL11.glPopMatrix();
        
        GL11.glDepthMask(true);
        GL11.glDisable(GL11.GL_CULL_FACE);
        GL11.glDisable(GL11.GL_BLEND);
        GL11.glShadeModel(GL11.GL_FLAT);
        GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
        GL11.glEnable(GL11.GL_TEXTURE_2D);
        GL11.glEnable(GL11.GL_ALPHA_TEST);
        RenderHelper.enableStandardItemLighting();
	}
	
	@Deprecated
	private void renderShockwave(double scale) {
		
		GL11.glPushMatrix();
		
		double s = 3;
		double timescale = 250;
		
        bindTexture(ResourceManager.dust);
        printShockwave(scale * s, 5, 32, -(System.currentTimeMillis() % timescale * 8) / (timescale));
		
		GL11.glPopMatrix();
	}
	
	/**
	 * Render call for the mush head model
	 * Includes offset and smoothing
	 * Also scales the fireball along XZ
	 * @param progress Lifetime + interpolation number
	 * @param height The current animation offset
	 */
	private void renderMushHead(float progress, double height) {
		
		GL11.glPushMatrix();
		
		double expansion = 100;
		double width = Math.min(progress, expansion) / expansion * 0.3 + 0.7;
		
		GL11.glTranslated(0, -26 + height, 0);
		GL11.glScaled(width, 1, width);
		
		GL11.glShadeModel(GL11.GL_SMOOTH);
        GL11.glDisable(GL11.GL_ALPHA_TEST);
		mush.renderPart("Ball");
        GL11.glEnable(GL11.GL_ALPHA_TEST);
		GL11.glShadeModel(GL11.GL_FLAT);
		
		GL11.glPopMatrix();
	}
	
	/**
	 * Render call for the mush stem model
	 * Includes offset and smoothing
	 * @param progress Lifetime + interpolation number
	 * @param height The current animation offset
	 */
	private void renderMushStem(float progress, double height) {
		
		GL11.glPushMatrix();

		GL11.glTranslated(0, -26 + height, 0);
		
		GL11.glShadeModel(GL11.GL_SMOOTH);
        GL11.glDisable(GL11.GL_ALPHA_TEST);
		mush.renderPart("Stem");
        GL11.glEnable(GL11.GL_ALPHA_TEST);
		GL11.glShadeModel(GL11.GL_FLAT);
		
		GL11.glPopMatrix();
	}
	
	/**
	 * Adds one cloudlet (one face) to the tessellator.
	 * Rotation is done using ActiveRenderInfo, which I'd assume runs on magic
	 * But hey, if it works for particles, why not here too?
	 * @param tess
	 * @param posX
	 * @param posY
	 * @param posZ
	 * @param age The mush' age when the cloudlet was created
	 * @param type DataWatcher byte #19 which differentiates between different mush types
	 */
	private void tessellateCloudlet(Tessellator tess, double posX, double posY, double posZ, float age, int type) {
		
		float alpha = 1F - Math.max(age / (float)(EntityNukeCloudSmall.cloudletLife), 0F);
		float alphaorig = alpha;
		
		float scale = 5F * (alpha * 0.5F + 0.5F);
		
		if(age < 3)
			alpha = age * 0.333F;
		
        float f1 = ActiveRenderInfo.rotationX;
        float f2 = ActiveRenderInfo.rotationZ;
        float f3 = ActiveRenderInfo.rotationYZ;
        float f4 = ActiveRenderInfo.rotationXY;
        float f5 = ActiveRenderInfo.rotationXZ;
        
        Random rand = new Random((long) ((posX * 5 + posY * 25 + posZ * 125) * 1000D));
        
        float brightness = rand.nextFloat() * 0.25F + 0.25F;

        if(type == 1) {
        	tess.setColorRGBA_F(0.25F * alphaorig, alphaorig - brightness * 0.5F, 0.25F * alphaorig, alpha);
        } else {
    		
        	tess.setColorRGBA_F(brightness, brightness, brightness, alpha);
        }
        
		tess.addVertexWithUV((double)(posX - f1 * scale - f3 * scale), (double)(posY - f5 * scale), (double)(posZ - f2 * scale - f4 * scale), 1, 1);
		tess.addVertexWithUV((double)(posX - f1 * scale + f3 * scale), (double)(posY + f5 * scale), (double)(posZ - f2 * scale + f4 * scale), 1, 0);
		tess.addVertexWithUV((double)(posX + f1 * scale + f3 * scale), (double)(posY + f5 * scale), (double)(posZ + f2 * scale + f4 * scale), 0, 0);
		tess.addVertexWithUV((double)(posX + f1 * scale - f3 * scale), (double)(posY - f5 * scale), (double)(posZ + f2 * scale - f4 * scale), 0, 1);
		
	}
	
	/*
	 *     //////  //////  //////  //    //  //////  //////  //////  //////
	 *    //  //  //  //    //    ////  //    //    //      //  //  //
	 *   //////  ////      //    //  ////    //    ////    ////    //////
	 *  //      //  //    //    //    //    //    //      //  //      //
	 * //      //  //  //////  //    //    //    //////  //  //  //////
	 */
	
	@Deprecated
	private void printShockwave(double scale, double radius, int segments, double offset) {
		
		double angle = 360D / segments;

		double[][] verts = new double[12][3];
		double[][] lastverts = new double[12][3];
		
		Tessellator tess = Tessellator.instance;
		tess.startDrawingQuads();
		
		for(int i = -1; i < segments; i++) {
			
			double rot = i * angle;
			
			for(int j = 0; j < 12; j++) {
				
				Vec3 vec = Vec3.createVectorHelper(radius, 0, 0);
				vec.rotateAroundZ((float) Math.toRadians(360D /12D * j));
				vec.rotateAroundY((float) Math.toRadians(rot));
				
				lastverts[j] = verts[j];
				verts[j] = new double[] {vec.xCoord, vec.yCoord, vec.zCoord};
			}
			
			if(i == -1)
				continue;

			Vec3 rotor = Vec3.createVectorHelper(scale, 0, 0);
			rotor.rotateAroundY((float) Math.toRadians(rot));
			Vec3 last = Vec3.createVectorHelper(scale, 0, 0);
			last.rotateAroundY((float) Math.toRadians(rot - angle));
			
			for(int k = 0; k < 12; k++) {
				
				int n = (k + 1) % 12;

				double uvlower = offset * 0.125;
				double uvupper = 1 + offset * 0.125;

				tess.addVertexWithUV(lastverts[k][0] + last.xCoord, lastverts[k][1], lastverts[k][2] + last.zCoord, uvlower, 0);
				tess.addVertexWithUV(verts[k][0] + rotor.xCoord, verts[k][1], verts[k][2] + rotor.zCoord, uvlower, 1);
				tess.addVertexWithUV(verts[n][0] + rotor.xCoord, verts[n][1], verts[n][2] + rotor.zCoord, uvupper, 1);
				tess.addVertexWithUV(lastverts[n][0] + last.xCoord, lastverts[n][1], lastverts[n][2] + last.zCoord, uvupper, 0);
			}
		}
		
		tess.draw();
	}
}
