/**
    Copyright (C) <2015> <coolAlias>

    This file is part of coolAlias' Zelda Sword Skills Minecraft Mod; as such,
    you can redistribute it and/or modify it under the terms of the GNU
    General Public License as published by the Free Software Foundation,
    either version 3 of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package zeldaswordskills.handler;

import java.util.HashMap;
import java.util.Map;

import net.minecraft.block.Block;
import net.minecraft.block.BlockBreakable;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.boss.IBossDisplayData;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.monster.EntityBlaze;
import net.minecraft.entity.monster.EntityCaveSpider;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.monster.EntityEnderman;
import net.minecraft.entity.monster.EntityGhast;
import net.minecraft.entity.monster.EntityIronGolem;
import net.minecraft.entity.monster.EntityMagmaCube;
import net.minecraft.entity.monster.EntityPigZombie;
import net.minecraft.entity.monster.EntitySilverfish;
import net.minecraft.entity.monster.EntitySkeleton;
import net.minecraft.entity.monster.EntitySpider;
import net.minecraft.entity.monster.EntityWitch;
import net.minecraft.entity.monster.EntityZombie;
import net.minecraft.entity.monster.IMob;
import net.minecraft.entity.passive.EntityHorse;
import net.minecraft.entity.passive.EntityOcelot;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemEnchantedBook;
import net.minecraft.item.ItemStack;
import net.minecraft.util.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.MathHelper;
import net.minecraft.world.World;
import net.minecraftforge.event.AnvilUpdateEvent;
import net.minecraftforge.event.entity.item.ItemTossEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.BlockEvent.HarvestDropsEvent;
import net.minecraftforge.fml.common.FMLCommonHandler;
import net.minecraftforge.fml.common.eventhandler.Event.Result;
import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
import zeldaswordskills.ZSSAchievements;
import zeldaswordskills.api.block.BlockWeight;
import zeldaswordskills.api.block.ILiftable;
import zeldaswordskills.api.block.ISmashable;
import zeldaswordskills.api.item.ArmorIndex;
import zeldaswordskills.api.item.IFairyUpgrade;
import zeldaswordskills.api.item.IHandlePickup;
import zeldaswordskills.api.item.IHandleToss;
import zeldaswordskills.api.item.ILiftBlock;
import zeldaswordskills.api.item.ISmashBlock;
import zeldaswordskills.api.item.IUnenchantable;
import zeldaswordskills.block.tileentity.TileEntityDungeonCore;
import zeldaswordskills.entity.mobs.EntityDarknut;
import zeldaswordskills.entity.mobs.EntityKeese;
import zeldaswordskills.entity.mobs.EntityOctorok;
import zeldaswordskills.entity.mobs.EntityWizzrobe;
import zeldaswordskills.entity.player.ZSSPlayerInfo;
import zeldaswordskills.entity.player.ZSSPlayerSkills;
import zeldaswordskills.item.ItemHeldBlock;
import zeldaswordskills.item.ZSSItems;
import zeldaswordskills.network.PacketDispatcher;
import zeldaswordskills.network.client.UnpressKeyPacket;
import zeldaswordskills.network.server.HeldBlockColorPacket;
import zeldaswordskills.ref.Config;
import zeldaswordskills.ref.Sounds;
import zeldaswordskills.skills.SkillBase;
import zeldaswordskills.util.PlayerUtils;
import zeldaswordskills.util.WorldUtils;

/**
 * 
 * Event Handler for various item- and block-related events
 *
 */
public class ZSSItemEvents
{
	/** Mapping of mobs to skill orb drops */
	private static final Map<Class<? extends EntityLivingBase>, ItemStack> dropsList = new HashMap<Class<? extends EntityLivingBase>, ItemStack>();

	public ZSSItemEvents() {
		if (dropsList.isEmpty()) {
			ZSSItemEvents.init();
		}
	}

	/** Adds a mob-class to skill orb mapping */
	private static void addDrop(Class<? extends EntityLivingBase> mobClass, SkillBase skill) {
		ItemStack stack = new ItemStack(ZSSItems.skillOrb, 1, skill.getId());
		dropsList.put(mobClass, stack);
	}

