package client.world;

import java.util.List;
import java.util.Set;

import client.Client;
import common.biome.Biome;
import common.collect.Lists;
import common.collect.Sets;
import common.dimension.Dimension;
import common.entity.Entity;
import common.entity.item.EntityCart;
import common.entity.npc.EntityNPC;
import common.init.BlockRegistry;
import common.init.Blocks;
import common.init.ItemRegistry;
import common.init.Items;
import common.init.SoundEvent;
import common.item.material.ItemDye;
import common.log.Log;
import common.rng.Random;
import common.sound.MovingSoundMinecart;
import common.sound.PositionedSound;
import common.tileentity.TileEntity;
import common.util.BlockPos;
import common.util.ChunkPos;
import common.util.ExtMath;
import common.util.LongHashMap;
import common.util.ParticleType;
import common.util.Vec3;
import common.util.BlockPos.MutableBlockPos;
import common.world.AWorldClient;
import common.world.State;
import common.world.World;

public class WorldClient extends AWorldClient
{
    private static final int DISPLAY_RANGE = 16;

    private final Client gm;
    private final ChunkClient emptyChunk;
    private final Set<Entity> entityList = Sets.<Entity>newHashSet();
    private final Set<Entity> spawnQueue = Sets.<Entity>newHashSet();
    private final Set<ChunkPos> previousActive = Sets.<ChunkPos>newHashSet();
    private final LongHashMap<ChunkClient> chunkMapping = new LongHashMap();
    private final List<ChunkClient> chunkListing = Lists.<ChunkClient>newArrayList();
    private final Set<Long> emptyChunkListing = Sets.<Long>newHashSet();
    private final Set<Long> nextEmptyChunkListing = Sets.<Long>newHashSet();
//	public final Profiler profiler;
	protected int lastLightning;
	protected Vec3 lightColor = new Vec3(0xffffff);

    public WorldClient(Client gm, Dimension dim)
    {
        super(dim);
        this.gm = gm;
        this.emptyChunk = new ChunkEmpty(this, this.gm.debugWorld);
        this.calculateInitialSkylight();
        this.calculateInitialWeather();
        this.setGravity(this.gm.gravity);
        this.setTimeFactor(this.gm.timeFactor);
//        this.setDifficulty(this.gm.difficulty);
    }
    
    private void markReload(int cx, int cz, int range) {
    	this.nextEmptyChunkListing.clear();
    	for(int x = cx - range; x <= cx + range; x++) {
        	for(int z = cz - range; z <= cz + range; z++) {
        		long id = LongHashMap.packInt(x, z);
	        	if(this.chunkMapping.getValueByKey(id) != null) {
	        		if(this.emptyChunkListing.contains(id)) {
	        			this.emptyChunkListing.remove(id);
	    				this.nextEmptyChunkListing.add(id);
	        		}
	        		continue;
	        	}
				this.chunkMapping.add(id, this.emptyChunk);
				this.emptyChunkListing.remove(id);
				this.nextEmptyChunkListing.add(id);
	        	this.markBlockRangeForRenderUpdate(x << 4, -World.MAX_SIZE_Y, z << 4, (x << 4) + 15, World.MAX_SIZE_Y, (z << 4) + 15);
        	}
    	}
    	for(Long id : this.emptyChunkListing) {
    		this.chunkMapping.remove(id);
    		int x = LongHashMap.getX(id);
    		int z = LongHashMap.getZ(id);
        	this.markBlockRangeForRenderUpdate(x << 4, -World.MAX_SIZE_Y, z << 4, (x << 4) + 15, World.MAX_SIZE_Y, (z << 4) + 15);
    	}
    	this.emptyChunkListing.clear();
    	this.emptyChunkListing.addAll(this.nextEmptyChunkListing);
    }
    
    public void markReload() {
    	if(this.gm.player != null && !this.gm.charEditor)
    		this.markReload((int)this.gm.player.posX >> 4, (int)this.gm.player.posZ >> 4, this.gm.renderDistance + 4);
    }
    
