package common.block;

import java.util.List;

import common.collect.Lists;
import common.entity.Entity;
import common.entity.types.EntityLiving;
import common.init.Blocks;
import common.init.Config;
import common.init.SoundEvent;
import common.item.CheatTab;
import common.item.ItemStack;
import common.material.Material;
import common.model.Model;
import common.model.ModelProvider;
import common.model.ModelRotation;
import common.properties.IProperty;
import common.properties.PropertyBool;
import common.properties.PropertyDirection;
import common.tileentity.TileEntity;
import common.tileentity.TileEntityPiston;
import common.util.BlockPos;
import common.util.BoundingBox;
import common.util.ExtMath;
import common.util.Facing;
import common.world.IWorldAccess;
import common.world.State;
import common.world.World;
import common.world.AWorldServer;

public class BlockPistonBase extends Block
{
	private static class BlockPistonStructureHelper
	{
	    private final World world;
	    private final BlockPos pistonPos;
	    private final BlockPos blockToMove;
	    private final Facing moveDirection;
	    private final List<BlockPos> toMove = Lists.<BlockPos>newArrayList();
	    private final List<BlockPos> toDestroy = Lists.<BlockPos>newArrayList();

	    public BlockPistonStructureHelper(World worldIn, BlockPos posIn, Facing pistonFacing, boolean extending)
	    {
	        this.world = worldIn;
	        this.pistonPos = posIn;

	        if (extending)
	        {
	            this.moveDirection = pistonFacing;
	            this.blockToMove = posIn.offset(pistonFacing);
	        }
	        else
	        {
	            this.moveDirection = pistonFacing.getOpposite();
	            this.blockToMove = posIn.offset(pistonFacing, 2);
	        }
	    }

	    public boolean canMove()
	    {
	        this.toMove.clear();
	        this.toDestroy.clear();
	        Block block = this.world.getState(this.blockToMove).getBlock();

	        if (!BlockPistonBase.canPush(block, this.world, this.blockToMove, this.moveDirection, false))
	        {
	            if (block.getMobilityFlag() != 1)
	            {
	                return false;
	            }
	            else
	            {
	                this.toDestroy.add(this.blockToMove);
	                return true;
	            }
	        }
	        else if (!this.canPistonSlide(this.blockToMove))
	        {
	            return false;
	        }
	        else
	        {
	            for (int i = 0; i < this.toMove.size(); ++i)
	            {
	                BlockPos blockpos = (BlockPos)this.toMove.get(i);

	                if (this.world.getState(blockpos).getBlock() == Blocks.slime_block && !this.canSlide(blockpos))
	                {
	                    return false;
	                }
	            }

	            return true;
	        }
	    }

	    private boolean canPistonSlide(BlockPos origin)
	    {
	        Block block = this.world.getState(origin).getBlock();

	        if (block.getMaterial() == Material.air)
	        {
	            return true;
	        }
	        else if (!BlockPistonBase.canPush(block, this.world, origin, this.moveDirection, false))
	        {
	            return true;
	        }
	        else if (origin.equals(this.pistonPos))
	        {
	            return true;
	        }
	        else if (this.toMove.contains(origin))
	        {
	            return true;
	        }
	        else
	        {
	            int i = 1;

	            if (i + this.toMove.size() > Config.pistonLimit)
	            {
	                return false;
	            }
	            else
	            {
	                while (block == Blocks.slime_block)
	                {
	                    BlockPos blockpos = origin.offset(this.moveDirection.getOpposite(), i);
	                    block = this.world.getState(blockpos).getBlock();

	                    if (block.getMaterial() == Material.air || !BlockPistonBase.canPush(block, this.world, blockpos, this.moveDirection, false) || blockpos.equals(this.pistonPos))
	                    {
	                        break;
	                    }

	                    ++i;

	                    if (i + this.toMove.size() > Config.pistonLimit)
	                    {
	                        return false;
	                    }
	                }

	                int i1 = 0;

	                for (int j = i - 1; j >= 0; --j)
	                {
	                    this.toMove.add(origin.offset(this.moveDirection.getOpposite(), j));
	                    ++i1;
	                }

	                int j1 = 1;

	                while (true)
	                {
	                    BlockPos blockpos1 = origin.offset(this.moveDirection, j1);
	                    int k = this.toMove.indexOf(blockpos1);

	                    if (k > -1)
	                    {
	                        this.reorderMoveList(i1, k);

	                        for (int l = 0; l <= k + i1; ++l)
	                        {
	                            BlockPos blockpos2 = (BlockPos)this.toMove.get(l);

	                            if (this.world.getState(blockpos2).getBlock() == Blocks.slime_block && !this.canSlide(blockpos2))
	                            {
	                                return false;
	                            }
	                        }

	                        return true;
	                    }

	                    block = this.world.getState(blockpos1).getBlock();

	                    if (block.getMaterial() == Material.air)
	                    {
	                        return true;
	                    }

	                    if (!BlockPistonBase.canPush(block, this.world, blockpos1, this.moveDirection, true) || blockpos1.equals(this.pistonPos))
	                    {
	                        return false;
	                    }

	                    if (block.getMobilityFlag() == 1)
	                    {
	                        this.toDestroy.add(blockpos1);
	                        return true;
	                    }

	                    if (this.toMove.size() >= Config.pistonLimit)
	                    {
	                        return false;
	                    }

	                    this.toMove.add(blockpos1);
	                    ++i1;
	                    ++j1;
	                }
	            }
	        }
	    }