	/**
	 * Returns the type of skill orb that the mob will drop this time;
	 * this is not always the same as the stack stored in dropsList
	 */
	private static ItemStack getOrbDrop(EntityLivingBase mob, boolean isBoss) {
		if (dropsList.get(mob.getClass()) != null && mob.worldObj.rand.nextFloat() > Config.getChanceForRandomDrop()) {
			return dropsList.get(mob.getClass());
		} else {
			ItemStack orb = null;
			int id = mob.worldObj.rand.nextInt(SkillBase.getNumSkills());
			if (SkillBase.doesSkillExist(id) && SkillBase.getSkill(id).canDrop()) {
				if (dropsList.get(mob.getClass()) != null || isBoss || mob.worldObj.rand.nextFloat() < Config.getRandomMobDropChance()) {
					orb = (id == SkillBase.bonusHeart.getId() ? new ItemStack(ZSSItems.heartPiece) : new ItemStack(ZSSItems.skillOrb, 1, id));
				}
			}
			return orb;
		}
	}

	@SubscribeEvent
	public void onLivingDrops(LivingDropsEvent event) {
		if (event.source.getEntity() instanceof EntityPlayer) {
			EntityPlayer player = (EntityPlayer) event.source.getEntity();
			EntityLivingBase mob = event.entityLiving;
			boolean isBoss = mob instanceof IBossDisplayData;
			boolean flag = ZSSPlayerSkills.get(player).getSkillLevel(SkillBase.mortalDraw) == SkillBase.mortalDraw.getMaxLevel();
			ItemStack orb = (isBoss && !flag ? new ItemStack(ZSSItems.skillOrb,1,SkillBase.mortalDraw.getId()) : getOrbDrop(mob, isBoss));
			if (orb != null && Config.areOrbDropsEnabled()) {
				ItemStack helm = (player).getCurrentArmor(ArmorIndex.WORN_HELM);
				float f = (helm != null && helm.getItem() == ZSSItems.maskTruth ? 0.01F : 0.0F); 
				float baseChance = Config.getDropChance(orb.getItem() == ZSSItems.heartPiece ? SkillBase.bonusHeart.getId() : orb.getItemDamage());
				if (baseChance > 0.0F && (isBoss || mob.worldObj.rand.nextFloat() < (baseChance + f + (0.005F * event.lootingLevel)))) {
					event.drops.add(new EntityItem(mob.worldObj, mob.posX, mob.posY, mob.posZ, orb.copy()));
					mob.worldObj.playSoundEffect(mob.posX, mob.posY, mob.posZ, Sounds.SPECIAL_DROP, 1.0F, 1.0F);
					player.triggerAchievement(ZSSAchievements.skillGain);
					if (isBoss) {
						player.triggerAchievement(ZSSAchievements.skillMortal);
					}
				}
			}
			if (mob instanceof EntityCreeper && mob.worldObj.rand.nextFloat() < Config.getCreeperDropChance()) {
				event.drops.add(new EntityItem(mob.worldObj, mob.posX, mob.posY, mob.posZ, new ItemStack(ZSSItems.bomb)));
			}
			if (mob instanceof IMob && mob.worldObj.rand.nextInt(Config.getPowerDropRate()) == 0) {
				event.drops.add(new EntityItem(mob.worldObj, mob.posX, mob.posY, mob.posZ, new ItemStack(ZSSItems.powerPiece)));
			}
			// Check for heart and magic jar drops
			if (mob instanceof IMob) {
				// High-HP mobs have a better chance of dropping a Large Magic Jar;
				// as a base line, Dark Nuts (50 HP) should have a 5-10% drop rate (caps at 25%)
				float hp = mob.getMaxHealth();
				float chance = MathHelper.clamp_float(((hp - 40F) / 100F), 0F, 0.25F);
				if (hp > 100) { // add 5% per additional 100 HP to a max of 50% for a 500+ HP critter
					chance = MathHelper.clamp_float(chance + (hp - 100F) / 2000F, 0F, 0.5F);
				}
				if (mob.worldObj.rand.nextFloat() < chance) {
					event.drops.add(new EntityItem(mob.worldObj, mob.posX, mob.posY, mob.posZ, new ItemStack(ZSSItems.magicJarBig)));
				}
				int consumable_chance = Config.getMobConsumableFrequency();
				if (consumable_chance > 0 && mob.worldObj.rand.nextInt((event.drops.size() + 1) * (12 - consumable_chance)) == 0) {
					ItemStack stack = new ItemStack(mob.worldObj.rand.nextInt(4) == 0 ? ZSSItems.magicJar : ZSSItems.smallHeart);
					event.drops.add(new EntityItem(mob.worldObj, mob.posX, mob.posY, mob.posZ, stack));
				}
			}
		}
	}