    public void tick()
    {
    	this.markReload();
//        this.info.tick();

        if (this.gm.dayCycle)
        {
            this.daytime += this.timeFactor;
        }

        for (int i = 0; i < 10 && !this.spawnQueue.isEmpty(); ++i)
        {
            Entity entity = (Entity)this.spawnQueue.iterator().next();
            this.spawnQueue.remove(entity);

            if (!this.entities.contains(entity))
            {
                this.spawnEntityInWorld(entity);
            }
        }
        long time = System.currentTimeMillis();
		for (ChunkClient chunk : this.chunkListing)
		{
		    chunk.update(System.currentTimeMillis() - time > 5L);
		}
		if (System.currentTimeMillis() - time > 100L)
		{
			Log.TICK.warn("Render-Chunk-Tick dauerte " + (System.currentTimeMillis() - time) + " ms");
		}
        this.updateBlocks();
    }

    protected void updateBlocks()
    {
    	this.setActivePlayerChunksAndCheckLight(this.gm.renderDistance);
        this.previousActive.retainAll(this.active);

        if (this.previousActive.size() == this.active.size())
        {
            this.previousActive.clear();
        }

        int i = 0;

        for (ChunkPos chunkcoordintpair : this.active)
        {
            if (!this.previousActive.contains(chunkcoordintpair))
            {
                int j = chunkcoordintpair.x * 16;
                int k = chunkcoordintpair.z * 16;
                ChunkClient chunk = this.getChunk(chunkcoordintpair.x, chunkcoordintpair.z);
        		chunk.enqueueRelight();
                this.previousActive.add(chunkcoordintpair);
                ++i;

                if (i >= 10)
                {
                    return;
                }
            }
        }
    }

    public void doPreChunk(int x, int z, boolean load)
    {
    	long id = LongHashMap.packInt(x, z);
        if (load)
        {
        	if(this.chunkMapping.getValueByKey(id) != null)
        		this.doPreChunk(x, z, false);
        	ChunkClient chunk = new ChunkClient(this, x, z);
			this.chunkMapping.add(id, chunk);
			this.chunkListing.add(chunk);
			chunk.setLoaded();
        }
        else
        {
        	ChunkClient chunk = this.getChunk(x, z);
			chunk.onChunkUnload();
			this.chunkMapping.remove(id);
			this.chunkListing.remove(chunk);
			this.emptyChunkListing.remove(id);
        }

        if (!load)
        {
            this.markBlockRangeForRenderUpdate(x * 16, -World.MAX_SIZE_Y, z * 16, x * 16 + 15, World.MAX_SIZE_Y, z * 16 + 15);
        }
    }
    
    public boolean spawnEntityInWorld(Entity entityIn)
    {
        boolean flag = super.spawnEntityInWorld(entityIn);
        this.entityList.add(entityIn);

        if (!flag)
        {
            this.spawnQueue.add(entityIn);
        }
        else if (entityIn instanceof EntityCart)
        {
            this.gm.getSoundManager().playSound(new MovingSoundMinecart((EntityCart)entityIn));
        }

        return flag;
    }
    
    public void removeEntity(Entity entityIn)
    {
        super.removeEntity(entityIn);
        this.entityList.remove(entityIn);
    }

    protected void onEntityAdded(Entity entityIn)
    {
        if (this.spawnQueue.contains(entityIn))
        {
            this.spawnQueue.remove(entityIn);
        }
    }

    protected void onEntityRemoved(Entity entityIn)
    {
        boolean flag = false;

        if (this.entityList.contains(entityIn))
        {
            if (entityIn.isEntityAlive())
            {
                this.spawnQueue.add(entityIn);
                flag = true;
            }
            else
            {
                this.entityList.remove(entityIn);
            }
        }
    }
    
    public void addEntityToWorld(int entityID, Entity entityToSpawn)
    {
        Entity entity = this.getEntityByID(entityID);

        if (entity != null)
        {
            this.removeEntity(entity);
        }

        this.entityList.add(entityToSpawn);
        entityToSpawn.setId(entityID);

        if (!this.spawnEntityInWorld(entityToSpawn))
        {
            this.spawnQueue.add(entityToSpawn);
        }

        this.entityIds.addKey(entityID, entityToSpawn);
    }
    
    public Entity getEntityByID(int id)
    {
        return (Entity)(id == this.gm.player.getId() ? this.gm.player : super.getEntityByID(id));
    }

    public Entity removeEntityFromWorld(int entityID)
    {
        Entity entity = (Entity)this.entityIds.removeObject(entityID);

        if (entity != null)
        {
            this.entityList.remove(entity);
            this.removeEntity(entity);
        }

        return entity;
    }

