package common.block;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.Function;

import common.collect.ImmutableList;
import common.collect.ImmutableMap;
import common.collect.Iterables;
import common.collect.Lists;
import common.collect.Maps;
import common.collect.UnmodifiableIterator;
import common.enchantment.EnchantmentHelper;
import common.entity.Entity;
import common.entity.item.EntityItem;
import common.entity.item.EntityXp;
import common.entity.npc.EntityNPC;
import common.entity.types.EntityLiving;
import common.init.Config;
import common.init.ItemRegistry;
import common.item.CheatTab;
import common.item.Item;
import common.item.ItemStack;
import common.material.Material;
import common.model.BlockLayer;
import common.model.Model;
import common.model.ModelProvider;
import common.model.Transforms;
import common.properties.IProperty;
import common.rng.Random;
import common.tileentity.TileEntity;
import common.util.BlockPos;
import common.util.BoundingBox;
import common.util.ExtMath;
import common.util.Facing;
import common.util.HitPosition;
import common.util.Vec3;
import common.util.HitPosition.ObjectType;
import common.world.Explosion;
import common.world.IBlockAccess;
import common.world.IWorldAccess;
import common.world.AWorldClient;
import common.world.State;
import common.world.World;
import common.world.AWorldServer;

public class Block
{
	private static class Product<T> implements Iterable<T[]>
    {
        private final Class<T> clazz;
        private final Iterable <? extends T > [] iterables;

        private Product(Class<T> clazz, Iterable <? extends T > [] iterables)
        {
            this.clazz = clazz;
            this.iterables = iterables;
        }

        public Iterator<T[]> iterator()
        {
            return (Iterator<T[]>)(this.iterables.length <= 0 ? Collections.singletonList((Object[])createArray(this.clazz, 0)).iterator() : new Product.ProductIterator(this.clazz, this.iterables));
        }

        static class ProductIterator<T> extends UnmodifiableIterator<T[]>
        {
            private int index;
            private final Iterable <? extends T > [] iterables;
            private final Iterator <? extends T > [] iterators;
            private final T[] results;

            private ProductIterator(Class<T> clazz, Iterable <? extends T > [] iterables)
            {
                this.index = -2;
                this.iterables = iterables;
                this.iterators = (Iterator[])createArray(Iterator.class, this.iterables.length);

                for (int i = 0; i < this.iterables.length; ++i)
                {
                    this.iterators[i] = iterables[i].iterator();
                }

                this.results = createArray(clazz, this.iterators.length);
            }

            private void endOfData()
            {
                this.index = -1;
                Arrays.fill(this.iterators, (Object)null);
                Arrays.fill(this.results, (Object)null);
            }

            public boolean hasNext()
            {
                if (this.index == -2)
                {
                    this.index = 0;

                    for (Iterator <? extends T > iterator1 : this.iterators)
                    {
                        if (!iterator1.hasNext())
                        {
                            this.endOfData();
                            break;
                        }
                    }

                    return true;
                }
                else
                {
                    if (this.index >= this.iterators.length)
                    {
                        for (this.index = this.iterators.length - 1; this.index >= 0; --this.index)
                        {
                            Iterator <? extends T > iterator = this.iterators[this.index];

                            if (iterator.hasNext())
                            {
                                break;
                            }

                            if (this.index == 0)
                            {
                                this.endOfData();
                                break;
                            }

                            iterator = this.iterables[this.index].iterator();
                            this.iterators[this.index] = iterator;

                            if (!iterator.hasNext())
                            {
                                this.endOfData();
                                break;
                            }
                        }
                    }

                    return this.index >= 0;
                }
            }

            public T[] next()
            {
                if (!this.hasNext())
                {
                    throw new NoSuchElementException();
                }
                else
                {
                    while (this.index < this.iterators.length)
                    {
                        this.results[this.index] = this.iterators[this.index].next();
                        ++this.index;
                    }

                    return (T[])((Object[])this.results.clone());
                }
            }
        }
    }

    private static class GetList<T> implements Function<Object[], List<T>>
    {
        private GetList()
        {
        }

        public List<T> apply(Object[] p_apply_1_)
        {
            return Arrays.<T>asList((T[])p_apply_1_);
        }
    }
	
	private final ImmutableList<IProperty> properties;
	private final ImmutableList<State> states;
    protected final Material material;
	
    protected boolean fullBlock;
    protected boolean translucent;
    protected boolean sumBrightness;
    protected boolean axeHarvest;
    protected boolean shovelHarvest;
//    protected boolean enableStats;
    protected boolean ticked;
    protected boolean hasTile;
    protected int lightOpacity;
    protected int lightValue;
    protected int miningLevel;
    protected int shearsEfficiency;
    protected float blockHardness;
    protected float blockResistance;
    protected float radiation;
    public float slipperiness;
    protected double minX;
    protected double minY;
    protected double minZ;
    protected double maxX;
    protected double maxY;
    protected double maxZ;
    private State defaultState;
    private String display;
    private CheatTab tab;
    public SoundType sound;
    
