package cubex2.cs4.util;

import com.google.common.collect.Lists;
import cubex2.cs4.api.OreClass;
import cubex2.cs4.api.RecipeInput;
import cubex2.cs4.api.WrappedItemStack;
import cubex2.cs4.plugins.vanilla.Attribute;
import cubex2.cs4.plugins.vanilla.BlockDrop;
import net.minecraft.creativetab.CreativeTabs;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.crafting.Ingredient;
import net.minecraft.util.NonNullList;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.IFluidTank;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.oredict.OreDictionary;

import java.lang.reflect.Field;
import java.util.*;

public class ItemHelper
{
    private static Field tabLabelField;

    public static Optional<CreativeTabs> findCreativeTab(String tabLabel)
    {
        return Arrays.stream(CreativeTabs.CREATIVE_TAB_ARRAY)
                     .filter(tab -> tab != null && Objects.equals(getTabLabel(tab), tabLabel))
                     .findFirst();
    }

    private static String getTabLabel(CreativeTabs tab)
    {
        if (tabLabelField == null)
        {
            tabLabelField = ReflectionHelper.findField(CreativeTabs.class, "tabLabel", "field_78034_o", "o");
            tabLabelField.setAccessible(true);
        }

        try
        {
            return (String) tabLabelField.get(tab);
        } catch (IllegalAccessException e)
        {
            e.printStackTrace();
        }

        return null;
    }

    public static CreativeTabs[] createCreativeTabs(Attribute<String> tabLabels, int[] subtypes)
    {
        return Arrays.stream(subtypes).mapToObj(tabLabels::get)
                     .filter(Optional::isPresent)
                     .map(Optional::get)
                     .map(ItemHelper::findCreativeTab)
                     .filter(Optional::isPresent)
                     .map(Optional::get)
                     .distinct()
                     .toArray(CreativeTabs[]::new);
    }

    public static NonNullList<ItemStack> createSubItems(Item item, CreativeTabs creativeTab, Attribute<String> tabLabels, int[] subtypes)
    {
        NonNullList<ItemStack> list = NonNullList.create();

        if (item.getHasSubtypes())
        {
            for (int meta : subtypes)
            {
                tabLabels.get(meta)
                         .ifPresent(tabLabel ->
                                    {
                                        if (creativeTab == null || creativeTab == CreativeTabs.SEARCH || Objects.equals(tabLabel, getTabLabel(creativeTab)))
                                        {
                                            list.add(new ItemStack(item, 1, meta));
                                        }
                                    });
            }
        } else
        {
            list.add(new ItemStack(item, 1, 0));
        }

        return list;
    }

    @SuppressWarnings("unchecked")
    public static boolean isSameRecipeInput(Ingredient target, Object input)
    {
        if (input instanceof String)
        {
            NonNullList<ItemStack> ores = OreDictionary.getOres(input.toString());
            return ores.stream().allMatch(target::apply);
        } else if (input instanceof ItemStack)
        {
            return target.apply((ItemStack) input);
        } else if (input instanceof NonNullList)
        {
            NonNullList<ItemStack> items = (NonNullList<ItemStack>) input;
            return items.stream().anyMatch(target::apply);
        } else
        {
            throw new IllegalArgumentException("Invalid input: " + input);
        }
    }

    public static boolean isSameStackForFuel(ItemStack fuel, ItemStack stack)
    {
        boolean itemEqual = stack.getMetadata() == OreDictionary.WILDCARD_VALUE
                            ? fuel.isItemEqualIgnoreDurability(stack)
                            : fuel.isItemEqual(stack);

        boolean nbtEqual = !stack.hasTagCompound() || ItemStack.areItemStackTagsEqual(stack, fuel);

        return itemEqual && nbtEqual;
    }

    public static boolean stackMatchesStackOrOreClass(ItemStack target, Object input)
    {
        if (input instanceof ItemStack)
        {
            return isSameStackForFuel(target, (ItemStack) input);
        }

        if (input instanceof String)
        {
            return OreDictionary.containsMatch(false, OreDictionary.getOres((String) input), target);
        }

        return false;
    }