    public boolean invalidateRegionAndSetBlock(BlockPos pos, State state)
    {
        return super.setState(pos, state, 3);
    }
    
//    public boolean canShowVoidParticles() {
//    	return this.gm.voidParticles; // && this.dimension.getType().voidPortal;
//    }

    public void displayTick(int posX, int posY, int posZ) {
        Random rand = new Random();
        BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
        for(int n = 0; n < 1000; n++) {
            int x = posX + rand.zrange(DISPLAY_RANGE) - rand.zrange(DISPLAY_RANGE);
            int y = posY + rand.zrange(DISPLAY_RANGE) - rand.zrange(DISPLAY_RANGE);
            int z = posZ + rand.zrange(DISPLAY_RANGE) - rand.zrange(DISPLAY_RANGE);
            pos.set(x, y, z);
            State state = this.getState(pos);
            state.getBlock().randomDisplayTick(this, pos, state, rand);
        }
//        if(this.canShowVoidParticles()) {
    	for(int n = 0; n < 1000; n++) {
    		float x = ((float)posX) + (rand.floatv() - rand.floatv() - 0.5f) * 32.0f;
    		float y = -64.0f + rand.floatv() * 65.0f; // * 68.0f;
    		float z = ((float)posZ) + (rand.floatv() - rand.floatv() - 0.5f) * 32.0f;
        	this.spawnParticle(ParticleType.DEPTH, (double)x, (double)y, (double)z);
        }
//        }
    }
    
    public void removeAllEntities()
    {
        this.entities.removeAll(this.unloaded);

        for (int i = 0; i < this.unloaded.size(); ++i)
        {
            Entity entity = (Entity)this.unloaded.get(i);
            int j = entity.chunkCoordX;
            int k = entity.chunkCoordZ;

            if (entity.addedToChunk && this.isLoaded(j, k, true))
            {
                this.getChunk(j, k).removeEntity(entity);
            }
        }

        for (int l = 0; l < this.unloaded.size(); ++l)
        {
            this.onEntityRemoved((Entity)this.unloaded.get(l));
        }

        this.unloaded.clear();

        for (int i1 = 0; i1 < this.entities.size(); ++i1)
        {
            Entity entity1 = (Entity)this.entities.get(i1);

            if (entity1.vehicle != null)
            {
                if (!entity1.vehicle.dead && entity1.vehicle.passenger == entity1)
                {
                    continue;
                }

                entity1.vehicle.passenger = null;
                entity1.vehicle = null;
            }

            if (entity1.dead)
            {
                int j1 = entity1.chunkCoordX;
                int k1 = entity1.chunkCoordZ;

                if (entity1.addedToChunk && this.isLoaded(j1, k1, true))
                {
                    this.getChunk(j1, k1).removeEntity(entity1);
                }

                this.entities.remove(i1--);
                this.onEntityRemoved(entity1);
            }
        }
    }
    
    public void playSound(double x, double y, double z, SoundEvent sound, float volume)
    {
//        if(this.gm.oldStepSounds && (soundName.equals("random.swim") || soundName.equals("step.ladder")))
//        	return;
//        if(this.gm.oldStepSounds && soundName.startsWith("step."))
//        	soundName = "dig." + soundName.substring(5);
//        if(this.gm.oldStepSounds && soundName.equals("random.swim_splash"))
//        	soundName = "random.splash";
//        double d0 = this.gm.getRenderViewEntity().getDistanceSq(x, y, z);
        PositionedSound positionedsoundrecord = new PositionedSound(sound, volume, /* pitch, */ (float)x, (float)y, (float)z);

//        if (distanceDelay && d0 > 100.0D)
//        {
//            double d1 = Math.sqrt(d0) / 40.0D;
//            this.gm.getSoundHandler().playDelayedSound(positionedsoundrecord, (int)(d1 * 20.0D));
//        }
//        else
//        {
        this.gm.getSoundManager().playSound(positionedsoundrecord);
//        }
    }
    
    public ChunkClient getChunk(int x, int z)
    {
    	ChunkClient chunk = this.chunkMapping.getValueByKey(LongHashMap.packInt(x, z));
        return chunk == null ? this.emptyChunk : chunk;
    }