	@SubscribeEvent
	public void onAnvilUpdate(AnvilUpdateEvent event) {
		// Don't allow unenchantable items to be enchanted in the anvil:
		boolean left = (event.left.getItem() instanceof ItemEnchantedBook && event.right.getItem().getItemEnchantability() < 1 && (Config.areUnenchantablesDisabled() || event.right.getItem() instanceof IUnenchantable));
		boolean right = (event.right.getItem() instanceof ItemEnchantedBook && event.left.getItem().getItemEnchantability() < 1 && (Config.areUnenchantablesDisabled() || event.left.getItem() instanceof IUnenchantable));
		event.setCanceled(left || right);
	}

	@SubscribeEvent
	public void onBlockHarvest(HarvestDropsEvent event) {
		if (event.harvester != null) {
			Block block = event.state.getBlock();
			if (block == Blocks.dirt || block == Blocks.grass) {
				if (event.harvester.getCurrentArmor(ArmorIndex.WORN_HELM) != null && event.harvester.getCurrentArmor(ArmorIndex.WORN_HELM).getItem() == ZSSItems.maskScents && event.world.rand.nextInt(32) == 0) {
					event.drops.add(event.world.rand.nextInt(4) == 0 ? new ItemStack(Blocks.red_mushroom) : new ItemStack(Blocks.brown_mushroom));
				}
			} else if (block == Blocks.tallgrass) {
				if (PlayerUtils.isSword(event.harvester.getHeldItem()) && event.world.rand.nextFloat() < Config.getGrassDropChance()) {
					event.drops.add(ZSSItems.getRandomGrassDrop(event.world.rand));
				}
			} else if (block == Blocks.iron_ore) {
				if (event.world.rand.nextFloat() < (0.005F * event.fortuneLevel)) {
					event.drops.add(new ItemStack(ZSSItems.masterOre));
					event.harvester.worldObj.playSoundEffect(event.harvester.posX, event.harvester.posY, event.harvester.posZ, Sounds.SPECIAL_DROP, 1.0F, 1.0F);
				}
			}
		}
	}

	@SubscribeEvent
	public void onItemToss(ItemTossEvent event) {
		EntityItem item = event.entityItem;
		ItemStack stack = item.getEntityItem();
		if (stack != null) {
			if (stack.getItem() instanceof IHandleToss) {
				((IHandleToss) stack.getItem()).onItemTossed(item, event.player);
			}
			if (!item.isDead && (stack.getItem() == Items.emerald || (stack.getItem() instanceof IFairyUpgrade)
					&& ((IFairyUpgrade) stack.getItem()).hasFairyUpgrade(stack))) {
				TileEntityDungeonCore core = WorldUtils.getNearbyFairySpawner(item.worldObj, item.posX, item.posY, item.posZ, true);
				if (core != null) {
					core.scheduleItemUpdate(event.player);
				}
			}
		}
		event.setCanceled(item.isDead);
	}