	    private void reorderMoveList(int splitA, int splitB)
	    {
	        List<BlockPos> list = Lists.<BlockPos>newArrayList();
	        List<BlockPos> list1 = Lists.<BlockPos>newArrayList();
	        List<BlockPos> list2 = Lists.<BlockPos>newArrayList();
	        list.addAll(this.toMove.subList(0, splitB));
	        list1.addAll(this.toMove.subList(this.toMove.size() - splitA, this.toMove.size()));
	        list2.addAll(this.toMove.subList(splitB, this.toMove.size() - splitA));
	        this.toMove.clear();
	        this.toMove.addAll(list);
	        this.toMove.addAll(list1);
	        this.toMove.addAll(list2);
	    }

	    private boolean canSlide(BlockPos pos)
	    {
	        for (Facing enumfacing : Facing.values())
	        {
	            if (enumfacing.getAxis() != this.moveDirection.getAxis() && !this.canPistonSlide(pos.offset(enumfacing)))
	            {
	                return false;
	            }
	        }

	        return true;
	    }

	    public List<BlockPos> getBlocksToMove()
	    {
	        return this.toMove;
	    }

	    public List<BlockPos> getBlocksToDestroy()
	    {
	        return this.toDestroy;
	    }
	}
	
    public static final PropertyDirection FACING = PropertyDirection.create("facing");
    public static final PropertyBool EXTENDED = PropertyBool.create("extended");

    /** This piston is the sticky one? */
    private final boolean isSticky;

    public BlockPistonBase(boolean isSticky)
    {
        super(Material.piston);
        this.setDefaultState(this.getBaseState().withProperty(FACING, Facing.NORTH).withProperty(EXTENDED, Boolean.valueOf(false)));
        this.isSticky = isSticky;
        this.setStepSound(SoundType.STONE);
        this.setHardness(0.5F);
        this.setTab(CheatTab.tabTech);
    }

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