	public ChunkClient getChunk(BlockPos pos) {
		return this.getChunk(pos.getX() >> 4, pos.getZ() >> 4);
	}
	
	public Biome getBiomeGenForCoords(BlockPos pos) {
		if(this.isBlockLoaded(pos))
			return this.getChunk(pos).getBiome(pos);
		else
			return Biome.DEF_BIOME;
	}
	
	protected boolean isLoaded(int x, int z, boolean allowEmpty) {
		return allowEmpty || !this.getChunk(x, z).isDummy();
	}
    
    public String getInfo()
    {
        return "Chunk-Cache: M " + this.chunkMapping.getNumHashElements() + ", L " + this.chunkListing.size();
    }
	
    public void playSound(SoundEvent sound, double x, double y, double z, float volume)
    {
    }

//	public void spawnParticle(EnumParticleTypes particleType, boolean force, double xCoord, double yCoord, double zCoord, double xOffset,
//			double yOffset, double zOffset, int... data) {
//		this.spawnParticle(particleType.getParticleID(), particleType.getShouldIgnoreRange() | force, xCoord, yCoord, zCoord, xOffset, yOffset,
//				zOffset, data);
//	}

	public void spawnParticle(ParticleType particle, double xCoord, double yCoord, double zCoord, int data) {
        if (this.gm.getRenderViewEntity() != null)
        {
            double d0 = this.gm.getRenderViewEntity().posX - xCoord;
            double d1 = this.gm.getRenderViewEntity().posY - yCoord;
            double d2 = this.gm.getRenderViewEntity().posZ - zCoord;
            if(particle.isUnlimited() || d0 * d0 + d1 * d1 + d2 * d2 <= 256.0D)
            	this.gm.effectRenderer.spawnParticle(particle, xCoord, yCoord, zCoord, data);
        }
    }

//    public void broadcastSound(int soundID, BlockPos pos, int data)
//    {
//        switch (soundID)
//        {
////            case 1013:
//            case 1018:
//                if (this.gm.getRenderViewEntity() != null)
//                {
//                    double d0 = (double)pos.getX() - this.gm.getRenderViewEntity().posX;
//                    double d1 = (double)pos.getY() - this.gm.getRenderViewEntity().posY;
//                    double d2 = (double)pos.getZ() - this.gm.getRenderViewEntity().posZ;
//                    double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);
//                    double d4 = this.gm.getRenderViewEntity().posX;
//                    double d5 = this.gm.getRenderViewEntity().posY;
//                    double d6 = this.gm.getRenderViewEntity().posZ;
//
//                    if (d3 > 0.0D)
//                    {
//                        d4 += d0 / d3 * 2.0D;
//                        d5 += d1 / d3 * 2.0D;
//                        d6 += d2 / d3 * 2.0D;
//                    }
//
////                    if (soundID == 1013)
////                    {
////                        this.playSound(d4, d5, d6, "mob.wither.spawn", 1.0F, 1.0F);
////                    }
////                    else
////                    {
//                    this.playSound(d4, d5, d6, "mob.dragon.end", 5.0F, 1.0F);
////                    }
//                }
//
//            default:
//        }
//    }
    
