package pneumaticCraft.common;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import net.minecraft.block.Block;
import net.minecraft.block.BlockGrass;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityCreature;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.monster.EntityGolem;
import net.minecraft.entity.monster.EntityMob;
import net.minecraft.entity.monster.EntitySlime;
import net.minecraft.entity.passive.EntitySquid;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.AxisAlignedBB;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.StatCollector;
import net.minecraft.world.ChunkPosition;
import net.minecraft.world.World;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.event.ServerChatEvent;
import net.minecraftforge.event.entity.EntityEvent.EntityConstructing;
import net.minecraftforge.event.entity.living.EnderTeleportEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent;
import net.minecraftforge.event.entity.living.LivingSetAttackTargetEvent;
import net.minecraftforge.event.entity.player.BonemealEvent;
import net.minecraftforge.event.entity.player.EntityItemPickupEvent;
import net.minecraftforge.event.entity.player.FillBucketEvent;
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
import net.minecraftforge.event.world.ExplosionEvent;
import net.minecraftforge.event.world.WorldEvent;
import net.minecraftforge.fluids.FluidStack;
import pneumaticCraft.api.PneumaticRegistry;
import pneumaticCraft.api.block.IPneumaticWrenchable;
import pneumaticCraft.api.client.pneumaticHelmet.EntityTrackEvent;
import pneumaticCraft.api.client.pneumaticHelmet.InventoryTrackEvent;
import pneumaticCraft.api.drone.AmadronRetrievalEvent;
import pneumaticCraft.api.drone.DroneConstructingEvent;
import pneumaticCraft.api.drone.DroneSuicideEvent;
import pneumaticCraft.api.item.IPressurizable;
import pneumaticCraft.client.gui.widget.GuiKeybindCheckBox;
import pneumaticCraft.client.render.pneumaticArmor.EntityTrackUpgradeHandler;
import pneumaticCraft.client.render.pneumaticArmor.HUDHandler;
import pneumaticCraft.client.render.pneumaticArmor.hacking.HackableHandler;
import pneumaticCraft.client.render.pneumaticArmor.hacking.entity.HackableEnderman;
import pneumaticCraft.common.ai.IDroneBase;
import pneumaticCraft.common.block.Blockss;
import pneumaticCraft.common.block.pneumaticPlants.BlockPlants;
import pneumaticCraft.common.block.pneumaticPlants.BlockPneumaticPlantBase;
import pneumaticCraft.common.config.Config;
import pneumaticCraft.common.entity.EntityProgrammableController;
import pneumaticCraft.common.entity.living.EntityDrone;
import pneumaticCraft.common.fluid.Fluids;
import pneumaticCraft.common.item.ItemAmadronTablet;
import pneumaticCraft.common.item.ItemMachineUpgrade;
import pneumaticCraft.common.item.ItemPlasticPlants;
import pneumaticCraft.common.item.ItemPneumaticArmor;
import pneumaticCraft.common.item.Itemss;
import pneumaticCraft.common.network.NetworkHandler;
import pneumaticCraft.common.network.PacketPlaySound;
import pneumaticCraft.common.network.PacketSetMobTarget;
import pneumaticCraft.common.recipes.AmadronOffer;
import pneumaticCraft.common.recipes.AmadronOfferCustom;
import pneumaticCraft.common.recipes.AmadronOfferManager;
import pneumaticCraft.common.remote.GlobalVariableManager;
import pneumaticCraft.common.thirdparty.ModInteractionUtilImplementation;
import pneumaticCraft.common.tileentity.TileEntityProgrammer;
import pneumaticCraft.common.util.PneumaticCraftUtils;
import pneumaticCraft.lib.TileEntityConstants;
import cpw.mods.fml.client.FMLClientHandler;
import cpw.mods.fml.common.eventhandler.Event.Result;
import cpw.mods.fml.common.eventhandler.EventPriority;
import cpw.mods.fml.common.eventhandler.SubscribeEvent;
import cpw.mods.fml.common.network.NetworkRegistry;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;

public class EventHandlerPneumaticCraft{

    private static ItemStack IRON_INGOT = new ItemStack(Items.iron_ingot);
    private static ItemStack IRON_BLOCK = new ItemStack(Blocks.iron_block);