	@SubscribeEvent
	public void onItemPickup(EntityItemPickupEvent event) {
		ItemStack stack = event.item.getEntityItem();
		EntityPlayer player = event.entityPlayer;
		if (stack != null && stack.getItem() instanceof IHandlePickup) {
			int size = stack.stackSize;
			if (((IHandlePickup) stack.getItem()).onPickupItem(stack, player)) {
				if (stack.stackSize < size) {
					FMLCommonHandler.instance().firePlayerItemPickupEvent(player, event.item);
					event.item.playSound(Sounds.POP, 0.2F, ((event.item.worldObj.rand.nextFloat()
							- event.item.worldObj.rand.nextFloat()) * 0.7F + 1.0F) * 2.0F);
					player.onItemPickup(event.item, size - stack.stackSize);
				}
				if (stack.stackSize <= 0) {
					event.item.setDead();
					event.setCanceled(true);
				}
			} else {
				event.setCanceled(true);
			}
		}
	}

	/**
	 * LEFT_CLICK_BLOCK is only called on the server
	 * RIGHT_CLICK_BLOCK is called on both sides... weird.
	 */
	@SubscribeEvent
	public void onInteract(PlayerInteractEvent event) {
		ItemStack stack = event.entityPlayer.getHeldItem();
		switch(event.action) {
		case LEFT_CLICK_BLOCK:
			if (stack != null && stack.getItem() instanceof ISmashBlock && ZSSPlayerInfo.get(event.entityPlayer).getAttackTime() == 0) {
				if (blockWasSmashed(event.entityPlayer.worldObj, event.entityPlayer, stack, event.pos, event.face)) {
					if (event.entityPlayer instanceof EntityPlayerMP) {
						ZSSCombatEvents.setPlayerAttackTime(event.entityPlayer);
						PacketDispatcher.sendTo(new UnpressKeyPacket(UnpressKeyPacket.LMB), (EntityPlayerMP) event.entityPlayer);
					}
					event.useBlock = Result.DENY;
				}
			}
			break;
		case RIGHT_CLICK_BLOCK:
			if (stack != null && stack.getItem() instanceof ILiftBlock) {
				if (blockWasLifted(event.entityPlayer.worldObj, event.entityPlayer, stack, event.pos, event.face)) {
					event.useBlock = Result.DENY;
				}
			}
			break;
		default:
		}
	}

	/**
	 * Returns true if the ILiftBlock itemstack was able to pick up the block clicked
	 * and the useBlock result should be denied
	 */
	private boolean blockWasLifted(World world, EntityPlayer player, ItemStack stack, BlockPos pos, EnumFacing face) {
		IBlockState state = world.getBlockState(pos);
		Block block = state.getBlock();
		if (player.canPlayerEdit(pos, face, stack) || block instanceof ILiftable) {
			boolean isLiftable = block instanceof ILiftable;
			boolean isValidBlock = (block.isOpaqueCube() || block instanceof BlockBreakable) && Item.getItemFromBlock(block) != null;
			BlockWeight weight = (isLiftable ? ((ILiftable) block).getLiftWeight(player, stack, state, face)
					: (Config.canLiftVanilla() ? null : BlockWeight.IMPOSSIBLE));
			float strength = ((ILiftBlock) stack.getItem()).getLiftStrength(player, stack, state).weight;
			float resistance = (weight != null ? weight.weight : (block.getExplosionResistance(world, pos, null, null) * 5.0F/3.0F));
			if (isValidBlock && weight != BlockWeight.IMPOSSIBLE && strength >= resistance && (isLiftable || !block.hasTileEntity(state))) {
				if (world.isRemote) { // Send block's render color to server so held block can render correctly
					PacketDispatcher.sendToServer(new HeldBlockColorPacket(block.colorMultiplier(world, pos)));
				} else {
					ItemStack returnStack = ((ILiftBlock) stack.getItem()).onLiftBlock(player, stack, state);
					if (returnStack != null && returnStack.stackSize <= 0) {
						returnStack = null;
					}
					ItemStack heldBlock = ItemHeldBlock.getBlockStack(state, returnStack);
					if (isLiftable) {
						((ILiftable) block).onLifted(world, player, heldBlock, pos, state);
					}
					player.setCurrentItemOrArmor(0, heldBlock);
					world.playSoundEffect(pos.getX() + 0.5D, pos.getY() + 0.5D, pos.getZ() + 0.5D, block.stepSound.getBreakSound(),
							(block.stepSound.getVolume() + 1.0F) / 2.0F, block.stepSound.getFrequency() * 0.8F);
					world.setBlockToAir(pos);
				}
				return true;
			} else {
				WorldUtils.playSoundAtEntity(player, Sounds.GRUNT, 0.3F, 0.8F);
			}
		}
		return false;
	}