    private void playSoundAtPos(BlockPos pos, SoundEvent sound, float volume)
    {
        this.playSound((double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, sound, volume);
    }

    public void playAuxSFX(EntityNPC player, int sfxType, BlockPos blockPosIn, int data)
    {
        switch (sfxType)
        {
            case 1000:
                this.playSoundAtPos(blockPosIn, SoundEvent.CLICK, 1.0F);
                break;

            case 1001:
                this.playSoundAtPos(blockPosIn, SoundEvent.CLICK, 1.0F);
                break;

            case 1002:
                this.playSoundAtPos(blockPosIn, SoundEvent.THROW, 1.0F);
                break;

            case 1003:
                this.playSoundAtPos(blockPosIn, SoundEvent.DOOR, 1.0F);
                break;

            case 1004:
                this.playSoundAtPos(blockPosIn, SoundEvent.FIZZ, 0.5F);
                break;

            case 1005:
                this.playSoundAtPos(blockPosIn, SoundEvent.TELEPORT, 10.0F);
                break;

            case 1006:
                this.playSoundAtPos(blockPosIn, SoundEvent.DOOR, 1.0F);
                break;

            case 1007:
                this.playSoundAtPos(blockPosIn, SoundEvent.SPELL, 10.0F);
                break;

            case 1008:
                this.playSoundAtPos(blockPosIn, SoundEvent.FIREBALL, 10.0F);
                break;

            case 1009:
                this.playSoundAtPos(blockPosIn, SoundEvent.FIREBALL, 2.0F);
                break;

//            case 1010:
//                this.playSoundAtPos(blockPosIn, "dig.wood", 0.6F, (random.floatv() - random.floatv()) * 0.2F + 1.0F);
//                break;

//            case 1011:
//                this.playSoundAtPos(blockPosIn, "random.metal", 2.0F, (random.floatv() - random.floatv()) * 0.2F + 1.0F);
//                break;

//            case 1012:
//                this.playSoundAtPos(blockPosIn, "dig.wood", 2.0F, (random.floatv() - random.floatv()) * 0.2F + 1.0F);
//                break;

            case 1013:
                this.playSoundAtPos(blockPosIn, SoundEvent.TELEPORT_REV, 0.5F);
                break;

            case 1014:
                this.playSoundAtPos(blockPosIn, SoundEvent.METAL, 2.0F);
                break;

            case 1015:
                this.playSoundAtPos(blockPosIn, SoundEvent.BAT_TAKEOFF, 0.05F);
                break;

            case 1016:
                this.playSoundAtPos(blockPosIn, SoundEvent.FIREBALL, 2.0F);
                break;

            case 1017:
                this.playSoundAtPos(blockPosIn, SoundEvent.EXPLODE, 20.0f);
                break;

            case 1020:
                this.playSoundAtPos(blockPosIn, SoundEvent.ANVIL_BREAK, 1.0F);
                break;

            case 1021:
                this.playSoundAtPos(blockPosIn, SoundEvent.ANVIL_USE, 1.0F);
                break;

            case 1022:
                this.playSoundAtPos(blockPosIn, SoundEvent.ANVIL_LAND, 0.3F);
                break;

            case 1023:
//                double d131 = (double)blockPosIn.getX();
//                double d141 = (double)blockPosIn.getY();
//                double d161 = (double)blockPosIn.getZ();
//                for (int i1 = 0; i1 < 8; ++i1) {
//					this.spawnEntityFX(EnumParticleTypes.ITEM_CRACK, EnumParticleTypes.ITEM_CRACK.getShouldIgnoreRange(), 
//							d131, d141, d161, random.gaussian() * 0.15D, random.doublev() * 0.2D, random.gaussian() * 0.15D, 
//							new int[] {ItemRegistry.getIdFromItem(Items.glass), 0});
//                }
                this.playSoundAtPos(blockPosIn, SoundEvent.GLASS, 1.0F);
                break;

            case 1024:
                this.playSoundAtPos(blockPosIn, SoundEvent.CLICK, 1.0F);
                break;

            case 1025:
            	MutableBlockPos pos = new MutableBlockPos(blockPosIn.getX(), blockPosIn.getY(), blockPosIn.getZ());
            	for(int z = 0; z < 1000; z++) {
            		this.spawnParticle(ParticleType.EXPLOSION_HUGE, 
        					(double)pos.getX() + this.rand.gaussian() * 128.0, (double)pos.getY() + this.rand.gaussian() * 2.0, (double)pos.getZ() + this.rand.gaussian() * 128.0);
            	}
            	for(int z = 0; z < 1000; z++) {
            		this.spawnParticle(ParticleType.EXPLOSION_NORMAL, 
            				(double)pos.getX() + this.rand.gaussian() * 128.0, (double)pos.getY() + this.rand.gaussian() * 2.0, (double)pos.getZ() + this.rand.gaussian() * 128.0, 100);
            	}
            	for(int z = 0; z < 30; z++) {
            		this.playSoundAtPos(pos.set(blockPosIn.getX() + this.rand.range(-128, 128), 
            				blockPosIn.getY() + this.rand.range(-4, 4), blockPosIn.getZ() + this.rand.range(-128, 128)), 
            				SoundEvent.EXPLODE, 30.0F);
            	}
                break;

            case 2000:
                int l = data % 3 - 1;
                int i = data / 3 % 3 - 1;
                double d15 = (double)blockPosIn.getX() + (double)l * 0.6D + 0.5D;
                double d17 = (double)blockPosIn.getY() + 0.5D;
                double d19 = (double)blockPosIn.getZ() + (double)i * 0.6D + 0.5D;

                for (int k1 = 0; k1 < 10; ++k1)
                {
                    double d20 = this.rand.doublev() * 0.2D + 0.01D;
                    double d21 = d15 + (double)l * 0.01D + (this.rand.doublev() - 0.5D) * (double)i * 0.5D;
                    double d4 = d17 + (this.rand.doublev() - 0.5D) * 0.5D;
                    double d6 = d19 + (double)i * 0.01D + (this.rand.doublev() - 0.5D) * (double)l * 0.5D;
                    this.spawnParticle(ParticleType.SMOKE, d21, d4, d6);
                }

                return;

            case 2001:
                State state = BlockRegistry.byId(data);

                if (state != null && state.getBlock() != Blocks.air)
                {
                    this.gm.getSoundManager().playSound(new PositionedSound(state.getBlock().sound.getBreakSound(), 1.0F, /* block.sound.getFrequency() * 0.8F, */ (float)blockPosIn.getX() + 0.5F, (float)blockPosIn.getY() + 0.5F, (float)blockPosIn.getZ() + 0.5F));
                }
                if(state != null)
                	this.gm.effectRenderer.destroyBlock(blockPosIn, state);
                break;

            case 2002:
                double d13 = (double)blockPosIn.getX();
                double d14 = (double)blockPosIn.getY();
                double d16 = (double)blockPosIn.getZ();

                for (int i1 = 0; i1 < 8; ++i1)
                {
					this.spawnParticle(ParticleType.ITEM_CRACK, d13, d14, d16, ItemRegistry.getId(Items.water_bottle));
                }

                for (int l1 = 0; l1 < 100; ++l1)
                {
                    this.spawnParticle(ParticleType.POTION, d13, d14, d16, data);
                }

                this.playSoundAtPos(blockPosIn, SoundEvent.GLASS, 1.0F);
                break;

            case 2005:
                ItemDye.spawnBonemealParticles(this, blockPosIn, data);
        }
    }

    public void markBlockForUpdate(BlockPos pos)
    {
        int i = pos.getX();
        int j = pos.getY();
        int k = pos.getZ();
        this.gm.renderGlobal.markBlocksForUpdate(i - 1, j - 1, k - 1, i + 1, j + 1, k + 1);
    }

    public void notifyLightSet(BlockPos pos)
    {
        int i = pos.getX();
        int j = pos.getY();
        int k = pos.getZ();
        this.gm.renderGlobal.markBlocksForUpdate(i - 1, j - 1, k - 1, i + 1, j + 1, k + 1);
    }
    
    public void markBlockRangeForRenderUpdate(int x1, int y1, int z1, int x2, int y2, int z2)
    {
        this.gm.renderGlobal.markBlocksForUpdate(x1 - 1, y1 - 1, z1 - 1, x2 + 1, y2 + 1, z2 + 1);
    }
    
    public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress)
    {
    	this.gm.renderGlobal.sendBlockBreakProgress(breakerId, pos, progress);
    }