    @SubscribeEvent
    public void handleIronExplosions(ExplosionEvent.Detonate event){
        Iterator<Entity> iterator = event.getAffectedEntities().iterator();
        while(iterator.hasNext()) {
            Entity entity = iterator.next();
            if(entity instanceof EntityItem) {
                ItemStack stack = ((EntityItem)entity).getEntityItem();
                if(stack != null && !entity.isDead && PneumaticCraftUtils.isSameOreDictStack(stack, IRON_INGOT) || PneumaticCraftUtils.isSameOreDictStack(stack, IRON_BLOCK)) {
                    Random rand = new Random();
                    if(stack.stackSize >= 3 || rand.nextDouble() >= Config.configCompressedIngotLossRate / 100D) {
                        Item newItem = PneumaticCraftUtils.isSameOreDictStack(stack, IRON_INGOT) ? Itemss.ingotIronCompressed : Item.getItemFromBlock(Blockss.compressedIron);
                        ItemStack newStack = new ItemStack(newItem, stack.stackSize, stack.getItemDamage());
                        if(stack.stackSize >= 3) {
                            newStack.stackSize = (int)(stack.stackSize * (rand.nextDouble() * Math.min(Config.configCompressedIngotLossRate * 0.02D, 0.2D) + (Math.max(0.9D, 1D - Config.configCompressedIngotLossRate * 0.01D) - Config.configCompressedIngotLossRate * 0.01D)));
                        }
                        ((EntityItem)entity).setEntityItemStack(newStack);
                        iterator.remove();
                        for(EntityPlayer player : (List<EntityPlayer>)event.world.getEntitiesWithinAABB(EntityPlayer.class, AxisAlignedBB.getBoundingBox(event.explosion.explosionX - 32, event.explosion.explosionY - 32, event.explosion.explosionZ - 32, event.explosion.explosionX + 32, event.explosion.explosionY + 32, event.explosion.explosionZ + 32))) {
                            AchievementHandler.giveAchievement(player, newStack);
                        }
                    }
                }
            }
        }
    }

    @SubscribeEvent
    public void onEntityConstruction(EntityConstructing event){
        HackableHandler.onEntityConstruction(event.entity);
        ItemPlasticPlants.onEntityConstruction(event.entity);
        if(event.entity instanceof IDroneBase) {
            MinecraftForge.EVENT_BUS.post(new DroneConstructingEvent((IDroneBase)event.entity));
        }
    }

    @SubscribeEvent
    public void onPlayerPickup(EntityItemPickupEvent event){
        if(event.item != null && event.entityPlayer != null) AchievementHandler.giveAchievement(event.entityPlayer, event.item.getEntityItem());
    }

    // add slime seeds as mobdrop to Slimes.
    @SubscribeEvent
    public void onEntityDeath(LivingDeathEvent event){
        if(!event.entity.worldObj.isRemote) {
            if(Config.enableSlimeSeedDrop && event.entity instanceof EntitySlime && Math.random() < 0.1D) {
                ItemPlasticPlants.markInactive(event.entity.entityDropItem(new ItemStack(Itemss.plasticPlant, 1, ItemPlasticPlants.SLIME_PLANT_DAMAGE), 0));
            } else if(Config.enableCreeperSeedDrop && event.entity instanceof EntityCreeper && Math.random() < 0.05D) {
                if(Config.enableCreeperDropExplosion) event.entity.worldObj.createExplosion(event.entity, event.entity.posX, event.entity.posY + event.entityLiving.height / 2D, event.entity.posZ, 0.5F, event.entity.worldObj.getGameRules().getGameRuleBooleanValue("mobGriefing"));
                int dropAmount = (int)(Math.random() * 3D) + 1;
                for(int i = 0; i < dropAmount; i++)
                    ItemPlasticPlants.markInactive(event.entity.entityDropItem(new ItemStack(Itemss.plasticPlant, 1, ItemPlasticPlants.CREEPER_PLANT_DAMAGE), 0));
            } else if(Config.enableSquidSeedDrop && event.entity instanceof EntitySquid && Math.random() < 0.05D) {
                ItemPlasticPlants.markInactive(event.entity.entityDropItem(new ItemStack(Itemss.plasticPlant, 1, ItemPlasticPlants.SQUID_PLANT_DAMAGE), 0));
            }
        }
    }

    @SubscribeEvent(priority = EventPriority.LOWEST)
    public void onEnderTeleport(EnderTeleportEvent event){
        if(!HackableEnderman.onEndermanTeleport(event.entity)) {
            event.setCanceled(true);
        } else {
            if(Config.enableEndermanSeedDrop && Math.random() < 0.05D) {
                if(!event.entity.worldObj.isRemote) ItemPlasticPlants.markInactive(event.entity.entityDropItem(new ItemStack(Itemss.plasticPlant, 1, ItemPlasticPlants.ENDER_PLANT_DAMAGE), 0));
            }
        }
    }