    /**
     * 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)
    {
        worldIn.setState(pos, state.withProperty(FACING, getFacingFromEntity(worldIn, pos, placer)), 2);

        if (!worldIn.client)
        {
            this.checkForMove(worldIn, pos, state);
        }
    }

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

    public void onBlockAdded(AWorldServer worldIn, BlockPos pos, State state)
    {
        if (!worldIn.client && worldIn.getTileEntity(pos) == null)
        {
            this.checkForMove(worldIn, pos, state);
        }
    }

    /**
     * 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.getState().withProperty(FACING, getFacingFromEntity(worldIn, pos, placer)).withProperty(EXTENDED, Boolean.valueOf(false));
    }

    private void checkForMove(World worldIn, BlockPos pos, State state)
    {
        Facing enumfacing = (Facing)state.getValue(FACING);
        boolean flag = this.shouldBeExtended(worldIn, pos, enumfacing);

        if (flag && !((Boolean)state.getValue(EXTENDED)).booleanValue())
        {
            if ((new BlockPistonStructureHelper(worldIn, pos, enumfacing, true)).canMove())
            {
                worldIn.addBlockEvent(pos, this, 0, enumfacing.getIndex());
            }
        }
        else if (!flag && ((Boolean)state.getValue(EXTENDED)).booleanValue())
        {
            worldIn.setState(pos, state.withProperty(EXTENDED, Boolean.valueOf(false)), 2);
            worldIn.addBlockEvent(pos, this, 1, enumfacing.getIndex());
        }
    }

    private boolean shouldBeExtended(World worldIn, BlockPos pos, Facing facing)
    {
        for (Facing enumfacing : Facing.values())
        {
            if (enumfacing != facing && worldIn.isSidePowered(pos.offset(enumfacing), enumfacing))
            {
                return true;
            }
        }

        if (worldIn.isSidePowered(pos, Facing.DOWN))
        {
            return true;
        }
        else
        {
            BlockPos blockpos = pos.up();

            for (Facing enumfacing1 : Facing.values())
            {
                if (enumfacing1 != Facing.DOWN && worldIn.isSidePowered(blockpos.offset(enumfacing1), enumfacing1))
                {
                    return true;
                }
            }

            return false;
        }
    }

    /**
     * Called on both Client and Server when World#addBlockEvent is called
     */
    public boolean onBlockEventReceived(World worldIn, BlockPos pos, State state, int eventID, int eventParam)
    {
        Facing enumfacing = (Facing)state.getValue(FACING);

        if (!worldIn.client)
        {
            boolean flag = this.shouldBeExtended(worldIn, pos, enumfacing);

            if (flag && eventID == 1)
            {
                worldIn.setState(pos, state.withProperty(EXTENDED, Boolean.valueOf(true)), 2);
                return false;
            }

            if (!flag && eventID == 0)
            {
                return false;
            }
        }

        if (eventID == 0)
        {
            if (!this.doMove(worldIn, pos, enumfacing, true))
            {
                return false;
            }

            worldIn.setState(pos, state.withProperty(EXTENDED, Boolean.valueOf(true)), 2);
            worldIn.playSound(SoundEvent.PISTON_OUT, (double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, 0.5F);
        }
        else if (eventID == 1)
        {
            TileEntity tileentity1 = worldIn.getTileEntity(pos.offset(enumfacing));

            if (tileentity1 instanceof TileEntityPiston)
            {
                ((TileEntityPiston)tileentity1).clearPistonTileEntity();
            }

            worldIn.setState(pos, Blocks.piston_extension.getState().withProperty(BlockPistonMoving.FACING, enumfacing).withProperty(BlockPistonMoving.TYPE, this.isSticky ? BlockPistonHead.EnumPistonType.STICKY : BlockPistonHead.EnumPistonType.DEFAULT), 3);
            worldIn.setTileEntity(pos, BlockPistonMoving.newTileEntity(this.getStateFromMeta(eventParam), enumfacing, false, true));

            if (this.isSticky)
            {
                BlockPos blockpos = pos.add(enumfacing.getFrontOffsetX() * 2, enumfacing.getFrontOffsetY() * 2, enumfacing.getFrontOffsetZ() * 2);
                Block block = worldIn.getState(blockpos).getBlock();
                boolean flag1 = false;

                if (block == Blocks.piston_extension)
                {
                    TileEntity tileentity = worldIn.getTileEntity(blockpos);

                    if (tileentity instanceof TileEntityPiston)
                    {
                        TileEntityPiston tileentitypiston = (TileEntityPiston)tileentity;

                        if (tileentitypiston.getFacing() == enumfacing && tileentitypiston.isExtending())
                        {
                            tileentitypiston.clearPistonTileEntity();
                            flag1 = true;
                        }
                    }
                }

                if (!flag1 && block.getMaterial() != Material.air && canPush(block, worldIn, blockpos, enumfacing.getOpposite(), false) && (block.getMobilityFlag() == 0 || block == Blocks.piston || block == Blocks.sticky_piston))
                {
                    this.doMove(worldIn, pos, enumfacing, false);
                }
            }
            else
            {
                worldIn.setBlockToAir(pos.offset(enumfacing));
            }

            worldIn.playSound(SoundEvent.PISTON_IN, (double)pos.getX() + 0.5D, (double)pos.getY() + 0.5D, (double)pos.getZ() + 0.5D, 0.5F);
        }

        return true;
    }

    public void setBlockBoundsBasedOnState(IWorldAccess worldIn, BlockPos pos)
    {
        State iblockstate = worldIn.getState(pos);

        if (iblockstate.getBlock() == this && ((Boolean)iblockstate.getValue(EXTENDED)).booleanValue())
        {
            float f = 0.25F;
            Facing enumfacing = (Facing)iblockstate.getValue(FACING);

            if (enumfacing != null)
            {
                switch (enumfacing)
                {
                    case DOWN:
                        this.setBlockBounds(0.0F, 0.25F, 0.0F, 1.0F, 1.0F, 1.0F);
                        break;

                    case UP:
                        this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 0.75F, 1.0F);
                        break;

                    case NORTH:
                        this.setBlockBounds(0.0F, 0.0F, 0.25F, 1.0F, 1.0F, 1.0F);
                        break;

                    case SOUTH:
                        this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 0.75F);
                        break;

                    case WEST:
                        this.setBlockBounds(0.25F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
                        break;

                    case EAST:
                        this.setBlockBounds(0.0F, 0.0F, 0.0F, 0.75F, 1.0F, 1.0F);
                }
            }
        }
        else
        {
            this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
        }
    }