	public float getSunBrightness(float p_72971_1_) {
		float f = this.getCelestialAngle(p_72971_1_);
		float f1 = 1.0F - (ExtMath.cos(f * (float)Math.PI * 2.0F) * 2.0F + 0.2F);
		f1 = ExtMath.clampf(f1, 0.0F, 1.0F);
		f1 = 1.0F - f1;
		f1 = (float)((double)f1 * (1.0D - (double)(this.getRainStrength() * 5.0F) / 16.0D));
		f1 = (float)((double)f1 * (1.0D - (double)(this.getDarkness() * 5.0F) / 16.0D));
		return f1 * 0.8F + 0.2F;
	}
	
	private static int hsvToRGB(float hue, float saturation, float value)
    {
        int i = (int)(hue * 6.0F) % 6;
        float f = hue * 6.0F - (float)i;
        float f1 = value * (1.0F - saturation);
        float f2 = value * (1.0F - f * saturation);
        float f3 = value * (1.0F - (1.0F - f) * saturation);
        float f4;
        float f5;
        float f6;

        switch (i)
        {
            case 0:
                f4 = value;
                f5 = f3;
                f6 = f1;
                break;

            case 1:
                f4 = f2;
                f5 = value;
                f6 = f1;
                break;

            case 2:
                f4 = f1;
                f5 = value;
                f6 = f3;
                break;

            case 3:
                f4 = f1;
                f5 = f2;
                f6 = value;
                break;

            case 4:
                f4 = f3;
                f5 = f1;
                f6 = value;
                break;

            case 5:
                f4 = value;
                f5 = f1;
                f6 = f2;
                break;

            default:
                throw new RuntimeException("Something went wrong when converting from HSV to RGB. Input was " + hue + ", " + saturation + ", " + value);
        }

        int j = ExtMath.clampi((int)(f4 * 255.0F), 0, 255);
        int k = ExtMath.clampi((int)(f5 * 255.0F), 0, 255);
        int l = ExtMath.clampi((int)(f6 * 255.0F), 0, 255);
        return j << 16 | k << 8 | l;
    }