    // bone meal event, to grow plants
    @SubscribeEvent
    public void onFertilization(BonemealEvent event){
        if(event.world.isRemote) return; // why would we want to handle this on the client-side?

        if(event.block == Blocks.netherrack || event.block == Blocks.end_stone || event.block.canSustainPlant(event.world, event.x, event.y, event.z, ForgeDirection.UP, Blocks.red_flower)) { // can bonemeal Biomes O' Plenty grass, etc.    			    			
            boolean onGrass = event.block instanceof BlockGrass;
            if(onGrass && Config.includePlantsOnBonemeal || !onGrass && Config.allowDirtBonemealing) {
                // we'll try to spawn plants in a 5x5 area which is centered on the block that has been bonemealed
                for(int x = event.x - 2; x < event.x + 3; x++) {
                    for(int z = event.z - 2; z < event.z + 3; z++) {
                        if(event.world.isAirBlock(x, event.y + 1, z)) {
                            if(event.world.rand.nextInt(8) == 1) { // increase .nextInt(x) to lower the chances of spawning a plant
                                BlockPneumaticPlantBase trySpawn = BlockPlants.allPlants.get(event.world.rand.nextInt(BlockPlants.allPlants.size() - 1)); // select a random plant    							
                                if(trySpawn.canPlantGrowOnThisBlock(event.world.getBlock(x, event.y, z), event.world, x, event.y, z)) { // make sure that the plant we selected can grow on the soil
                                    event.world.setBlock(x, event.y + (trySpawn.isPlantHanging() ? -1 : 1), z, trySpawn);
                                }
                            }
                        }
                    }

                    /*
                     * vanilla mechanics will spawn flowers etc. when bonemeal is used on grass,
                     * so we cannot set Result.ALLOW in this case because it would stop event-propagation
                     */
                    if(!onGrass) event.setResult(Result.ALLOW);
                }
            }
        }
    }

    @SubscribeEvent
    public void FillBucket(FillBucketEvent event){
        MovingObjectPosition p = event.target;
        if(event.current == null || event.current.getItem() != Items.bucket || event.world.getBlockMetadata(p.blockX, p.blockY, p.blockZ) != 0) return;
        ItemStack result = attemptFill(event.world, event.target);
        if(result != null) {
            event.result = result;
            AchievementHandler.giveAchievement(event.entityPlayer, result);
            event.setResult(Result.ALLOW);
        }
    }

    private ItemStack attemptFill(World world, MovingObjectPosition p){
        Block id = world.getBlock(p.blockX, p.blockY, p.blockZ);
        for(Map.Entry<Block, Item> entry : Fluids.fluidBlockToBucketMap.entrySet()) {
            if(id == entry.getKey()) {
                world.setBlock(p.blockX, p.blockY, p.blockZ, Blocks.air);
                return new ItemStack(entry.getValue());
            }
        }
        return null;
    }