    /**
     * Sets the block's bounds for rendering it as an item
     */
    public void setBlockBoundsForItemRender()
    {
        this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
    }

    /**
     * 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)
    {
        this.setBlockBounds(0.0F, 0.0F, 0.0F, 1.0F, 1.0F, 1.0F);
        super.addCollisionBoxesToList(worldIn, pos, state, mask, list, collidingEntity);
    }

    public BoundingBox getCollisionBoundingBox(World worldIn, BlockPos pos, State state)
    {
        this.setBlockBoundsBasedOnState(worldIn, pos);
        return super.getCollisionBoundingBox(worldIn, pos, state);
    }

    public boolean isFullCube()
    {
        return false;
    }

    public static Facing getFacing(int meta)
    {
        int i = meta & 7;
        return i > 5 ? Facing.getFront(0) : Facing.getFront(i);
    }

    public static Facing getFacingFromEntity(World worldIn, BlockPos clickedBlock, EntityLiving entityIn)
    {
        if (ExtMath.absf((float)entityIn.posX - (float)clickedBlock.getX()) < 2.0F && ExtMath.absf((float)entityIn.posZ - (float)clickedBlock.getZ()) < 2.0F)
        {
            double d0 = entityIn.posY + (double)entityIn.getEyeHeight();

            if (d0 - (double)clickedBlock.getY() > 2.0D)
            {
                return Facing.UP;
            }

            if ((double)clickedBlock.getY() - d0 > 0.0D)
            {
                return Facing.DOWN;
            }
        }

        return entityIn.getHorizontalFacing().getOpposite();
    }

    public static boolean canPush(Block blockIn, World worldIn, BlockPos pos, Facing direction, boolean allowDestroy)
    {
        if (blockIn == Blocks.obsidian)
        {
            return false;
        }
        else if (!World.isValidXZ(pos))
        {
            return false;
        }
        else if (pos.getY() >= 0 && (direction != Facing.DOWN || pos.getY() != 0))
        {
            if (pos.getY() <= World.HEIGHT - 1 && (direction != Facing.UP || pos.getY() != World.HEIGHT - 1))
            {
                if (blockIn != Blocks.piston && blockIn != Blocks.sticky_piston)
                {
                    if (blockIn.getBlockHardness(worldIn, pos) == -1.0F)
                    {
                        return false;
                    }

                    if (blockIn.getMobilityFlag() == 2)
                    {
                        return false;
                    }

                    if (blockIn.getMobilityFlag() == 1)
                    {
                        if (!allowDestroy)
                        {
                            return false;
                        }

                        return true;
                    }
                }
                else if (((Boolean)worldIn.getState(pos).getValue(EXTENDED)).booleanValue())
                {
                    return false;
                }

                return !(blockIn instanceof ITileEntityProvider);
            }
            else
            {
                return false;
            }
        }
        else
        {
            return false;
        }
    }

    private boolean doMove(World worldIn, BlockPos pos, Facing direction, boolean extending)
    {
        if (!extending)
        {
            worldIn.setBlockToAir(pos.offset(direction));
        }

        BlockPistonStructureHelper blockpistonstructurehelper = new BlockPistonStructureHelper(worldIn, pos, direction, extending);
        List<BlockPos> list = blockpistonstructurehelper.getBlocksToMove();
        List<BlockPos> list1 = blockpistonstructurehelper.getBlocksToDestroy();

        if (!blockpistonstructurehelper.canMove())
        {
            return false;
        }
        else
        {
            int i = list.size() + list1.size();
            Block[] ablock = new Block[i];
            Facing enumfacing = extending ? direction : direction.getOpposite();

            for (int j = list1.size() - 1; j >= 0; --j)
            {
                BlockPos blockpos = (BlockPos)list1.get(j);
                Block block = worldIn.getState(blockpos).getBlock();
                block.dropBlockAsItem(worldIn, blockpos, worldIn.getState(blockpos), 0);
                worldIn.setBlockToAir(blockpos);
                --i;
                ablock[i] = block;
            }

            for (int k = list.size() - 1; k >= 0; --k)
            {
                BlockPos blockpos2 = (BlockPos)list.get(k);
                State iblockstate = worldIn.getState(blockpos2);
                Block block1 = iblockstate.getBlock();
                block1.getMetaFromState(iblockstate);
                worldIn.setBlockToAir(blockpos2);
                blockpos2 = blockpos2.offset(enumfacing);
                worldIn.setState(blockpos2, Blocks.piston_extension.getState().withProperty(FACING, direction), 4);
                worldIn.setTileEntity(blockpos2, BlockPistonMoving.newTileEntity(iblockstate, direction, extending, false));
                --i;
                ablock[i] = block1;
            }

            BlockPos blockpos1 = pos.offset(direction);

            if (extending)
            {
                BlockPistonHead.EnumPistonType blockpistonextension$enumpistontype = this.isSticky ? BlockPistonHead.EnumPistonType.STICKY : BlockPistonHead.EnumPistonType.DEFAULT;
                State iblockstate1 = Blocks.piston_head.getState().withProperty(BlockPistonHead.FACING, direction).withProperty(BlockPistonHead.TYPE, blockpistonextension$enumpistontype);
                State iblockstate2 = Blocks.piston_extension.getState().withProperty(BlockPistonMoving.FACING, direction).withProperty(BlockPistonMoving.TYPE, this.isSticky ? BlockPistonHead.EnumPistonType.STICKY : BlockPistonHead.EnumPistonType.DEFAULT);
                worldIn.setState(blockpos1, iblockstate2, 4);
                worldIn.setTileEntity(blockpos1, BlockPistonMoving.newTileEntity(iblockstate1, direction, true, false));
            }

            for (int l = list1.size() - 1; l >= 0; --l)
            {
                worldIn.notifyNeighborsOfStateChange((BlockPos)list1.get(l), ablock[i++]);
            }

            for (int i1 = list.size() - 1; i1 >= 0; --i1)
            {
                worldIn.notifyNeighborsOfStateChange((BlockPos)list.get(i1), ablock[i++]);
            }

            if (extending)
            {
                worldIn.notifyNeighborsOfStateChange(blockpos1, Blocks.piston_head);
                worldIn.notifyNeighborsOfStateChange(pos, this);
            }

            return true;
        }
    }

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

    /**
     * Convert the given metadata into a BlockState for this Block
     */
    public State getStateFromMeta(int meta)
    {
        return this.getState().withProperty(FACING, getFacing(meta)).withProperty(EXTENDED, Boolean.valueOf((meta & 8) > 0));
    }