	/**
	 * Returns true if the ISmashBlock itemstack was able to smash up the block clicked
	 * and the useBlock result should be denied
	 */
	private boolean blockWasSmashed(World world, EntityPlayer player, ItemStack stack, BlockPos pos, EnumFacing face) {
		IBlockState state = world.getBlockState(pos);
		Block block = state.getBlock();
		boolean isSmashable = block instanceof ISmashable;
		Result smashResult = Result.DEFAULT;
		boolean wasDestroyed = false;
		if (player.canPlayerEdit(pos, face, stack) || isSmashable) {
			BlockWeight weight = (isSmashable ? ((ISmashable) block).getSmashWeight(player, stack, state, face)
					: (Config.canSmashVanilla() || isVanillaBlockSmashable(block) ? null : BlockWeight.IMPOSSIBLE));
			float strength = ((ISmashBlock) stack.getItem()).getSmashStrength(player, stack, state, face).weight;
			float resistance = (weight != null ? weight.weight : (block.getExplosionResistance(world, pos, null, null) * 5.0F/3.0F));
			smashResult = (isSmashable ? ((ISmashable) block).onSmashed(world, player, stack, pos, state, face) : smashResult);
			if (smashResult == Result.DEFAULT) {
				boolean isValidBlock = block.isOpaqueCube() || block instanceof BlockBreakable;
				if (isValidBlock && weight != BlockWeight.IMPOSSIBLE && strength >= resistance && (!block.hasTileEntity(state) || isSmashable)) {
					if (!(block instanceof BlockBreakable)) {
						world.playSoundAtEntity(player, Sounds.ROCK_FALL, 1.0F, 1.0F);
					}
					if (!world.isRemote) { 
						world.destroyBlock(pos, false);
					}
					wasDestroyed = true;
				}
			}
			((ISmashBlock) stack.getItem()).onBlockSmashed(player, stack, state, face, (smashResult == Result.ALLOW || wasDestroyed));
		}
		return (smashResult == Result.ALLOW || wasDestroyed);
	}

	private boolean isVanillaBlockSmashable(Block block) {
		return block.getMaterial() == Material.glass || block.getMaterial() == Material.ice;
	}

	private static void init() {
		addDrop(EntityCreeper.class, SkillBase.armorBreak);
		addDrop(EntityIronGolem.class, SkillBase.armorBreak);
		addDrop(EntitySilverfish.class, SkillBase.dash);
		addDrop(EntityHorse.class, SkillBase.dash);
		addDrop(EntityEnderman.class, SkillBase.dodge);
		addDrop(EntityKeese.class, SkillBase.dodge);
		addDrop(EntitySpider.class, SkillBase.endingBlow);
		addDrop(EntityCaveSpider.class, SkillBase.leapingBlow);
		addDrop(EntityMagmaCube.class, SkillBase.leapingBlow);
		addDrop(EntityPigZombie.class, SkillBase.parry);
		addDrop(EntityOcelot.class, SkillBase.parry);
		addDrop(EntityOctorok.class, SkillBase.risingCut);
		addDrop(EntityBlaze.class, SkillBase.spinAttack);
		addDrop(EntityDarknut.class, SkillBase.spinAttack);
		addDrop(EntityZombie.class, SkillBase.swordBasic);
		addDrop(EntitySkeleton.class, SkillBase.swordBasic);
		addDrop(EntityGhast.class, SkillBase.swordBeam);
		addDrop(EntityWitch.class, SkillBase.swordBeam);
		addDrop(EntityWizzrobe.class, SkillBase.swordBreak);
	}
}