package net.minecraft.world.item.crafting;

import net.minecraft.core.NonNullList;
import net.minecraft.core.IRegistryCustom;
import net.minecraft.core.IRegistryCustom$Dimension;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.ItemStack;

import com.bergerkiller.bukkit.common.inventory.CraftInputSlot;

import com.bergerkiller.generated.net.minecraft.world.item.crafting.CraftingManagerHandle;
import com.bergerkiller.generated.net.minecraft.world.item.crafting.RecipesFurnaceHandle;
import com.bergerkiller.generated.net.minecraft.world.item.crafting.IRecipeHandle;
import com.bergerkiller.generated.net.minecraft.world.item.crafting.FurnaceRecipeHandle;
import com.bergerkiller.generated.net.minecraft.world.item.ItemStackHandle;

#if version >= 1.12

class IRecipe {
#if version >= 1.15
    public (org.bukkit.inventory.ItemStack) ItemStack getOutput() {
        if (instance instanceof RecipeFireworks) {
            // Special case (see getIngredients code)
            net.minecraft.world.item.ItemStack itemstack = new ItemStack(net.minecraft.world.item.Items.FIREWORK_ROCKET, 3);
            ItemStackHandle.T.setFireworksFlightDuration.invoke(itemstack, Integer.valueOf(3));
            return itemstack;
        } else {
  #if version >= 1.21.2
            //TODO: Technically could support world-specific recipes!
            IRegistryCustom$Dimension registry = MinecraftServer.getServer().registryAccess();

            //TODO: Recipes could change depending on inputs
            RecipeInput input;
            if (instance instanceof RecipeTippedArrow || instance instanceof DecoratedPotRecipe) {
                // These recipes cannot operate with an empty input
                return ItemStack.EMPTY;
            } else if (instance instanceof RecipeCrafting) {
                input = CraftingInput.EMPTY;
            } else if (instance instanceof RecipeSingleItem) {
                input = new SingleRecipeInput(ItemStack.EMPTY);
            } else if (instance instanceof SmithingRecipe) {
                // record SmithingRecipeInput(ItemStack template, ItemStack base, ItemStack addition);
                input = new SmithingRecipeInput(ItemStack.EMPTY, ItemStack.EMPTY, ItemStack.EMPTY);
            } else {
                // Unknown recipe type. Fail.
                throw new IllegalStateException("Unknown recipe type: " + instance.getClass().getName());
            }

            return instance.assemble((RecipeInput) input, registry);
  #elseif version >= 1.19.4
            //TODO: Technically could support world-specific recipes!
            IRegistryCustom$Dimension registry = MinecraftServer.getServer().registryAccess();
            return instance.getResultItem(registry);
  #elseif version >= 1.18
            return instance.getResultItem();
  #else
            return instance.getResult();
  #endif
        }
    }
#elseif version >= 1.14
    public abstract (org.bukkit.inventory.ItemStack) ItemStack getOutput:c();
#elseif version >= 1.13
    public abstract (org.bukkit.inventory.ItemStack) ItemStack getOutput:d();
#else
    public abstract (org.bukkit.inventory.ItemStack) ItemStack getOutput:b();
#endif

    public abstract (List<CraftInputSlot>) List<RecipeItemStack> getIngredients() {
        if (instance instanceof ShapedRecipes) {
            ShapedRecipes s = (ShapedRecipes) instance;
#select version >=
#case 1.21.2:   java.util.List optionalIngredients = s.getIngredients();
                java.util.List ingredients = new java.util.ArrayList(optionalIngredients.size());
                for (java.util.Iterator iter = optionalIngredients.iterator(); iter.hasNext();) {
                    java.util.Optional opt = (java.util.Optional) iter.next();
                    if (opt.isPresent()) {
                        ingredients.add(opt.get());
                    }
                }
                return ingredients;
#case 1.18:     return s.getIngredients();
#case 1.14:     return s.a();
#case 1.13:     return s.e();
#case 1.12.1:   return s.d();
#case else:     #require net.minecraft.world.item.crafting.ShapedRecipes private final NonNullList<RecipeItemStack> items;
                return instance#items;
#endselect
        } else if (instance instanceof ShapelessRecipes) {
            ShapelessRecipes s = (ShapelessRecipes) instance;
#select version >=
#case 1.21.2:   #require net.minecraft.world.item.crafting.ShapelessRecipes final java.util.List<RecipeItemStack> shapelessIngredients:ingredients;
                return s#shapelessIngredients;
#case 1.18:     return s.getIngredients();
#case 1.14:     return s.a();
#case 1.13:     return s.e();
#case 1.12.1:   return s.d();
#case else:     #require net.minecraft.world.item.crafting.ShapelessRecipes private final NonNullList<RecipeItemStack> ingredients;
                return instance#ingredients;
#endselect

#if version >= 1.15
        } else if (instance instanceof RecipeFireworks) {
            // Fireworks are a 'complex' recipe with many possible options
            // To sort of work properly rather than not at all, default to
            // crafting a flight 3 firework:
            // Shapeless(1 paper + 3 gunpowder) -> Flight 3 firework
  #if version >= 1.18
            RecipeItemStack paper = RecipeItemStack.of(new net.minecraft.world.level.IMaterial[] { net.minecraft.world.item.Items.PAPER });
            RecipeItemStack gunpowder = RecipeItemStack.of(new net.minecraft.world.level.IMaterial[] { net.minecraft.world.item.Items.GUNPOWDER });
  #else
            RecipeItemStack paper = RecipeItemStack.a(new net.minecraft.world.level.IMaterial[] { net.minecraft.world.item.Items.PAPER });
            RecipeItemStack gunpowder = RecipeItemStack.a(new net.minecraft.world.level.IMaterial[] { net.minecraft.world.item.Items.GUNPOWDER });
  #endif

            java.util.List ingredients = new java.util.ArrayList(4);
            ingredients.add(paper);
            ingredients.add(gunpowder);
            ingredients.add(gunpowder);
            ingredients.add(gunpowder);
            return ingredients;
#endif
        } else {
            return null;
        }
    }
}