    @SubscribeEvent
    public void onPlayerClick(PlayerInteractEvent event){
        Block interactedBlock = event.world.getBlock(event.x, event.y, event.z);
        if(!event.entityPlayer.capabilities.isCreativeMode || !event.entityPlayer.canCommandSenderUseCommand(2, "securityStation")) {
            if(event.action != PlayerInteractEvent.Action.RIGHT_CLICK_AIR && event.world != null && !event.world.isRemote) {
                if(interactedBlock != Blockss.securityStation || event.action == PlayerInteractEvent.Action.LEFT_CLICK_BLOCK) {
                    ItemStack heldItem = event.entityPlayer.getCurrentEquippedItem();
                    boolean tryingToPlaceSecurityStation = heldItem != null && heldItem.getItem() instanceof ItemBlock && ((ItemBlock)heldItem.getItem()).field_150939_a == Blockss.securityStation;
                    int blockingStations = PneumaticCraftUtils.getProtectingSecurityStations(event.entity.worldObj, event.x, event.y, event.z, event.entityPlayer, true, tryingToPlaceSecurityStation);
                    if(blockingStations > 0) {
                        event.setCanceled(true);
                        event.entityPlayer.addChatComponentMessage(new ChatComponentText(StatCollector.translateToLocalFormatted(tryingToPlaceSecurityStation ? "message.securityStation.stationPlacementPrevented" : "message.securityStation.accessPrevented", blockingStations)));
                    }
                }
            }
        }

        /**
         * Due to some weird quirk that causes Block#onBlockActivated not getting called on the server when the player is sneaking, this is a workaround.
         */
        if(!event.isCanceled() && event.action == PlayerInteractEvent.Action.RIGHT_CLICK_BLOCK && !event.world.isRemote) {
            if(event.entityPlayer.isSneaking() && (interactedBlock == Blockss.elevatorCaller || interactedBlock == Blockss.chargingStation)) {
                event.setCanceled(interactedBlock.onBlockActivated(event.world, event.x, event.y, event.z, event.entityPlayer, event.face, 0, 0, 0));
            } else if(event.entityPlayer.getCurrentEquippedItem() != null && ModInteractionUtilImplementation.getInstance().isModdedWrench(event.entityPlayer.getCurrentEquippedItem().getItem())) {
                if(interactedBlock instanceof IPneumaticWrenchable) {
                    ((IPneumaticWrenchable)interactedBlock).rotateBlock(event.world, event.entityPlayer, event.x, event.y, event.z, ForgeDirection.getOrientation(event.face));
                }
            }
        }

        if(!event.isCanceled() && interactedBlock == Blocks.cobblestone) {
            AchievementHandler.checkFor9x9(event.entityPlayer, event.x, event.y, event.z);
        }
    }

    /**
     * Used by PneumaticHelmet
     * @param event
     */
    @SubscribeEvent
    public void onMobTargetSet(LivingSetAttackTargetEvent event){
        if(event.entity instanceof EntityCreature) {
            if(!event.entity.worldObj.isRemote) {
                NetworkHandler.sendToAllAround(new PacketSetMobTarget((EntityCreature)event.entity, event.target), new NetworkRegistry.TargetPoint(event.entity.worldObj.provider.dimensionId, event.entity.posX, event.entity.posY, event.entity.posZ, TileEntityConstants.PACKET_UPDATE_DISTANCE));
            } else {
                warnPlayerIfNecessary(event);
            }
        }
    }

    @SideOnly(Side.CLIENT)
    private void warnPlayerIfNecessary(LivingSetAttackTargetEvent event){
        EntityPlayer player = FMLClientHandler.instance().getClient().thePlayer;
        if(event.target == player && (event.entityLiving instanceof EntityGolem || event.entityLiving instanceof EntityMob)) {
            ItemStack helmetStack = player.getCurrentArmor(3);
            if(helmetStack != null && helmetStack.getItem() == Itemss.pneumaticHelmet && ((IPressurizable)helmetStack.getItem()).getPressure(helmetStack) > 0 && ItemPneumaticArmor.getUpgrades(ItemMachineUpgrade.UPGRADE_ENTITY_TRACKER, helmetStack) > 0 && GuiKeybindCheckBox.trackedCheckboxes.get("pneumaticHelmet.upgrade.coreComponents").checked && GuiKeybindCheckBox.trackedCheckboxes.get("pneumaticHelmet.upgrade." + EntityTrackUpgradeHandler.UPGRADE_NAME).checked) {
                HUDHandler.instance().getSpecificRenderer(EntityTrackUpgradeHandler.class).warnIfNecessary(event.entityLiving);
            }
        } else {
            HUDHandler.instance().getSpecificRenderer(EntityTrackUpgradeHandler.class).removeTargetingEntity(event.entityLiving);
        }
    }

    @SubscribeEvent
    public void quetziMoo(ServerChatEvent event){
        if(event.username.equals("Quetzz") && event.message.equals("m00")) {
            for(int i = 0; i < 4; i++)
                NetworkHandler.sendTo(new PacketPlaySound("mob.cow.say", event.player.posX, event.player.posY, event.player.posZ, 1, 1, true), event.player);
        }
    }

    @SubscribeEvent
    public void onWorldLoad(WorldEvent.Load event){
        if(!event.world.isRemote) {
            if(event.world.provider.dimensionId == 0) {
                GlobalVariableManager.overworld = event.world;
                event.world.loadItemData(GlobalVariableManager.class, GlobalVariableManager.DATA_KEY);
            }
        }
    }

    @SubscribeEvent
    public void onEntityTracking(EntityTrackEvent event){
        if(event.trackingEntity instanceof EntityProgrammableController) event.setCanceled(true);
    }

