package client.renderer.entity;

import java.util.Map;

import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;

import client.Client;
import client.init.RenderRegistry;
import client.renderer.DefaultVertexFormats;
import client.renderer.Drawing;
import client.renderer.Renderer;
import client.renderer.GlState;
import client.renderer.RenderBuffer;
import client.renderer.Tessellator;
import client.renderer.texture.Sprite;
import client.renderer.texture.TextureManager;
import client.renderer.texture.TextureMap;
import common.collect.Maps;
import common.entity.Entity;
import common.entity.types.EntityLiving;
import common.init.SpeciesRegistry.ModelType;
import common.util.BlockPos;
import common.util.BoundingBox;
import common.util.Vec3;
import common.world.World;

public class RenderManager {
	private Map<Class<? extends Entity>, Render<? extends Entity>> entityRenderMap = Maps.<Class<? extends Entity>, Render<? extends Entity>>newHashMap();
	private Map<ModelType, RenderNpc> models = Maps.newEnumMap(ModelType.class);

	private double renderPosX;
	private double renderPosY;
	private double renderPosZ;
	public TextureManager renderEngine;
	public World worldObj;
	public Entity livingPlayer;
	public Entity pointedEntity;
	public float playerViewY;
	public float playerViewX;
	public Client gm;
	public double viewerPosX;
	public double viewerPosY;
	public double viewerPosZ;
	private boolean renderOutlines = false;
	private boolean debugBoundingBox = false;

	public RenderManager(TextureManager renderEngineIn, RenderItem itemRendererIn) {
		this.renderEngine = renderEngineIn;
		RenderRegistry.registerRenderers(this.entityRenderMap, this.models, this, itemRendererIn);
		for(RenderNpc render : this.models.values()) {
			render.initSegments();
		}
	}

	public void setRenderPosition(double renderPosXIn, double renderPosYIn, double renderPosZIn) {
		this.renderPosX = renderPosXIn;
		this.renderPosY = renderPosYIn;
		this.renderPosZ = renderPosZIn;
	}

	private <T extends Entity> Render<T> getEntityClassRenderObject(Class<? extends Entity> entityClass) {
		Render<? extends Entity> render = (Render)this.entityRenderMap.get(entityClass);

		if(render == null && entityClass != Entity.class) {
			render = this.<Entity>getEntityClassRenderObject((Class<? extends Entity>)entityClass.getSuperclass());
			this.entityRenderMap.put(entityClass, render);
		}

		return (Render<T>)render;
	}

	private <T extends Entity> Render<T> getEntityRenderObject(Entity entityIn) {
		return this.<T>getEntityClassRenderObject(entityIn.getClass());
	}

	public RenderNpc getRenderObject(ModelType model) {
		return this.models.get(model);
	}

	public void cacheActiveRenderInfo(World worldIn, Entity livingPlayerIn, Entity pointedEntityIn, Client optionsIn, float partialTicks) {
		this.worldObj = worldIn;
		this.gm = optionsIn;
		this.livingPlayer = livingPlayerIn;
		this.pointedEntity = pointedEntityIn;
		this.playerViewY = livingPlayerIn.prevYaw + (livingPlayerIn.rotYaw - livingPlayerIn.prevYaw) * partialTicks;
		this.playerViewX = livingPlayerIn.prevPitch + (livingPlayerIn.rotPitch - livingPlayerIn.prevPitch) * partialTicks;

		if(optionsIn.thirdPersonView == 2) {
			this.playerViewY += 180.0F;
		}

		this.viewerPosX = livingPlayerIn.lastTickPosX + (livingPlayerIn.posX - livingPlayerIn.lastTickPosX) * (double)partialTicks;
		this.viewerPosY = livingPlayerIn.lastTickPosY + (livingPlayerIn.posY - livingPlayerIn.lastTickPosY) * (double)partialTicks;
		this.viewerPosZ = livingPlayerIn.lastTickPosZ + (livingPlayerIn.posZ - livingPlayerIn.lastTickPosZ) * (double)partialTicks;
	}

	public void setPlayerViewY(float playerViewYIn) {
		this.playerViewY = playerViewYIn;
	}

	public void setDebugBoundingBox(boolean debugBoundingBoxIn) {
		this.debugBoundingBox = debugBoundingBoxIn;
	}

	public boolean isDebugBoundingBox() {
		return this.debugBoundingBox;
	}

	public void set(World worldIn) {
		this.worldObj = worldIn;
	}

