package client.util;

import client.Client;
import client.network.ClientPlayer;
import common.block.Block;
import common.entity.Entity;
import common.entity.npc.EntityNPC;
import common.init.BlockRegistry;
import common.init.Blocks;
import common.init.EntityRegistry;
import common.item.ItemControl;
import common.item.ItemStack;
import common.packet.CPacketAction;
import common.packet.CPacketBreak;
import common.packet.CPacketClick;
import common.packet.CPacketPlace;
import common.sound.PositionedSound;
import common.util.BlockPos;
import common.util.Facing;
import common.util.Vec3;
import common.world.State;
import common.world.World;

public class PlayerController {
	private final Client gm;
	private final ClientPlayer handler;
	
	private BlockPos position = new BlockPos(-1, -World.MAX_SIZE_Y - 1, -1);
	private ItemStack stack;
	private float damage;
	private float stepCounter;
	private int delay;
	private boolean hitting;
	private int lastSelected;
	private boolean interacting;
    private int itemUseCooldown;
    private int itemUseCooldownMax;

	public PlayerController(Client gm, ClientPlayer handler) {
		this.gm = gm;
		this.handler = handler;
	}

	public boolean destroyBlock(BlockPos pos, Facing side) {
		World world = this.gm.world;
		State state = world.getState(pos);
		Block block = state.getBlock();

		if(block == Blocks.air) {
			return false;
		}
		else {
			world.playEffect(2001, pos, BlockRegistry.getId(state));
			boolean flag = world.setBlockToAir(pos);

			if(flag) {
				block.onBroken(world, pos, state);
			}

			this.position = new BlockPos(this.position.getX(), -1, this.position.getZ());

			ItemStack stack = this.gm.player.getHeldItem();

			if(stack != null) {
				stack.getItem().onBlockDestroyed(stack, world, block, pos, this.gm.player);

				if(stack.isEmpty()) {
					this.gm.player.setHeldItem(null);
				}
			}

			return flag;
		}
	}

	public boolean clickBlock(BlockPos pos, Facing face) {
		if(!World.isValidXZ(pos)) {
			return false;
		}
		else {
			ItemStack stack = this.gm.player.getHeldItem();
			if(stack != null && stack.getItem().onAction(stack, this.gm.player, this.gm.world, ItemControl.PRIMARY, pos)) {
				this.interacting = true;
				this.handler.addToSendQueue(new CPacketBreak(CPacketBreak.Action.START_DESTROY_BLOCK, pos, face));
        		if(this.gm.player.getHeldItem() != null && this.gm.player.getHeldItem().isEmpty())
        			this.gm.player.setHeldItem(null);
				return true;
			}
			if(!this.hitting || !this.isHitting(pos)) {
				if(this.hitting) {
					this.handler.addToSendQueue(new CPacketBreak(CPacketBreak.Action.ABORT_DESTROY_BLOCK, this.position, face));
				}

				this.handler.addToSendQueue(new CPacketBreak(CPacketBreak.Action.START_DESTROY_BLOCK, pos, face));
				Block block = this.gm.world.getState(pos).getBlock();
				boolean flag = block != Blocks.air;

				if(flag && this.damage == 0.0F) {
					block.onStartBreak(this.gm.world, pos, this.gm.player);
				}

				if(flag && block.getHardness(this.gm.player, this.gm.player.worldObj, pos) >= 1.0F) {
					this.destroyBlock(pos, face);
					this.delay = 3;
				}
				else {
					this.hitting = true;
					this.position = pos;
					this.stack = this.gm.player.getHeldItem();
					this.damage = 0.0F;
					this.stepCounter = 0.0F;
					this.gm.renderer.setBreakProgress(this.gm.player.getId(), this.position, (int)(this.damage * 10.0F) - 1);
				}
			}

			return true;
		}
	}

	public void resetProgress() {
		if(this.hitting) {
			this.handler.addToSendQueue(new CPacketBreak(CPacketBreak.Action.ABORT_DESTROY_BLOCK, this.position, Facing.DOWN));
			this.hitting = false;
			this.damage = 0.0F;
			this.gm.renderer.setBreakProgress(this.gm.player.getId(), this.position, -1);
		}
	}

	public void resetInteraction() {
		this.interacting = false;
	}

	public boolean damageBlock(BlockPos pos, Facing face) {
		if(this.interacting)
			return false;
		this.syncItem();

		if(this.delay > 0) {
			--this.delay;
			return true;
		}
		else if(this.isHitting(pos)) {
			Block block = this.gm.world.getState(pos).getBlock();

			if(block == Blocks.air) {
				this.hitting = false;
				return false;
			}
			else {
				this.damage += block.getHardness(this.gm.player, this.gm.player.worldObj, pos);

				if(this.stepCounter % 4.0F == 0.0F && block.getSound().getStepSound() != null) {
					this.gm.getSoundManager().playSound(new PositionedSound(block.getSound().getStepSound(), 0.25F,
							(float)pos.getX() + 0.5F, (float)pos.getY() + 0.5F, (float)pos.getZ() + 0.5F));
				}

				++this.stepCounter;

				if(this.damage >= 1.0F) {
					this.hitting = false;
					this.handler.addToSendQueue(new CPacketBreak(CPacketBreak.Action.STOP_DESTROY_BLOCK, pos, face));
					this.destroyBlock(pos, face);
					this.damage = 0.0F;
					this.stepCounter = 0.0F;
					this.delay = 5;
				}

				this.gm.renderer.setBreakProgress(this.gm.player.getId(), this.position, (int)(this.damage * 10.0F) - 1);
				return true;
			}
		}
		else {
			return this.clickBlock(pos, face);
		}
	}