    @SubscribeEvent
    public void onInventoryTracking(InventoryTrackEvent event){
        if(event.getTileEntity() instanceof TileEntityProgrammer) event.setCanceled(true);
    }

    @SubscribeEvent
    public void onDroneSuicide(DroneSuicideEvent event){
        if(event.drone instanceof EntityDrone) {
            EntityDrone drone = (EntityDrone)event.drone;
            AmadronOffer offer = drone.getHandlingOffer();
            if(offer != null) {
                int times = drone.getOfferTimes();
                if(offer.getInput() instanceof ItemStack) {
                    int requiredCount = ((ItemStack)offer.getInput()).stackSize * times;
                    for(int i = 0; i < drone.getInventory().getSizeInventory(); i++) {
                        if(drone.getInventory().getStackInSlot(i) != null) {
                            requiredCount -= drone.getInventory().getStackInSlot(i).stackSize;
                        }
                    }
                    if(requiredCount <= 0) {
                        for(int i = 0; i < drone.getInventory().getSizeInventory(); i++) {
                            drone.getInventory().setInventorySlotContents(i, null);
                        }
                        MinecraftForge.EVENT_BUS.post(new AmadronRetrievalEvent(event.drone));
                    }
                } else {
                    int requiredCount = ((FluidStack)offer.getInput()).amount * times;
                    if(drone.getTank().getFluidAmount() >= requiredCount) {
                        MinecraftForge.EVENT_BUS.post(new AmadronRetrievalEvent(event.drone));
                    }
                }
            }
        }
    }

    @SubscribeEvent
    public void onAmadronSuccess(AmadronRetrievalEvent event){
        EntityDrone drone = (EntityDrone)event.drone;
        AmadronOffer offer = drone.getHandlingOffer();

        boolean shouldDeliver = false;
        if(offer instanceof AmadronOfferCustom) {
            AmadronOffer realOffer = AmadronOfferManager.getInstance().get(offer);
            if(realOffer != null) {//If we find the non-inverted offer, that means the Drone just has completed trading with a different player.
                ((AmadronOfferCustom)realOffer).addPayment(drone.getOfferTimes());
                ((AmadronOfferCustom)realOffer).addStock(-drone.getOfferTimes());
                realOffer.onTrade(drone.getOfferTimes(), drone.getBuyingPlayer());
                shouldDeliver = true;
            }
            realOffer = AmadronOfferManager.getInstance().get(((AmadronOfferCustom)offer).copy().invert());
            if(realOffer != null) {//If we find the inverted offer, that means the Drone has just restocked.
                ((AmadronOfferCustom)realOffer).addStock(drone.getOfferTimes());
            }
        } else {
            shouldDeliver = true;
        }
        if(shouldDeliver) {
            ItemStack usedTablet = drone.getUsedTablet();
            if(offer.getOutput() instanceof ItemStack) {
                ItemStack offeringItems = (ItemStack)offer.getOutput();
                int producedItems = offeringItems.stackSize * drone.getOfferTimes();
                List<ItemStack> stacks = new ArrayList<ItemStack>();
                while(producedItems > 0) {
                    ItemStack stack = offeringItems.copy();
                    stack.stackSize = Math.min(producedItems, stack.getMaxStackSize());
                    stacks.add(stack);
                    producedItems -= stack.stackSize;
                }
                ChunkPosition pos = ItemAmadronTablet.getItemProvidingLocation(usedTablet);
                if(pos != null) {
                    World world = PneumaticCraftUtils.getWorldForDimension(ItemAmadronTablet.getItemProvidingDimension(usedTablet));
                    PneumaticRegistry.getInstance().deliverItemsAmazonStyle(world, pos.chunkPosX, pos.chunkPosY, pos.chunkPosZ, stacks.toArray(new ItemStack[stacks.size()]));
                }
            } else {
                FluidStack offeringFluid = ((FluidStack)offer.getOutput()).copy();
                offeringFluid.amount *= drone.getOfferTimes();
                ChunkPosition pos = ItemAmadronTablet.getLiquidProvidingLocation(usedTablet);
                if(pos != null) {
                    World world = PneumaticCraftUtils.getWorldForDimension(ItemAmadronTablet.getLiquidProvidingDimension(usedTablet));
                    PneumaticRegistry.getInstance().deliverFluidAmazonStyle(world, pos.chunkPosX, pos.chunkPosY, pos.chunkPosZ, offeringFluid);
                }
            }
        }
    }
}