	private static <T> Iterable<List<T>> cartesianProduct(Iterable <? extends Iterable <? extends T >> sets)
    {
        return arraysAsLists(cartesianProduct(Object.class, sets));
    }
    
	private static <T> Iterable<T[]> cartesianProduct(Class<T> clazz, Iterable <? extends Iterable <? extends T >> sets)
    {
        return new Product(clazz, (Iterable[])toArray(Iterable.class, sets));
    }

    private static <T> Iterable<List<T>> arraysAsLists(Iterable<Object[]> arrays)
    {
        return Iterables.transform(arrays, new GetList());
    }

    private static <T> T[] toArray(Class <? super T > clazz, Iterable <? extends T > it)
    {
        List<T> list = Lists.<T>newArrayList();

        for (T t : it)
        {
            list.add(t);
        }

        return (T[])((Object[])list.toArray(createArray(clazz, list.size())));
    }

    private static <T> T[] createArray(Class <? super T > p_179319_0_, int p_179319_1_)
    {
        return (T[])((Object[])((Object[])Array.newInstance(p_179319_0_, p_179319_1_)));
    }
    
    private static Map<IProperty, Comparable> createMap(Iterable<IProperty> keys, Iterable<Comparable> values)
    {
        return populateMap(keys, values, Maps.<IProperty, Comparable>newLinkedHashMap());
    }

    private static Map<IProperty, Comparable> populateMap(Iterable<IProperty> keys, Iterable<Comparable> values, Map<IProperty, Comparable> map)
    {
        Iterator<Comparable> iterator = values.iterator();

        for (IProperty k : keys)
        {
            map.put(k, iterator.next());
        }

        if (iterator.hasNext())
        {
            throw new NoSuchElementException();
        }
        else
        {
            return map;
        }
    }

    public Block(Material material)
    {
    	this.miningLevel = (material == Material.rock || material == Material.iron || material == Material.anvil) ? 0 : -1;
    	this.axeHarvest = material == Material.wood || material == Material.plants || material == Material.vine || 
    			material == Material.gourd;
    	this.shearsEfficiency = (material == Material.leaves || material == Material.web) ? 3 : -1;
//        this.enableStats = true;
        this.sound = SoundType.STONE;
        this.slipperiness = 0.6F;
        this.material = material;
//        this.blockMapColor = blockMapColorIn;
        this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
        this.fullBlock = this.isOpaqueCube();
        this.lightOpacity = this.isOpaqueCube() ? 255 : 0;
        this.translucent = !material.blocksLight();
//        this.smap = new StateList(this, this.getProperties());
		IProperty[] properties = this.getProperties();
		Arrays.sort(properties, new Comparator<IProperty>() {
			public int compare(IProperty p1, IProperty p2) {
				return p1.getName().compareTo(p2.getName());
			}
		});
		this.properties = ImmutableList.copyOf(properties);
		Map<Map<IProperty, Comparable>, State> map = Maps.<Map<IProperty, Comparable>, State>newLinkedHashMap();
		List<State> list = Lists.<State>newArrayList();
		for(List<Comparable> allowed : cartesianProduct(this.getAllowedValues())) {
			Map<IProperty, Comparable> props = createMap(this.properties, allowed);
			State state = new State(this, ImmutableMap.copyOf(props));
			map.put(props, state);
			list.add(state);
		}
		for(State state : list) {
			state.buildTable(map);
		}
		this.states = ImmutableList.<State>copyOf(list);
        this.setDefaultState(this.getBaseState());
    }

    /**
     * Sets the footstep sound for the block. Returns the object for convenience in constructing.
     */
    public Block setStepSound(SoundType sound)
    {
        this.sound = sound;
        return this;
    }

    /**
     * Sets how much light is blocked going through this block. Returns the object for convenience in constructing.
     */
    public Block setLightOpacity(int opacity)
    {
        this.lightOpacity = opacity;
        return this;
    }

    /**
     * Sets the light value that the block emits. Returns resulting block instance for constructing convenience. Args:
     * level
     */
    public Block setLightLevel(float value)
    {
        this.lightValue = (int)(15.0F * value);
        return this;
    }

    /**
     * Sets the the blocks resistance to explosions. Returns the object for convenience in constructing.
     */
    public Block setResistance(float resistance)
    {
        this.blockResistance = resistance * 3.0F;
        return this;
    }

    /**
     * Sets how many hits it takes to break a block.
     */
    public Block setHardness(float hardness)
    {
        this.blockHardness = hardness;

        if (this.blockResistance < hardness * 5.0F)
        {
            this.blockResistance = hardness * 5.0F;
        }

        return this;
    }

//    public Block setBlockUnbreakable()
//    {
//        this.setHardness(-1.0F);
//        this.setResistance(6000000.0F);
//        return this;
//    }