#else

interface IRecipe {
    public abstract (org.bukkit.inventory.ItemStack) ItemStack getOutput:b();
    public abstract (List<CraftInputSlot>) List<ItemStack> getIngredients();
}

#endif

// >= MC 1.12
optional class RecipeItemStack {
#if version >= 1.21.2
    public (List<org.bukkit.inventory.ItemStack>) List<ItemStack> getChoices() {
        if (instance.isExact()) {
  #if exists net.minecraft.world.item.crafting.RecipeItemStack public java.util.Set<net.minecraft.world.item.ItemStack> itemStacks();
            java.util.Set itemStacks = instance.itemStacks();
            if (itemStacks.isEmpty()) {
                return java.util.Collections.emptyList();
            } else {
                return new java.util.ArrayList(itemStacks);
            }
  #else
            return (List) instance.itemStacks();
  #endif
        }

        // Only stores the Item material to match. Create itemstacks out of them (count 1)
  #if version >= 1.21.4
        #require RecipeItemStack private final net.minecraft.core.HolderSet<net.minecraft.world.item.Item> values;
        net.minecraft.core.HolderSet items = instance#values;
  #else
        java.util.List items = instance.items();
  #endif
        java.util.List itemStacks = new java.util.ArrayList(items.size());
        for (java.util.Iterator iter = items.iterator(); iter.hasNext();) {
            net.minecraft.core.Holder itemHolder = (net.minecraft.core.Holder) iter.next();
            ItemStack item = new ItemStack(itemHolder);
            itemStacks.add(item);
        }
        return itemStacks;
    }
#elseif version >= 1.18
    public (List<org.bukkit.inventory.ItemStack>) ItemStack[] getChoices:getItems();
#elseif version >= 1.17
    public (List<org.bukkit.inventory.ItemStack>) ItemStack[] getChoices:a();
#else
    public (List<org.bukkit.inventory.ItemStack>) ItemStack[] getChoices() {
  #if version >= 1.13
        instance.buildChoices();
  #endif
        return instance.choices;
    }
#endif

    public void setChoices((List<org.bukkit.inventory.ItemStack>) List<ItemStack> choices) {
#if version >= 1.21.2
  #if exists net.minecraft.world.item.crafting.RecipeItemStack private java.util.Set<net.minecraft.world.item.ItemStack> itemStacks;
        // Paper
        #require net.minecraft.world.item.crafting.RecipeItemStack private Set<ItemStack> choicesRecipeItemStack:itemStacks;
        choices = new HashSet(choices); // Safe copy / avoid conversion stuff sticking around
        instance#choicesRecipeItemStack = choices;
  #else
        #require net.minecraft.world.item.crafting.RecipeItemStack private List<ItemStack> choicesRecipeItemStack:itemStacks;
        choices = new ArrayList(choices); // Safe copy / avoid conversion stuff sticking around
        instance#choicesRecipeItemStack = choices;
  #endif
#else

  #if version >= 1.17
        #require net.minecraft.world.item.crafting.RecipeItemStack private ItemStack[] choicesRecipeItemStack:itemStacks;
  #elseif version >= 1.12
        #require net.minecraft.world.item.crafting.RecipeItemStack public final ItemStack[] choicesRecipeItemStack:choices;
  #endif
        ItemStack[] choicesArr = (ItemStack[]) choices.toArray(new ItemStack[0]);
        instance#choicesRecipeItemStack = choicesArr;
#endif
    }

    <code>
    public static Object createRawRecipeItemStack(List<org.bukkit.inventory.ItemStack> choices) {
        Object raw = T.newInstanceNull();
        T.setChoices.invoke(raw, choices);
        return raw;
    }
    </code>
}