	public Vec3 getSkyColor(Entity entity, float partial) {
		BlockPos pos = new BlockPos(ExtMath.floord(entity.posX), ExtMath.floord(entity.posY), 
				ExtMath.floord(entity.posZ));
		Biome biome = this.getBiomeGenForCoords(pos);
		Vec3 vec;
		if(biome.skyColor != 0xffffffff)
			vec = new Vec3(biome.skyColor);
		else
			vec = new Vec3(this.dimension.getSkyColor());
		if(this.dimension.getType().days) {
	    	float mult = ExtMath.clampf(ExtMath.cos(this.getCelestialAngle(partial) * (float)Math.PI * 2.0F) * 2.0F + 0.5F, 
					0.0F, 1.0F);
			if(this.dimension.getSkyColor() == 0xffffffff) {
		    	float temp = ExtMath.clampf(((biome.getTemperature(pos) + 14.0f) / 40.0f + 0.15f) / 3.0F, 
		    			-1.0F, 1.0F);
		    	Vec3 sky = new Vec3(hsvToRGB(0.62222224F - temp * 0.05F, 0.5F + temp * 0.1F, 1.0F));
		    	vec = new Vec3(vec.xCoord * sky.xCoord * mult, vec.yCoord * sky.yCoord * mult, vec.zCoord * sky.zCoord * mult);
			}
			else {
				vec = new Vec3(vec.xCoord * mult, vec.yCoord * mult, vec.zCoord * mult);
			}
		}
		float r = (float)vec.xCoord;
		float g = (float)vec.yCoord;
		float b = (float)vec.zCoord;
		
		float rain = this.getRainStrength();
		if(rain > 0.0F) {
			float mul = (r * 0.3F + g * 0.59F + b * 0.11F) * 0.6F;
			float shift = 1.0F - rain * 0.75F;
			r = r * shift + mul * (1.0F - shift);
			g = g * shift + mul * (1.0F - shift);
			b = b * shift + mul * (1.0F - shift);
		}

		float dark = this.getDarkness();
		if(dark > 0.0F) {
			float mul = (r * 0.3F + g * 0.59F + b * 0.11F) * 0.2F;
			float shift = 1.0F - dark * 0.75F;
			r = r * shift + mul * (1.0F - shift);
			g = g * shift + mul * (1.0F - shift);
			b = b * shift + mul * (1.0F - shift);
		}

		if(this.lastLightning > 0) {
			float light = (float)this.lastLightning - partial;
			if(light > 1.0F)
				light = 1.0F;
//			light = light * 0.45F;
			r = r * (1.0F - light) + (float)this.lightColor.xCoord * light;
			g = g * (1.0F - light) + (float)this.lightColor.yCoord * light;
			b = b * (1.0F - light) + (float)this.lightColor.zCoord * light;
		}

		return new Vec3((double)r, (double)g, (double)b);
	}