    /**
     * Sets whether this block type will receive random update ticks
     */
    protected Block setTickRandomly()
    {
        this.ticked = true;
        return this;
    }

    public Block setDisplay(String name)
    {
        this.display = name;
        return this;
    }

//    public Block disableStats()
//    {
//        this.enableStats = false;
//        return this;
//    }

    public Block setTab(CheatTab tab)
    {
        this.tab = tab;
        return this;
    }
    
    public Block setSumBrightness()
    {
        this.sumBrightness = true;
        return this;
    }
    
    public Block setMiningLevel(int level)
    {
        this.miningLevel = level;
        return this;
    }
    
    public Block setAxeHarvestable()
    {
        this.axeHarvest = true;
        return this;
    }
    
    public Block setShovelHarvestable()
    {
        this.shovelHarvest = true;
        return this;
    }
    
    public Block setShearsEfficiency(int efficiency)
    {
        this.shearsEfficiency = efficiency;
        return this;
    }
    
    public Block setRadiation(float value)
    {
        this.radiation = value;
        if(value > 0.0f)
        	this.ticked = true;
        return this;
    }
    
    

    public boolean isFullBlock()
    {
        return this.fullBlock;
    }

    public int getLightOpacity()
    {
        return this.lightOpacity;
    }

    /**
     * Used in the renderer to apply ambient occlusion
     */
    public boolean isTranslucent()
    {
        return this.translucent;
    }

    public int getLightValue()
    {
        return this.lightValue;
    }

    /**
     * Should block use the brightest neighbor light value as its own
     */
    public boolean getSumBrightness()
    {
        return this.sumBrightness;
    }

    /**
     * Get a material of block
     */
    public Material getMaterial()
    {
        return this.material;
    }

	public ImmutableList<State> getValidStates() {
		return this.states;
	}

	private List<Iterable<Comparable>> getAllowedValues() {
		List<Iterable<Comparable>> list = Lists.<Iterable<Comparable>>newArrayList();
		for(int z = 0; z < this.properties.size(); z++) {
			list.add((this.properties.get(z)).getAllowedValues());
		}
		return list;
	}

	public final State getBaseState() {
		return this.states.get(0);
	}

//	public Block getBlock() {
//		return this;
//	}

	public Collection<IProperty> getPropertyMap() {
		return this.properties;
	}

//	public String toString() {
//		return Objects.toStringHelper(this).add("block", BlockRegistry.REGISTRY.getNameForObject(this))
//				.add("properties", Iterables.transform(this.properties, GET_NAME_FUNC)).toString();
//	}

//    /**
//     * Get the MapColor for this Block and the given BlockState
//     */
//    public final MapColor getMapColor(IBlockState state)
//    {
//        return this.blockMapColor;
//    }

    /**
     * Convert the given metadata into a BlockState for this Block
     */
    public State getStateFromMeta(int meta)
    {
        return this.getState();
    }

    /**
     * Convert the BlockState into the correct metadata value
     */
    public int getMetaFromState(State state)
    {
        if (state != null && !state.getPropertyNames().isEmpty())
        {
            throw new IllegalArgumentException("Don\'t know how to convert " + state + " back into data...");
        }
        else
        {
            return 0;
        }
    }

    /**
     * Get the actual Block state of this Block at the given position. This applies properties not visible in the
     * metadata, such as fence connections.
     */
    public State getActualState(State state, IWorldAccess worldIn, BlockPos pos)
    {
        return state;
    }

    /**
     * Indicate if a material is a normal solid opaque cube
     */
    public boolean isBlockNormalCube()
    {
        return this.material.blocksMovement() && this.isFullCube();
    }

    /**
     * Used for nearly all game logic (non-rendering) purposes. Use Forge-provided isNormalCube(IBlockAccess, BlockPos)
     * instead.
     */
    public boolean isNormalCube()
    {
        return this.material.isOpaque() && this.isFullCube() && !this.canProvidePower();
    }

    public boolean isVisuallyOpaque()
    {
        return this.material.blocksMovement() && this.isFullCube();
    }

    public boolean isFullCube()
    {
        return true;
    }

    public boolean isPassable(IBlockAccess worldIn, BlockPos pos)
    {
        return !this.material.blocksMovement();
    }

    /**
     * The type of render function called. 3 for standard block models, 2 for TESR's, 1 for liquids, -1 is no render
     */
    public int getRenderType()
    {
        return 3;
    }
    
    public boolean isXrayVisible()
    {
        return false;
    }

    /**
     * Whether this Block can be replaced directly by other blocks (true for e.g. tall grass)
     */
    public boolean isReplaceable(World worldIn, BlockPos pos)
    {
        return false;
    }

    public float getBlockHardness(World worldIn, BlockPos pos)
    {
        return this.blockHardness;
    }