// <= MC 1.12.2
optional class RecipesFurnace {
    public (Map<org.bukkit.inventory.ItemStack, org.bukkit.inventory.ItemStack>) Map<ItemStack, ItemStack> recipes;

    public static (RecipesFurnaceHandle) RecipesFurnace getInstance();

    public (ItemStackHandle) ItemStack getResult((ItemStackHandle) ItemStack itemstack);

}

// >= MC 1.13
optional class FurnaceRecipe extends IRecipe {

#if version >= 1.21.2
    public (CraftInputSlot) RecipeItemStack getIngredient:input();
#else
    public (CraftInputSlot) RecipeItemStack getIngredient() {
  #if version >= 1.14
        #require net.minecraft.world.item.crafting.RecipeCooking protected final RecipeItemStack ingredient;
  #elseif version >= 1.13
        #require net.minecraft.world.item.crafting.FurnaceRecipe private final RecipeItemStack ingredient;
  #endif
        return instance#ingredient;
    }
#endif

    public static (Iterable<FurnaceRecipeHandle>) Iterable<FurnaceRecipe> getRecipes() {
#if version >= 1.18
        java.util.Iterator allRecipes = MinecraftServer.getServer().getRecipeManager().getRecipes().iterator();
#else
        java.util.Iterator allRecipes = MinecraftServer.getServer().getCraftingManager().b().iterator();
#endif
        java.util.List filteredResult = new java.util.ArrayList();
        while (allRecipes.hasNext()) {
            Object recipe = allRecipes.next();
#if version >= 1.20.2
            recipe = ((net.minecraft.world.item.crafting.RecipeHolder) recipe).value();
#endif
            if (recipe instanceof FurnaceRecipe) {
                filteredResult.add(recipe);
            }
        }
        return filteredResult;
    }
}

class CraftingManager {
    public static (Iterable<IRecipeHandle>) Iterable<IRecipe> getRecipes() {
#if version >= 1.13
  #if version >= 1.18
        java.util.Iterator allRecipes = MinecraftServer.getServer().getRecipeManager().getRecipes().iterator();
  #else
        java.util.Iterator allRecipes = MinecraftServer.getServer().getCraftingManager().b().iterator();
  #endif
        java.util.List filteredResult = new java.util.ArrayList();

  #if version >= 1.19.4
        IRegistryCustom$Dimension registry = MinecraftServer.getServer().registryAccess();
  #endif
  #if version >= 1.21.2
        RecipeInput emptyCraftingInput = CraftingInput.EMPTY;
  #endif

        while (allRecipes.hasNext()) {
            Object recipe = allRecipes.next();
  #if version >= 1.20.2
            recipe = ((net.minecraft.world.item.crafting.RecipeHolder) recipe).value();
  #endif

            // Recipe must be a crafting recipe. This can be shaped, shapeless or complex
            // This removes furnace recipes, stone cutting, etc. Which is not intended to be 'seen' here.
            // TODO: In future, we probably do want to have an api accessible for such recipes?
  #if version >= 1.14
            if (!(recipe instanceof RecipeCrafting)) {
                continue;
            }
  #else
            if (!(recipe instanceof ShapedRecipes || recipe instanceof ShapelessRecipes || recipe instanceof IRecipeComplex)) {
                continue;
            }
  #endif

            // Since Minecraft 1.17 the fireworks recipe exists twice
            // One is shapeless, the other is complex
            // Correct for this and only keep the complex one!
  #if version >= 1.21.2
            if (recipe instanceof ShapelessRecipes && ((IRecipe) recipe).assemble(emptyCraftingInput, registry).getItem() == Items.FIREWORK_ROCKET) {
                continue;
            }
  #elseif version >= 1.19.4
            if (!(recipe instanceof RecipeFireworks) && ((IRecipe) recipe).getResultItem(registry).getItem() == Items.FIREWORK_ROCKET) {
                continue;
            }
  #elseif version >= 1.18
            if (!(recipe instanceof RecipeFireworks) && ((IRecipe) recipe).getResultItem().getItem() == Items.FIREWORK_ROCKET) {
                continue;
            }
  #elseif version >= 1.17
            if (!(recipe instanceof RecipeFireworks) && ((IRecipe) recipe).getResult().getItem() == Items.FIREWORK_ROCKET) {
                continue;
            }
  #endif

            filteredResult.add(recipe);
        }
        return filteredResult;
#elseif version >= 1.12
        return CraftingManager.recipes;
#else
        return CraftingManager.getInstance().recipes;
#endif
    }
}