	public Vec3 getCloudColour(Entity entity, float partialTicks) {
		Vec3 color = new Vec3(this.dimension.getCloudColor());
		Biome biome = this.getBiomeGenForCoords(new BlockPos(ExtMath.floord(entity.posX), ExtMath.floord(entity.posY), 
				ExtMath.floord(entity.posZ)));
		if(biome.cloudColor != 0xffffffff)
			color = new Vec3(biome.cloudColor);
		float r = (float)color.xCoord;
		float g = (float)color.yCoord;
		float b = (float)color.zCoord;
		
		float rain = this.getRainStrength();
		if(rain > 0.0F) {
			float mul = (r * 0.3F + g * 0.59F + b * 0.11F) * 0.6F;
			float shift = 1.0F - rain * 0.95F;
			r = r * shift + mul * (1.0F - shift);
			g = g * shift + mul * (1.0F - shift);
			b = b * shift + mul * (1.0F - shift);
		}

		if(this.dimension.getType().days) {
			float sun = ExtMath.clampf(ExtMath.cos(this.getCelestialAngle(partialTicks) * (float)Math.PI * 2.0F) * 2.0F + 0.5F, 
					0.0F, 1.0F);
			r = r * (sun * 0.9F + 0.1F);
			g = g * (sun * 0.9F + 0.1F);
			b = b * (sun * 0.85F + 0.15F);
		}
		
		float dark = this.getDarkness();
		if(dark > 0.0F) {
			float mul = (r * 0.3F + g * 0.59F + b * 0.11F) * 0.2F;
			float shift = 1.0F - dark * 0.95F;
			r = r * shift + mul * (1.0F - shift);
			g = g * shift + mul * (1.0F - shift);
			b = b * shift + mul * (1.0F - shift);
		}

		return new Vec3((double)r, (double)g, (double)b);
	}

	public Vec3 getFogColor(Entity entity, float partialTicks) {
		Vec3 color = new Vec3(this.dimension.getFogColor());
		Biome biome = this.getBiomeGenForCoords(new BlockPos(ExtMath.floord(entity.posX), ExtMath.floord(entity.posY), 
				ExtMath.floord(entity.posZ)));
		if(biome.fogColor != 0xffffffff)
			color = new Vec3(biome.fogColor);
		if(!this.dimension.getType().days)
			return color;
		float sun = ExtMath.clampf(ExtMath.cos(this.getCelestialAngle(partialTicks) * (float)Math.PI * 2.0F) * 2.0F + 0.5F, 
				0.0F, 1.0F);
		float r = (float)color.xCoord;
		float g = (float)color.yCoord;
		float b = (float)color.zCoord;
		r = r * (sun * 0.94F + 0.06F);
		g = g * (sun * 0.94F + 0.06F);
		b = b * (sun * 0.91F + 0.09F);
		return new Vec3((double)r, (double)g, (double)b);
	}

	public float getStarBrightness(float partialTicks) {
		float f = this.getCelestialAngle(partialTicks);
		float f1 = 1.0F - (ExtMath.cos(f * (float)Math.PI * 2.0F) * 2.0F + 0.25F);
		f1 = ExtMath.clampf(f1, 0.0F, 1.0F);
		return f1 * f1 * this.dimension.getStarBrightness();
	}

	public float getDeepStarBrightness(float partialTicks) {
		float f = this.getCelestialAngle(partialTicks);
		float f1 = 1.0F - (ExtMath.cos(f * (float)Math.PI * 2.0F) * 2.0F + 0.25F);
		f1 = ExtMath.clampf(f1, 0.0F, 1.0F);
		return f1 * f1 * this.dimension.getDeepStarBrightness();
	}

	public int getLastLightning() {
		return this.lastLightning;
	}

	public Vec3 getLightColor() {
		return this.lightColor;
	}

	public void decrLightning() {
		if(this.lastLightning > 0)
			this.lastLightning -= 1;
	}

	public void setLastLightning(int last, int color) {
		this.lastLightning = last;
		this.lightColor = new Vec3(color);
	}

	public void ensureAreaLoaded(Entity entityIn) {
		int i = ExtMath.floord(entityIn.posX / 16.0D);
		int j = ExtMath.floord(entityIn.posZ / 16.0D);
		int k = 2;

		for(int l = i - k; l <= i + k; ++l) {
			for(int i1 = j - k; i1 <= j + k; ++i1) {
				this.getChunk(l, i1);
			}
		}

		if(!this.entities.contains(entityIn)) {
			this.entities.add(entityIn);
		}
	}

	public List<Entity> getLoadedEntityList() {
		return this.entities;
	}

	public List<TileEntity> getLoadedTileList() {
		return this.tiles;
	}

	public String getDebugLoadedEntities() {
		return "" + this.entities.size();
	}
}