    /**
     * Returns whether or not this block is of a type that needs random ticking. Called for ref-counting purposes by
     * ExtendedBlockStorage in order to broadly cull a chunk from the random chunk update list for efficiency's sake.
     */
    public boolean getTickRandomly()
    {
        return this.ticked;
    }

    public boolean hasTileEntity()
    {
        return this.hasTile;
    }

    protected final void setBlockBounds(float minX, float minY, float minZ, float maxX, float maxY, float maxZ)
    {
        this.minX = (double)minX;
        this.minY = (double)minY;
        this.minZ = (double)minZ;
        this.maxX = (double)maxX;
        this.maxY = (double)maxY;
        this.maxZ = (double)maxZ;
    }

    public int getMixedBrightnessForBlock(IWorldAccess worldIn, BlockPos pos)
    {
        Block block = worldIn.getState(pos).getBlock();
        int i = worldIn.getCombinedLight(pos, block.getLightValue());

        if (i == 0 && block instanceof BlockSlab)
        {
            pos = pos.down();
            block = worldIn.getState(pos).getBlock();
            return worldIn.getCombinedLight(pos, block.getLightValue());
        }
        else
        {
            return i;
        }
    }

    public boolean shouldSideBeRendered(IWorldAccess worldIn, BlockPos pos, Facing side)
    {
        return side == Facing.DOWN && this.minY > 0.0D ? true : (side == Facing.UP && this.maxY < 1.0D ? true : (side == Facing.NORTH && this.minZ > 0.0D ? true : (side == Facing.SOUTH && this.maxZ < 1.0D ? true : (side == Facing.WEST && this.minX > 0.0D ? true : (side == Facing.EAST && this.maxX < 1.0D ? true : !worldIn.getState(pos).getBlock().isOpaqueCube())))));
    }

    /**
     * Whether this Block is solid on the given Side
     */
    public boolean isBlockSolid(IBlockAccess worldIn, BlockPos pos, Facing side)
    {
        return worldIn.getState(pos).getBlock().getMaterial().isSolid();
    }

    public BoundingBox getSelectedBoundingBox(World worldIn, BlockPos pos)
    {
        return new BoundingBox((double)pos.getX() + this.minX, (double)pos.getY() + this.minY, (double)pos.getZ() + this.minZ, (double)pos.getX() + this.maxX, (double)pos.getY() + this.maxY, (double)pos.getZ() + this.maxZ);
    }

    /**
     * Add all collision boxes of this Block to the list that intersect with the given mask.
     */
    public void addCollisionBoxesToList(World worldIn, BlockPos pos, State state, BoundingBox mask, List<BoundingBox> list, Entity collidingEntity)
    {
        BoundingBox axisalignedbb = this.getCollisionBoundingBox(worldIn, pos, state);

        if (axisalignedbb != null && mask.intersectsWith(axisalignedbb))
        {
            list.add(axisalignedbb);
        }
    }

    public BoundingBox getCollisionBoundingBox(World worldIn, BlockPos pos, State state)
    {
        return new BoundingBox((double)pos.getX() + this.minX, (double)pos.getY() + this.minY, (double)pos.getZ() + this.minZ, (double)pos.getX() + this.maxX, (double)pos.getY() + this.maxY, (double)pos.getZ() + this.maxZ);
    }

    /**
     * Used to determine ambient occlusion and culling when rebuilding chunks for render
     */
    public boolean isOpaqueCube()
    {
        return true;
    }

    public boolean canCollideCheck(State state, boolean hitIfLiquid)
    {
        return this.isCollidable();
    }

    /**
     * Returns if this block is collidable (only used by Fire). Args: x, y, z
     */
    public boolean isCollidable()
    {
        return true;
    }

    /**
     * Called randomly when setTickRandomly is set to true (used by e.g. crops to grow, etc.)
     */
    public void randomTick(AWorldServer worldIn, BlockPos pos, State state, Random random)
    {
        this.updateTick(worldIn, pos, state, random);
        if(this.radiation > 0.0f && /* worldIn.getTime() % 5L == 0L && */ random.chance(Config.randomTick / 3))
        	this.affectEntities(worldIn, pos, state, this.radiation * 8.0f * 0.25f);
    }

    private void affectEntities(AWorldServer worldIn, BlockPos pos, State state, float rad)
    {
    	float r = ExtMath.clampf(rad * 2.0f, 0.0f, 25.0f);
    	BoundingBox box = this.getCollisionBoundingBox(worldIn, pos, state);
    	if(box == null)
    		box = new BoundingBox(pos, pos.add(1, 1, 1));
        for (EntityLiving entity : worldIn.getEntitiesWithinAABB(EntityLiving.class, box.expand(r, r, r)))
        {
        	float effect = rad * 2.0f * (r - ExtMath.sqrtf((float)entity.getDistanceSq(pos))) / r;
        	if(effect > 0.0f)
        		entity.addRadiation(effect);
        }
    }

