package de.hessj.environmentex;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Dispenser;
import org.bukkit.block.data.Directional;
import org.bukkit.entity.Item;
import org.bukkit.entity.ItemFrame;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockDispenseEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.ShapedRecipe;
import org.bukkit.inventory.ShapelessRecipe;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.util.Vector;

public class AutoCrafter implements Listener {

    private Recipe findRecipe(Material output) {
        for (Recipe recipe : Bukkit.getServer().getRecipesFor(new ItemStack(output))) {
            if (recipe.getResult().getType() == output) {
                return recipe;
            }
        }
        return null;
    }

    @EventHandler
    private void onThrow(BlockDispenseEvent event) {
        if (event.getBlock().getType() != Material.DISPENSER) {
            return;
        }

        Dispenser dispenser = (Dispenser) event.getBlock().getState();
        BlockFace facing = ((Directional) dispenser.getBlockData()).getFacing();
        Inventory tempInventory = dispenser.getInventory();

        Collection<ItemFrame> frames = event.getBlock().getLocation().getNearbyEntitiesByType(ItemFrame.class, 1.5);
        if (frames.toArray().length == 0)
            return;

        for (ItemFrame frame : frames) {
            if (frame.getItem().getType() == Material.AIR)
                return;

            event.setCancelled(true);
            String outputItemName = frame.getItem().getType().name();
            Recipe recipe = findRecipe(Material.matchMaterial(outputItemName));

            if (recipe == null) {
                return;
            }

            Map<Material, Integer> required = new HashMap<>();

            if (recipe instanceof ShapedRecipe) {
                ShapedRecipe shapedRecipe = (ShapedRecipe) recipe;
                shapedRecipe.getIngredientMap().values().forEach(item -> {
                    if (item != null) {
                        required.put(item.getType(), required.getOrDefault(item.getType(), 0) + 1);
                    }
                });
            } else if (recipe instanceof ShapelessRecipe) {
                ShapelessRecipe shapelessRecipe = (ShapelessRecipe) recipe;
                shapelessRecipe.getIngredientList().forEach(item -> {
                    required.put(item.getType(), required.getOrDefault(item.getType(), 0) + 1);
                });
            }

            new BukkitRunnable() {
                @Override
                public void run() {
                    if (!hasRequiredMaterials(tempInventory, required)) {
                        return;
                    }
                    for (Map.Entry<Material, Integer> entry : required.entrySet()) {

                        removeMaterialFromInventory(dispenser.getInventory(), entry.getKey(), entry.getValue());
                    }
                    dispenseThroughFace(event.getBlock(), new ItemStack(Material.matchMaterial(outputItemName)),
                            facing);
                }

            }.runTaskLater(App.main, 0L);
        }
    }

    private void dispenseThroughFace(Block dispenser, ItemStack item, BlockFace facing) {
        Location dispenseLocation = dispenser.getRelative(facing).getLocation().add(0.5, 0.5, 0.5);
        Item droppedItem = dispenser.getWorld().dropItem(dispenseLocation, item);
        Vector velocity = new Vector(facing.getModX(), facing.getModY(), facing.getModZ()).multiply(0.3);
        droppedItem.setVelocity(velocity);
    }

    private boolean hasRequiredMaterials(Inventory inventory, Map<Material, Integer> required) {

        for (Map.Entry<Material, Integer> entry : required.entrySet()) {
            int materialCount = countMaterialInInventory(inventory, entry.getKey());
            if (materialCount < entry.getValue()) {
                return false;
            }
        }
        return true;
    }

    private int countMaterialInInventory(Inventory inventory, Material material) {

        int count = 0;
        for (int i = 0; i < inventory.getSize(); i++) {
            if (inventory.getItem(i) != null && inventory.getItem(i).getType() == material) {
                count += inventory.getItem(i).getAmount();
            }
        }
        return count;
    }

    private void removeMaterialFromInventory(Inventory inventory, Material material, int amount) {
        for (int i = 0; i < inventory.getSize(); i++) {
            if (inventory.getItem(i) != null && inventory.getItem(i).getType() == material) {
                int currentAmount = inventory.getItem(i).getAmount();
                if (currentAmount <= amount) {
                    amount -= currentAmount;
                    inventory.clear(i);
                } else {
                    inventory.getItem(i).setAmount(currentAmount - amount);
                    break;
                }
                if (amount <= 0) {
                    break;
                }
            }
        }
    }
}