	public void setRenderOutlines(boolean renderOutlinesIn) {
		this.renderOutlines = renderOutlinesIn;
	}

	public boolean shouldRender(Entity entityIn, double camX, double camY, double camZ) {
		Render<Entity> render = this.<Entity>getEntityRenderObject(entityIn);
		return render != null && render.shouldRender(entityIn, camX, camY, camZ);
	}

	public static int getBrightnessForRender(Entity entity) {
		BlockPos pos = new BlockPos(entity.posX, entity.posY + (double)entity.getEyeHeight(), entity.posZ);
		return entity.worldObj.isBlockLoaded(pos) ? entity.worldObj.getCombinedLight(pos, 0) : 0;
	}

	public boolean renderEntity(Entity entity, float partialTicks) {
		if(entity.ticksExisted == 0) {
			entity.lastTickPosX = entity.posX;
			entity.lastTickPosY = entity.posY;
			entity.lastTickPosZ = entity.posZ;
		}

		double d0 = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * (double)partialTicks;
		double d1 = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * (double)partialTicks;
		double d2 = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * (double)partialTicks;
		int i = getBrightnessForRender(entity);

		if(entity.isBurning()) {
			i = 15728880;
		}

		int j = i % 65536;
		int k = i / 65536;
		GL13.glMultiTexCoord2f(GL13.GL_TEXTURE1, (float)j / 1.0F, (float)k / 1.0F);
		GlState.color(1.0F, 1.0F, 1.0F, 1.0F);
		return this.renderEntity(entity, d0 - this.renderPosX, d1 - this.renderPosY, d2 - this.renderPosZ, partialTicks);
	}

	public boolean renderEntity(Entity entity, double x, double y, double z, float partialTicks) {
		Render<Entity> render = this.<Entity>getEntityRenderObject(entity);

		if(render != null && this.renderEngine != null) {
			if(render instanceof RendererLivingEntity) {
				((RendererLivingEntity)render).setRenderOutlines(this.renderOutlines);
			}

			render.doRender(entity, x, y, z, partialTicks);

			if(!this.renderOutlines && this.gm != null && entity.canRenderOnFire()) {
				this.renderEntityFire(entity, x, y, z);
			}

			if(this.debugBoundingBox) // && !entity.isInvisible()) // && !hideDebugBox)
			{
				this.renderDebugBoundingBox(entity, x, y, z, partialTicks);
			}
		}
		else if(this.renderEngine != null) {
			return false;
		}

		return true;
	}

	public void renderLabel(Entity entity, String str, double x, double y, double z, int maxDistance) {
		if(this.gm == null)
			return;
		double dist = entity.getDistanceSqToEntity(this.livingPlayer);
		if(dist > (double)(maxDistance * maxDistance) || !this.gm.canRenderHud())
			return;
		float scale = 0.016666668F;
		GL11.glPushMatrix();
		GL11.glTranslatef((float)x + 0.0F, (float)y + entity.height + 0.5F, (float)z);
		GL11.glNormal3f(0.0F, 1.0F, 0.0F);
		GL11.glRotatef(-this.playerViewY, 0.0F, 1.0F, 0.0F);
		GL11.glRotatef(this.playerViewX, 1.0F, 0.0F, 0.0F);
		GL11.glScalef(-scale, -scale, scale);
		GlState.disableLighting();
		GlState.depthMask(false);
		GlState.enableBlend();
		GlState.tryBlendFuncSeparate(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA, GL11.GL_ONE, GL11.GL_ZERO);
		GlState.depthMask(true);
		Drawing.drawTextboxCentered(str, 0, 0, 0x3f000000);
		GlState.enableLighting();
		GlState.disableBlend();
		GlState.color(1.0F, 1.0F, 1.0F, 1.0F);
		GL11.glPopMatrix();
	}