    public void updateTick(AWorldServer worldIn, BlockPos pos, State state, Random rand)
    {
    }

    public void randomDisplayTick(AWorldClient worldIn, BlockPos pos, State state, Random rand)
    {
    }

    /**
     * Called when a player destroys this Block
     */
    public void onBlockDestroyedByPlayer(World worldIn, BlockPos pos, State state)
    {
    }

    /**
     * Called when a neighboring block changes.
     */
    public void onNeighborBlockChange(World worldIn, BlockPos pos, State state, Block neighborBlock)
    {
    }

    /**
     * How many world ticks before ticking
     */
    public int tickRate(World worldIn, BlockPos pos)
    {
        return 10;
    }

    public void onBlockAdded(AWorldServer world, BlockPos pos, State state)
    {
    }

    public void onBlockRemoved(AWorldServer world, BlockPos pos, State state)
    {
    }

    /**
     * Returns the quantity of items to drop on block destruction.
     */
    public int quantityDropped(Random random)
    {
        return 1;
    }

    /**
     * Get the Item that this Block should drop when harvested.
     */
    public Item getItemDropped(State state, Random rand, int fortune)
    {
        return ItemRegistry.getItemFromBlock(this);
    }

    /**
     * Get the hardness of this Block relative to the ability of the given player
     */
    public float getPlayerRelativeBlockHardness(EntityNPC playerIn, World worldIn, BlockPos pos)
    {
        float f = this.getBlockHardness(worldIn, pos);
        return f < 0.0F ? 0.0F : (!playerIn.canHarvestBlock(this) ? playerIn.getToolDigEfficiency(this) / f / 100.0F : playerIn.getToolDigEfficiency(this) / f / 30.0F);
    }

    /**
     * Spawn this Block's drops into the World as EntityItems
     */
    public final void dropBlockAsItem(World worldIn, BlockPos pos, State state, int forture)
    {
        this.dropBlockAsItemWithChance(worldIn, pos, state, 1.0F, forture);
    }

    /**
     * Spawns this Block's drops into the World as EntityItems.
     */
    public void dropBlockAsItemWithChance(World worldIn, BlockPos pos, State state, float chance, int fortune)
    {
        if (!worldIn.client)
        {
            int i = this.quantityDroppedWithBonus(fortune, worldIn.rand);

            for (int j = 0; j < i; ++j)
            {
                if (worldIn.rand.floatv() <= chance)
                {
                    Item item = this.getItemDropped(state, worldIn.rand, fortune);

                    if (item != null)
                    {
                        spawnAsEntity(worldIn, pos, new ItemStack(item, 1, this.damageDropped(state)));
                    }
                }
            }
        }
    }

    /**
     * Spawns the given ItemStack as an EntityItem into the World at the given position
     */
    public static void spawnAsEntity(World worldIn, BlockPos pos, ItemStack stack)
    {
        if (!worldIn.client && Config.blockDrop)
        {
            float f = 0.5F;
            double d0 = (double)(worldIn.rand.floatv() * f) + (double)(1.0F - f) * 0.5D;
            double d1 = (double)(worldIn.rand.floatv() * f) + (double)(1.0F - f) * 0.5D;
            double d2 = (double)(worldIn.rand.floatv() * f) + (double)(1.0F - f) * 0.5D;
            EntityItem entityitem = new EntityItem(worldIn, (double)pos.getX() + d0, (double)pos.getY() + d1, (double)pos.getZ() + d2, stack);
            entityitem.setDefaultPickupDelay();
            worldIn.spawnEntityInWorld(entityitem);
        }
    }