	public void update() {
		this.syncItem();
    	if(this.itemUseCooldown > 0)
    		--this.itemUseCooldown;

		if(this.handler.getConnection().isChannelOpen()) {
			this.handler.getConnection().processReceivedPackets();
		}
		else {
			this.handler.getConnection().checkDisconnected();
		}
	}

	private boolean isHitting(BlockPos pos) {
		ItemStack stack = this.gm.player.getHeldItem();
		boolean flag = this.stack == null && stack == null;

		if(this.stack != null && stack != null) {
			flag = stack.getItem() == this.stack.getItem()
					&& ItemStack.dataEquals(stack, this.stack);
		}

		return pos.equals(this.position) && flag;
	}

	private void syncItem() {
		int slot = this.gm.player.getSelectedIndex();

		if(slot != this.lastSelected) {
			this.lastSelected = slot;
			this.handler.addToSendQueue(new CPacketAction(CPacketAction.Action.SET_ITEMSLOT, this.lastSelected));
		}
	}

	public boolean clickRight(EntityNPC player, World world, ItemStack stack, BlockPos pos, Facing side, Vec3 hit) {
		this.syncItem();
		float f = (float)(hit.xCoord - (double)pos.getX());
		float f1 = (float)(hit.yCoord - (double)pos.getY());
		float f2 = (float)(hit.zCoord - (double)pos.getZ());
		boolean flag = false;

		if(!World.isValidXZ(pos)) {
			return false;
		}
		else {
			if(stack == null || !stack.getItem().onAction(stack, player, world, ItemControl.SECONDARY, pos)) {
				State iblockstate = world.getState(pos);

				if((!player.isSneaking() || player.getHeldItem() == null)
						&& iblockstate.getBlock().onUse(world, pos, iblockstate, player, side, f, f1, f2)) {
					flag = true;
				}

				if(!flag && stack != null && stack.getItem().getBlock() != null && !stack.getItem().getBlock().checkPlace(world, pos, side, player, stack))
					return false;
			}
			else {
				stack.getItem().onItemUse(stack, player, world, pos, side, f, f1, f2);
				flag = true;
			}

			this.handler.addToSendQueue(new CPacketPlace(pos, side.getIndex(), player.getHeldItem(), f, f1, f2));

			if(!flag) {
				if(stack == null) {
					return false;
				}
				else {
					return stack.getItem().onItemUse(stack, player, world, pos, side, f, f1, f2);
				}
			}
			else {
				return true;
			}
		}
	}

	public boolean sendUseItem(EntityNPC player, World world, ItemStack stack) {
		this.syncItem();
		if(this.itemUseCooldown > 0)
			return false;
		this.handler.addToSendQueue(new CPacketPlace(player.getHeldItem()));
		int size = stack.getSize();
		this.itemUseCooldown = this.itemUseCooldownMax = stack.getItem().getUseCooldown(stack, world, player);
		ItemStack changed = stack.getItem().onItemRightClick(stack, world, player);

		if(changed != stack || changed != null && changed.getSize() != size) {
			player.setHeldItem(changed);

			if(changed.isEmpty()) {
				player.setHeldItem(null);
			}

			return true;
		}
		else {
			return false;
		}
	}

	public EntityNPC createPlayerEntity(World world, int type) {
		EntityNPC player = (EntityNPC)EntityRegistry.createEntityByID(type, world);
		player.setClientPlayer(this.handler);
		return player;
	}

	public void attackEntity(EntityNPC player, Entity target) {
		this.syncItem();
		this.handler.addToSendQueue(new CPacketAction(CPacketAction.Action.ATTACK, target.getId()));
		player.attackTargetEntityWithCurrentItem(target);
	}

	public boolean interact(EntityNPC player, Entity target) {
		this.syncItem();
		this.handler.addToSendQueue(new CPacketAction(CPacketAction.Action.INTERACT, target.getId()));
		return player.interactWith(target);
	}

	public ItemStack windowClick(int window, int slot, int button, int mode, EntityNPC player) {
		short id = player.openContainer.getNextTransactionID(player);
		ItemStack stack = player.openContainer.slotClick(slot, button, mode, player);
		this.handler.addToSendQueue(new CPacketClick(window, slot, button, mode, stack, id));
		return stack;
	}

	public void sendEnchantPacket(int window, int button) {
		this.handler.addToSendQueue(new CPacketAction(CPacketAction.Action.ENCHANT_ITEM, window | (button << 8)));
	}

	public void stopUsing(EntityNPC player) {
		this.syncItem();
		this.handler.addToSendQueue(new CPacketBreak(CPacketBreak.Action.RELEASE_USE_ITEM, BlockPos.ORIGIN, Facing.DOWN));
		player.stopUsingItem();
	}

	public boolean isHittingBlock() {
		return this.hitting;
	}

	public int getUseCooldown() {
		return this.itemUseCooldown;
	}

	public int getUseCooldownMax() {
		return this.itemUseCooldownMax;
	}

	public void resetUseCooldown() {
		this.itemUseCooldown = 0;
	}
	
	public float getDamage(BlockPos pos) {
		return this.hitting && pos.equals(this.position) ? this.damage : 0.0f;
	}
}