	private void renderEntityFire(Entity entity, double x, double y, double z) {
		GlState.disableLighting();
		TextureMap map = this.gm.getTextureMapBlocks();
		Sprite layer0 = map.getAtlasSprite("blocks/fire_layer_0");
		Sprite layer1 = map.getAtlasSprite("blocks/fire_layer_1");
		GL11.glPushMatrix();
		GL11.glTranslatef((float)x, (float)y, (float)z);
		float scale = entity.width * 1.4F;
		GL11.glScalef(scale, scale, scale);
		RenderBuffer rb = Tessellator.getBuffer();
		float f1 = 0.5F;
		float f2 = 0.0F;
		float f3 = entity.height / scale;
		float f4 = (float)(entity.posY - entity.getEntityBoundingBox().minY);
		GL11.glRotatef(-this.playerViewY, 0.0F, 1.0F, 0.0F);
		GL11.glTranslatef(0.0F, 0.0F, -0.3F + (float)((int)f3) * 0.02F);
		GlState.color(1.0F, 1.0F, 1.0F, 1.0F);
		float f5 = 0.0F;
		int i = 0;
		rb.begin(GL11.GL_QUADS, DefaultVertexFormats.POSITION_TEX);

		while(f3 > 0.0F) {
			Sprite layer = i % 2 == 0 ? layer0 : layer1;
			this.renderEngine.bindTexture(TextureMap.BLOCKS);
			float f6 = layer.getMinU();
			float f7 = layer.getMinV();
			float f8 = layer.getMaxU();
			float f9 = layer.getMaxV();

			if(i / 2 % 2 == 0) {
				float f10 = f8;
				f8 = f6;
				f6 = f10;
			}

			rb.pos((double)(f1 - f2), (double)(0.0F - f4), (double)f5).tex((double)f8, (double)f9).endVertex();
			rb.pos((double)(-f1 - f2), (double)(0.0F - f4), (double)f5).tex((double)f6, (double)f9).endVertex();
			rb.pos((double)(-f1 - f2), (double)(1.4F - f4), (double)f5).tex((double)f6, (double)f7).endVertex();
			rb.pos((double)(f1 - f2), (double)(1.4F - f4), (double)f5).tex((double)f8, (double)f7).endVertex();
			f3 -= 0.45F;
			f4 -= 0.45F;
			f1 *= 0.9F;
			f5 += 0.03F;
			++i;
		}

		Tessellator.draw();
		GL11.glPopMatrix();
		GlState.enableLighting();
	}

	private void renderDebugBoundingBox(Entity entityIn, double x, double y, double z, float partialTicks) {
		GlState.depthMask(false);
		GlState.disableTexture2D();
		GlState.disableLighting();
		GlState.disableCull();
		GlState.disableBlend();
		float f = entityIn.width / 2.0F;
		BoundingBox axisalignedbb = entityIn.getEntityBoundingBox();
		BoundingBox axisalignedbb1 = new BoundingBox(axisalignedbb.minX - entityIn.posX + x, axisalignedbb.minY - entityIn.posY + y, axisalignedbb.minZ - entityIn.posZ + z,
			axisalignedbb.maxX - entityIn.posX + x, axisalignedbb.maxY - entityIn.posY + y, axisalignedbb.maxZ - entityIn.posZ + z);
		Renderer.drawOutlinedBoundingBox(axisalignedbb1, 255, 255, 255, 255);

		if(entityIn instanceof EntityLiving) {
			float f1 = 0.01F;
			Renderer.drawOutlinedBoundingBox(new BoundingBox(x - (double)f, y + (double)entityIn.getEyeHeight() - 0.009999999776482582D, z - (double)f, x + (double)f,
				y + (double)entityIn.getEyeHeight() + 0.009999999776482582D, z + (double)f), 255, 0, 0, 255);
		}

		RenderBuffer worldrenderer = Tessellator.getBuffer();
		Vec3 vec3 = entityIn.getLook(partialTicks);
		worldrenderer.begin(GL11.GL_LINE_STRIP, DefaultVertexFormats.POSITION_COLOR);
		worldrenderer.pos(x, y + (double)entityIn.getEyeHeight(), z).color(0, 0, 255, 255).endVertex();
		worldrenderer.pos(x + vec3.xCoord * 2.0D, y + (double)entityIn.getEyeHeight() + vec3.yCoord * 2.0D, z + vec3.zCoord * 2.0D).color(0, 0, 255, 255).endVertex();
		Tessellator.draw();
		GlState.enableTexture2D();
		GlState.enableLighting();
		GlState.enableCull();
		GlState.disableBlend();
		GlState.depthMask(true);
	}

	public boolean renderBox(Entity entity, float partialTicks) {
		if(this.debugBoundingBox) {
			if(entity.ticksExisted == 0) {
				entity.lastTickPosX = entity.posX;
				entity.lastTickPosY = entity.posY;
				entity.lastTickPosZ = entity.posZ;
			}
			double d0 = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * (double)partialTicks;
			double d1 = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * (double)partialTicks;
			double d2 = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * (double)partialTicks;
			this.renderDebugBoundingBox(entity, d0 - this.renderPosX, d1 - this.renderPosY, d2 - this.renderPosZ, partialTicks);
			return true;
		}
		return false;
	}
}