    /**
     * Spawns the given amount of experience into the World as XP orb entities
     */
    protected void dropXpOnBlockBreak(World worldIn, BlockPos pos, int amount)
    {
        if (!worldIn.client && Config.blockXP)
        {
            while (amount > 0)
            {
                int i = EntityXp.getXPSplit(amount);
                amount -= i;
                worldIn.spawnEntityInWorld(new EntityXp(worldIn, (double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, i));
            }
        }
    }

    /**
     * Gets the metadata of the item this Block can drop. This method is called when the block gets destroyed. It
     * returns the metadata of the dropped item based on the old metadata of the block.
     */
    public int damageDropped(State state)
    {
        return 0;
    }

    /**
     * Returns how much this block can resist explosions from the passed in entity.
     */
    public float getExplosionResistance(Entity exploder)
    {
        return this.blockResistance / 5.0F;
    }

    /**
     * Ray traces through the blocks collision from start vector to end vector returning a ray trace hit.
     */
    public HitPosition collisionRayTrace(World worldIn, BlockPos pos, Vec3 start, Vec3 end)
    {
        this.setBlockBoundsBasedOnState(worldIn, pos);
        start = start.addVector((double)(-pos.getX()), (double)(-pos.getY()), (double)(-pos.getZ()));
        end = end.addVector((double)(-pos.getX()), (double)(-pos.getY()), (double)(-pos.getZ()));
        Vec3 vec3 = start.getIntermediateWithXValue(end, this.minX);
        Vec3 vec31 = start.getIntermediateWithXValue(end, this.maxX);
        Vec3 vec32 = start.getIntermediateWithYValue(end, this.minY);
        Vec3 vec33 = start.getIntermediateWithYValue(end, this.maxY);
        Vec3 vec34 = start.getIntermediateWithZValue(end, this.minZ);
        Vec3 vec35 = start.getIntermediateWithZValue(end, this.maxZ);

        if (!this.isVecInsideYZBounds(vec3))
        {
            vec3 = null;
        }

        if (!this.isVecInsideYZBounds(vec31))
        {
            vec31 = null;
        }

        if (!this.isVecInsideXZBounds(vec32))
        {
            vec32 = null;
        }

        if (!this.isVecInsideXZBounds(vec33))
        {
            vec33 = null;
        }

        if (!this.isVecInsideXYBounds(vec34))
        {
            vec34 = null;
        }

        if (!this.isVecInsideXYBounds(vec35))
        {
            vec35 = null;
        }

        Vec3 vec36 = null;

        if (vec3 != null && (vec36 == null || start.squareDistanceTo(vec3) < start.squareDistanceTo(vec36)))
        {
            vec36 = vec3;
        }

        if (vec31 != null && (vec36 == null || start.squareDistanceTo(vec31) < start.squareDistanceTo(vec36)))
        {
            vec36 = vec31;
        }

        if (vec32 != null && (vec36 == null || start.squareDistanceTo(vec32) < start.squareDistanceTo(vec36)))
        {
            vec36 = vec32;
        }

        if (vec33 != null && (vec36 == null || start.squareDistanceTo(vec33) < start.squareDistanceTo(vec36)))
        {
            vec36 = vec33;
        }

        if (vec34 != null && (vec36 == null || start.squareDistanceTo(vec34) < start.squareDistanceTo(vec36)))
        {
            vec36 = vec34;
        }

        if (vec35 != null && (vec36 == null || start.squareDistanceTo(vec35) < start.squareDistanceTo(vec36)))
        {
            vec36 = vec35;
        }

        if (vec36 == null)
        {
            return null;
        }
        else
        {
            Facing enumfacing = null;

            if (vec36 == vec3)
            {
                enumfacing = Facing.WEST;
            }

            if (vec36 == vec31)
            {
                enumfacing = Facing.EAST;
            }

            if (vec36 == vec32)
            {
                enumfacing = Facing.DOWN;
            }

            if (vec36 == vec33)
            {
                enumfacing = Facing.UP;
            }

            if (vec36 == vec34)
            {
                enumfacing = Facing.NORTH;
            }

            if (vec36 == vec35)
            {
                enumfacing = Facing.SOUTH;
            }

            return new HitPosition(ObjectType.BLOCK, vec36.addVector((double)pos.getX(), (double)pos.getY(), (double)pos.getZ()), enumfacing, pos);
        }
    }

    /**
     * Checks if a vector is within the Y and Z bounds of the block.
     */
    private boolean isVecInsideYZBounds(Vec3 point)
    {
        return point == null ? false : point.yCoord >= this.minY && point.yCoord <= this.maxY && point.zCoord >= this.minZ && point.zCoord <= this.maxZ;
    }

    /**
     * Checks if a vector is within the X and Z bounds of the block.
     */
    private boolean isVecInsideXZBounds(Vec3 point)
    {
        return point == null ? false : point.xCoord >= this.minX && point.xCoord <= this.maxX && point.zCoord >= this.minZ && point.zCoord <= this.maxZ;
    }

    /**
     * Checks if a vector is within the X and Y bounds of the block.
     */
    private boolean isVecInsideXYBounds(Vec3 point)
    {
        return point == null ? false : point.xCoord >= this.minX && point.xCoord <= this.maxX && point.yCoord >= this.minY && point.yCoord <= this.maxY;
    }

    /**
     * Called when this Block is destroyed by an Explosion
     */
    public void onBlockDestroyedByExplosion(World worldIn, BlockPos pos, Explosion explosionIn, State prevState)
    {
    }

    public BlockLayer getBlockLayer()
    {
        return BlockLayer.SOLID;
    }

    public boolean canReplace(World worldIn, BlockPos pos, Facing side, ItemStack stack)
    {
        return this.canPlaceBlockOnSide(worldIn, pos, side);
    }

    /**
     * Check whether this Block can be placed on the given side
     */
    public boolean canPlaceBlockOnSide(World worldIn, BlockPos pos, Facing side)
    {
        return this.canPlaceBlockAt(worldIn, pos);
    }

    public boolean canPlaceBlockAt(World worldIn, BlockPos pos)
    {
        return worldIn.getState(pos).getBlock().material.isReplaceable();
    }

    public boolean onBlockActivated(World worldIn, BlockPos pos, State state, EntityNPC playerIn, Facing side, float hitX, float hitY, float hitZ)
    {
        return false;
    }

    /**
     * Triggered whenever an entity collides with this block (enters into the block)
     */
    public void onEntityCollidedWithBlock(World worldIn, BlockPos pos, Entity entityIn)
    {
    }

    /**
     * Called by ItemBlocks just before a block is actually set in the world, to allow for adjustments to the
     * IBlockstate
     */
    public State onBlockPlaced(World worldIn, BlockPos pos, Facing facing, float hitX, float hitY, float hitZ, int meta, EntityLiving placer)
    {
        return this.getStateFromMeta(meta);
    }

    public void onBlockClicked(World worldIn, BlockPos pos, EntityNPC playerIn)
    {
    }

    public Vec3 modifyAcceleration(World worldIn, BlockPos pos, Entity entityIn, Vec3 motion)
    {
        return motion;
    }

    public void setBlockBoundsBasedOnState(IWorldAccess worldIn, BlockPos pos)
    {
    }

    /**
     * returns the block bounderies minX value
     */
    public final double getBlockBoundsMinX()
    {
        return this.minX;
    }

    /**
     * returns the block bounderies maxX value
     */
    public final double getBlockBoundsMaxX()
    {
        return this.maxX;
    }

    /**
     * returns the block bounderies minY value
     */
    public final double getBlockBoundsMinY()
    {
        return this.minY;
    }

    /**
     * returns the block bounderies maxY value
     */
    public final double getBlockBoundsMaxY()
    {
        return this.maxY;
    }

    /**
     * returns the block bounderies minZ value
     */
    public final double getBlockBoundsMinZ()
    {
        return this.minZ;
    }

    /**
     * returns the block bounderies maxZ value
     */
    public final double getBlockBoundsMaxZ()
    {
        return this.maxZ;
    }

//    public int getBlockColor()
//    {
//        return 16777215;
//    }

    public int getRenderColor(State state)
    {
        return 16777215;
    }

    public int colorMultiplier(IWorldAccess worldIn, BlockPos pos, int renderPass)
    {
        return 16777215;
    }

    public final int colorMultiplier(IWorldAccess worldIn, BlockPos pos)
    {
        return this.colorMultiplier(worldIn, pos, 0);
    }

    public int getWeakPower(IWorldAccess worldIn, BlockPos pos, State state, Facing side)
    {
        return 0;
    }

    /**
     * Can this block provide power. Only wire currently seems to have this change based on its state.
     */
    public boolean canProvidePower()
    {
        return false;
    }

    /**
     * Called When an Entity Collided with the Block
     */
    public void onEntityCollidedWithBlock(World worldIn, BlockPos pos, State state, Entity entityIn)
    {
    }

    public int getStrongPower(IWorldAccess worldIn, BlockPos pos, State state, Facing side)
    {
        return 0;
    }

    /**
     * Sets the block's bounds for rendering it as an item
     */
    public void setBlockBoundsForItemRender()
    {
    }

    public void harvestBlock(World worldIn, EntityNPC player, BlockPos pos, State state, TileEntity te)
    {
//        player.triggerAchievement(StatRegistry.mineBlockStatArray[BlockRegistry.getIdFromBlock(this)]);

        if (this.canSilkHarvest() && EnchantmentHelper.getSilkTouchModifier(player))
        {
            ItemStack itemstack = this.createStackedBlock(state);

            if (itemstack != null)
            {
                spawnAsEntity(worldIn, pos, itemstack);
            }
        }
        else
        {
            int i = EnchantmentHelper.getFortuneModifier(player);
            this.dropBlockAsItem(worldIn, pos, state, i);
        }
    }

    public boolean canSilkHarvest()
    {
        return this.isFullCube() && !this.hasTile;
    }

    public ItemStack createStackedBlock(State state)
    {
        int i = 0;
        Item item = ItemRegistry.getItemFromBlock(this);

        if (item != null && item.getHasSubtypes())
        {
            i = this.getMetaFromState(state);
        }

        return new ItemStack(item, 1, i);
    }

    /**
     * Get the quantity dropped based on the given fortune level
     */
    public int quantityDroppedWithBonus(int fortune, Random random)
    {
        return this.quantityDropped(random);
    }

    /**
     * Called by ItemBlocks after a block is set in the world, to allow post-place logic
     */
    public void onBlockPlacedBy(World worldIn, BlockPos pos, State state, EntityLiving placer, ItemStack stack)
    {
    }

    /**
     * Return true if an entity can be spawned inside the block (used to get the player's bed spawn location)
     */
    public boolean canSpawnInBlock()
    {
        return !this.material.isSolid() && !this.material.isLiquid();
    }

//    /**
//     * Gets the localized name of this block. Used for the statistics page.
//     */
//    public String getLocalizedName()
//    {
//        return Strs.get(this.getUnlocalizedName() + ".name");
//    }
    
    public final String getDisplay()
    {
        return this.display;
    }

    /**
     * Called on both Client and Server when World#addBlockEvent is called
     */
    public boolean onBlockEventReceived(World worldIn, BlockPos pos, State state, int eventID, int eventParam)
    {
        return false;
    }

//    /**
//     * Return the state of blocks statistics flags - if the block is counted for mined and placed.
//     */
//    public boolean getEnableStats()
//    {
//        return this.enableStats;
//    }

    public int getMobilityFlag()
    {
        return this.material.getMaterialMobility();
    }

    /**
     * Returns the default ambient occlusion value based on block opacity
     */
    public float getAmbientOcclusionLightValue()
    {
        return this.isBlockNormalCube() ? 0.2F : 1.0F;
    }

    /**
     * Block's chance to react to a living entity falling on it.
     */
    public void onFallenUpon(World worldIn, BlockPos pos, Entity entityIn, float fallDistance)
    {
        entityIn.fall(fallDistance, 1.0F);
    }

    /**
     * Called when an Entity lands on this Block. This method *must* update motionY because the entity will not do that
     * on its own
     */
    public void onLanded(World worldIn, Entity entityIn)
    {
        entityIn.motionY = 0.0D;
    }

    public Item getItem(World worldIn, BlockPos pos)
    {
        return ItemRegistry.getItemFromBlock(this);
    }

    /**
     * Gets the meta to use for the Pick Block ItemStack result
     */
    public int getDamageValue(World worldIn, BlockPos pos)
    {
        return this.damageDropped(worldIn.getState(pos));
    }

    /**
     * returns a list of blocks with the same ID, but different meta (eg: wood returns 4 blocks)
     */
    public void getSubBlocks(Item itemIn, CheatTab tab, List<ItemStack> list)
    {
        list.add(new ItemStack(itemIn, 1, 0));
    }

    /**
     * Returns the tab to display the given block on.
     */
    public CheatTab getTab()
    {
        return this.tab;
    }

    public void onBlockHarvested(World worldIn, BlockPos pos, State state, EntityNPC player)
    {
    }

    /**
     * Called similar to random ticks, but only when it is raining.
     */
    public void fillWithRain(World worldIn, BlockPos pos)
    {
    }
    
    public boolean isPickStrict()
    {
        return false;
    }

    public boolean requiresUpdates()
    {
        return true;
    }

    /**
     * Return whether this block can drop from an explosion.
     */
    public boolean canDropFromExplosion(Explosion explosionIn)
    {
        return true;
    }

    public boolean isAssociatedBlock(Block other)
    {
        return this == other;
    }

    public static boolean isEqualTo(Block blockIn, Block other)
    {
        return blockIn != null && other != null ? (blockIn == other ? true : blockIn.isAssociatedBlock(other)) : false;
    }

    public boolean hasComparatorInputOverride()
    {
        return false;
    }

    public int getComparatorInputOverride(World worldIn, BlockPos pos)
    {
        return 0;
    }

    /**
     * Possibly modify the given BlockState before rendering it on an Entity (Minecarts, Endermen, ...)
     */
    public State getStateForEntityRender(State state)
    {
        return state;
    }

    protected IProperty[] getProperties()
    {
        return new IProperty[0];
    }

    protected final void setDefaultState(State state)
    {
        this.defaultState = state;
    }

    public final State getState()
    {
        return this.defaultState;
    }
    
//    public EnumOffsetType getOffsetType()
//    {
//        return EnumOffsetType.NONE;
//    }

//    public String toString()
//    {
//        return "Block{" + BlockRegistry.REGISTRY.getNameForObject(this) + "}";
//    }
    
    public int getMiningLevel() {
    	return this.miningLevel;
    }
    
    public boolean canAxeHarvest() {
    	return this.axeHarvest;
    }
    
    public boolean canShovelHarvest() {
    	return this.shovelHarvest;
    }
    
    public int getShearsEfficiency() {
    	return this.shearsEfficiency;
    }
    
    public boolean isMagnetic() {
    	return false;
    }

    public Transforms getTransform() {
    	return Transforms.BLOCK;
    }

    public Model getModel(ModelProvider provider, String name, State state) {
    	return provider.getModel(name).add().all();
    }

    public IProperty<?>[] getIgnoredProperties() {
    	return null;
    }
    
    public void getAnimatedTextures(Map<String, Object> map) {
    }

    public boolean canKeepFire() {
        return false;
    }

    public void onDestroyedByFire(World world, BlockPos pos, State state) {
    }
}