    /**
     * Convert the BlockState into the correct metadata value
     */
    public int getMetaFromState(State state)
    {
        int i = 0;
        i = i | ((Facing)state.getValue(FACING)).getIndex();

        if (((Boolean)state.getValue(EXTENDED)).booleanValue())
        {
            i |= 8;
        }

        return i;
    }

    protected IProperty[] getProperties()
    {
        return new IProperty[] {FACING, EXTENDED};
    }

    public Model getModel(ModelProvider provider, String name, State state) {
    	return (state.getValue(EXTENDED) ? provider.getModel("piston_side")
			.add(0, 0, 4, 16, 16, 16)
				.d().uv(0, 4, 16, 16).rot(180)
				.u().uv(0, 4, 16, 16)
				.n("piston_inner").uv(0, 0, 16, 16).noCull()
				.s("piston_bottom").uv(0, 0, 16, 16)
				.w().uv(0, 4, 16, 16).rot(270)
				.e().uv(0, 4, 16, 16).rot(90) : provider.getModel("piston_side")
			.add(0, 0, 0, 16, 16, 16)
				.d().uv(0, 0, 16, 16).rot(180)
				.u().uv(0, 0, 16, 16)
				.n(this.isSticky ? "piston_top_sticky" : "piston_top").uv(0, 0, 16, 16)
				.s("piston_bottom").uv(0, 0, 16, 16)
				.w().uv(0, 0, 16, 16).rot(270)
				.e().uv(0, 0, 16, 16).rot(90))
    			.rotate(ModelRotation.getNorthRot(state.getValue(FACING).getAxis() == Facing.Axis.Y 
    				? state.getValue(FACING).getOpposite() : state.getValue(FACING)));
    }
}
