package cubex2.cs4.plugins.vanilla;

import com.google.common.collect.Lists;
import cubex2.cs4.api.ContentHelper;
import cubex2.cs4.api.InitPhase;
import cubex2.cs4.api.RecipeInput;
import cubex2.cs4.api.WrappedItemStack;
import cubex2.cs4.data.SimpleContent;
import cubex2.cs4.plugins.jei.JEICompatRegistry;
import cubex2.cs4.plugins.vanilla.crafting.CraftingManagerCS4;
import cubex2.cs4.util.ItemHelper;
import cubex2.cs4.util.ReflectionHelper;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.IRecipe;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.item.crafting.ShapelessRecipes;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.oredict.OreDictionary;
import net.minecraftforge.oredict.ShapelessOreRecipe;

import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

class ShapelessRecipe extends SimpleContent
{
    List<RecipeInput> items = Lists.newArrayList();
    WrappedItemStack result;
    boolean remove = false;
    ResourceLocation recipeList = new ResourceLocation("minecraft", "vanilla");
    int[] damage = new int[0];

    @Override
    protected void doInit(InitPhase phase, ContentHelper helper)
    {
        if (remove)
        {
            if (!recipeList.toString().equals("minecraft:vanilla")) // temporary until one can remove vanilla recipes
            {
                removeRecipe(CraftingManagerCS4.getRecipes(recipeList));
            }
        } else
        {
            addRecipe();
        }
    }

    @Override
    protected boolean isReady()
    {
        if (result != null && !result.isItemLoaded())
            return false;

        return items.stream().allMatch(input -> input.isOreClass() || (input.isItemStack() && input.getStack().isItemLoaded()));
    }

    boolean removeRecipe(Collection<IRecipe> from)
    {
        if (items.size() == 0)
        {
            return removeWithResult(from);
        } else if (result == null)
        {
            return removeWithInput(from);
        } else
        {
            return removeWithBoth(from);
        }
    }

    private boolean removeWithResult(Collection<IRecipe> from)
    {
        return from.removeIf(this::matchesOutput);
    }

    private boolean removeWithInput(Collection<IRecipe> from)
    {
        List<IRecipe> recipes = from.stream()
                                    .filter(this::matchesInput)
                                    .collect(Collectors.toList());

        return from.removeAll(recipes);
    }

    private boolean removeWithBoth(Collection<IRecipe> from)
    {
        List<IRecipe> recipes = from.stream()
                                    .filter(this::matchesOutput)
                                    .filter(this::matchesInput)
                                    .collect(Collectors.toList());

        return from.removeAll(recipes);
    }

    private boolean matchesOutput(IRecipe recipe)
    {
        return OreDictionary.itemMatches(recipe.getRecipeOutput(), result.getItemStack(), false);
    }

    private boolean matchesInput(IRecipe recipe)
    {
        if (recipe instanceof ShapelessRecipes)
        {
            return matchesInput((ShapelessRecipes) recipe);
        } else if (recipe instanceof ShapelessOreRecipe)
        {
            return matchesInput((ShapelessOreRecipe) recipe);
        }

        return false;
    }

    private boolean matchesInput(ShapelessRecipes recipe)
    {
        if (isOreRecipe())
            return false;
        if (recipe.recipeItems.size() != getRecipeSize())
            return false;

        Object[] input = getRecipeInput();

        for (int i = 0; i < recipe.recipeItems.size(); i++)
        {
            Ingredient target = recipe.recipeItems.get(i);
            ItemStack source = (ItemStack) input[i];

            if (!target.apply(source))
                return false;
        }

        return true;
    }

    private boolean isOreRecipe()
    {
        return items.stream().anyMatch(RecipeInput::isOreClass);
    }

    private int getRecipeSize()
    {
        return items.size();
    }

    private boolean matchesInput(ShapelessOreRecipe recipe)
    {
        if (recipe.getIngredients().size() != getRecipeSize())
            return false;

        Object[] input = getRecipeInput();

        for (int i = 0; i < recipe.getIngredients().size(); i++)
        {
            Ingredient target = recipe.getIngredients().get(i);
            Object source = input[i];

            if (!ItemHelper.isSameRecipeInput(target, source))
                return false;
        }
        return true;
    }


    void addRecipe()
    {
        if (damage.length == 0)
            damage = new int[items.size()];

        Class<DamageableShapelessOreRecipe> recipeClass = JEICompatRegistry.getShapelessCraftingRecipeClass(recipeList);
        Constructor<DamageableShapelessOreRecipe> constructor = ReflectionHelper.getConstructor(recipeClass, ResourceLocation.class, int[].class, ItemStack.class, Object[].class);
        DamageableShapelessOreRecipe recipe = ReflectionHelper.newInstance(constructor, null, damage, result.getItemStack(), getInputForRecipe());

        if (recipe != null)
        {
            CraftingManagerCS4.addRecipe(recipeList, recipe);
        }
    }

    Object[] getInputForRecipe()
    {
        return items.stream()
                    .map(input -> input.isOreClass() ? input.getOreClass().getOreName() : input.getStack().getItemStack())
                    .toArray();
    }

    /**
     * Gets recipe input without the chars for the shape.
     */
    Object[] getRecipeInput()
    {
        return items.stream()
                    .map(input -> input.isOreClass() ? OreDictionary.getOres(input.getOreClass().getOreName()) : input.getStack().getItemStack())
                    .toArray();
    }
}