    public static boolean stackMatchesRecipeInput(ItemStack stack, RecipeInput input, boolean checkCount)
    {
        if (input.isItemStack())
        {
            ItemStack inputStack = input.getStack().getItemStack();
            if (OreDictionary.itemMatches(inputStack, stack, false)
                && (!checkCount || inputStack.getCount() <= stack.getCount()))
                return true;
        } else
        {
            OreClass oreClass = input.getOreClass();
            if (OreDictionary.containsMatch(false, OreDictionary.getOres(oreClass.getOreName()), stack)
                && (!checkCount || oreClass.getAmount() <= stack.getCount()))
                return true;
        }

        return false;
    }

    public static void removeInputsFromInventory(List<RecipeInput> inputs, IItemHandlerModifiable inv, int start, int numSlots)
    {
        List<RecipeInput> remaining = Lists.newLinkedList(inputs);

        for (int i = start; i < start + numSlots; i++)
        {
            ItemStack stack = inv.getStackInSlot(i);
            for (Iterator<RecipeInput> iterator = remaining.iterator(); iterator.hasNext(); )
            {
                RecipeInput input = iterator.next();
                if (stackMatchesRecipeInput(stack, input, true))
                {
                    extractInput(input, inv, i);

                    iterator.remove();
                    break;
                }
            }
        }
    }

    static void extractInput(RecipeInput input, IItemHandlerModifiable from, int slot)
    {
        if (input.isOreClass())
        {
            from.extractItem(slot, input.getOreClass().getAmount(), false);
        } else
        {
            ItemStack toExtract = input.getStack().getItemStack();
            from.extractItem(slot, toExtract.getCount(), false);
        }
    }

    public static boolean fluidStackEqual(FluidStack stack, FluidStack input, boolean checkCount)
    {
        if (stack == null && input == null)
            return true;
        if (stack == null ^ input == null)
            return false;

        return stack.isFluidEqual(input) && (!checkCount || input.amount <= stack.amount);
    }

    public static boolean fluidStackEqual(FluidStack stack1, FluidStack stack2)
    {
        if (stack1 == null && stack2 == null)
            return true;
        if (stack1 == null ^ stack2 == null)
            return false;

        return stack1.isFluidEqual(stack2) && stack2.amount == stack1.amount;
    }

    public static void extractFluidsFromTanks(List<IFluidTank> tanks, List<FluidStack> fluids)
    {
        LinkedList<IFluidTank> remaining = Lists.newLinkedList(tanks);

        for (FluidStack stack : fluids)
        {
            for (Iterator<IFluidTank> iterator = remaining.iterator(); iterator.hasNext(); )
            {
                IFluidTank tank = iterator.next();
                if (tank.getFluid() != null && tank.getFluid().getFluid().getName().equals(stack.getFluid().getName()))
                {
                    FluidStack drained = tank.drain(stack.amount, false);
                    if (drained != null && drained.amount == stack.amount)
                    {
                        tank.drain(stack.amount, true);
                        iterator.remove();
                        break;
                    }
                }
            }
        }
    }

    public static ItemStack copyStack(ItemStack stack, int newAmount)
    {
        ItemStack copy = stack.copy();
        copy.setCount(newAmount);
        return copy;
    }

    public static List<ItemStack> getDroppedStacks(BlockDrop[] drops, int fortune)
    {
        List<ItemStack> result = Lists.newArrayList();

        for (BlockDrop drop : drops)
        {
            WrappedItemStack wrappedItemStack = drop.getItem();
            ItemStack droppedStack = wrappedItemStack.getItemStack();

            if (!droppedStack.isEmpty())
            {
                int amount = drop.getAmount(fortune);
                if (amount > 0)
                {
                    result.add(ItemHelper.copyStack(droppedStack, amount));
                }
            }
        }

        return result;
    }
}