// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.inventory.container;

import java.util.Iterator;
import com.hypixel.hytale.server.core.inventory.transaction.Transaction;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction;
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
import java.util.List;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.Collections;
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction;
import java.util.function.BiPredicate;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.inventory.transaction.SlotTransaction;
import com.hypixel.hytale.server.core.inventory.transaction.ActionType;
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import javax.annotation.Nonnull;

public class InternalContainerUtilItemStack
{
    protected static int testAddToExistingSlot(@Nonnull final ItemContainer abstractItemContainer, final short slot, final ItemStack itemStack, final int itemMaxStack, int testQuantityRemaining, final boolean filter) {
        final ItemStack slotItemStack = abstractItemContainer.internal_getSlot(slot);
        if (ItemStack.isEmpty(slotItemStack)) {
            return testQuantityRemaining;
        }
        if (!slotItemStack.isStackableWith(itemStack)) {
            return testQuantityRemaining;
        }
        if (filter && abstractItemContainer.cantAddToSlot(slot, itemStack, slotItemStack)) {
            return testQuantityRemaining;
        }
        final int quantity = slotItemStack.getQuantity();
        final int quantityAdjustment = Math.min(itemMaxStack - quantity, testQuantityRemaining);
        testQuantityRemaining -= quantityAdjustment;
        return testQuantityRemaining;
    }
    
    @Nonnull
    protected static ItemStackSlotTransaction internal_addToExistingSlot(@Nonnull final ItemContainer container, final short slot, @Nonnull final ItemStack itemStack, final int itemMaxStack, final boolean filter) {
        final ItemStack slotItemStack = container.internal_getSlot(slot);
        if (ItemStack.isEmpty(slotItemStack)) {
            return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, true, itemStack, itemStack);
        }
        if (!slotItemStack.isStackableWith(itemStack)) {
            return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, true, itemStack, itemStack);
        }
        if (filter && container.cantAddToSlot(slot, itemStack, slotItemStack)) {
            return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, true, itemStack, itemStack);
        }
        int quantityRemaining = itemStack.getQuantity();
        final int quantity = slotItemStack.getQuantity();
        final int quantityAdjustment = Math.min(itemMaxStack - quantity, quantityRemaining);
        final int newQuantity = quantity + quantityAdjustment;
        quantityRemaining -= quantityAdjustment;
        if (quantityAdjustment <= 0) {
            return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, true, itemStack, itemStack);
        }
        final ItemStack slotNew = slotItemStack.withQuantity(newQuantity);
        if (newQuantity > 0) {
            container.internal_setSlot(slot, slotNew);
        }
        else {
            container.internal_removeSlot(slot);
        }
        final ItemStack remainder = (quantityRemaining != itemStack.getQuantity()) ? itemStack.withQuantity(quantityRemaining) : itemStack;
        return new ItemStackSlotTransaction(true, ActionType.ADD, slot, slotItemStack, slotNew, null, false, false, filter, true, itemStack, remainder);
    }
    
    @Nonnull
    protected static ItemStackSlotTransaction internal_addToEmptySlot(@Nonnull final ItemContainer container, final short slot, @Nonnull final ItemStack itemStack, final int itemMaxStack, final boolean filter) {
        final ItemStack slotItemStack = container.internal_getSlot(slot);
        if (slotItemStack != null && !slotItemStack.isEmpty()) {
            return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, false, itemStack, itemStack);
        }
        if (filter && container.cantAddToSlot(slot, itemStack, slotItemStack)) {
            return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, false, false, filter, false, itemStack, itemStack);
        }
        int quantityRemaining = itemStack.getQuantity();
        final int quantityAdjustment = Math.min(itemMaxStack, quantityRemaining);
        quantityRemaining -= quantityAdjustment;
        final ItemStack slotNew = itemStack.withQuantity(quantityAdjustment);
        container.internal_setSlot(slot, slotNew);
        final ItemStack remainder = (itemStack.getQuantity() != quantityRemaining) ? itemStack.withQuantity(quantityRemaining) : itemStack;
        return new ItemStackSlotTransaction(true, ActionType.ADD, slot, slotItemStack, slotNew, null, false, false, filter, false, itemStack, remainder);
    }
    
    protected static int testAddToEmptySlots(@Nonnull final ItemContainer container, final ItemStack itemStack, final int itemMaxStack, int testQuantityRemaining, final boolean filter) {
        for (short i = 0; i < container.getCapacity() && testQuantityRemaining > 0; ++i) {
            final ItemStack slotItemStack = container.internal_getSlot(i);
            if (slotItemStack == null || slotItemStack.isEmpty()) {
                if (!filter || !container.cantAddToSlot(i, itemStack, slotItemStack)) {
                    final int quantityAdjustment = Math.min(itemMaxStack, testQuantityRemaining);
                    testQuantityRemaining -= quantityAdjustment;
                }
            }
        }
        return testQuantityRemaining;
    }
    
    protected static ItemStackSlotTransaction internal_addItemStackToSlot(@Nonnull final ItemContainer itemContainer, final short slot, @Nonnull final ItemStack itemStack, final boolean allOrNothing, final boolean filter) {
        ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity());
        return itemContainer.writeAction(() -> {
            final int quantityRemaining = itemStack.getQuantity();
            final ItemStack slotItemStack = itemContainer.internal_getSlot(slot);
            if (filter && itemContainer.cantAddToSlot(slot, itemStack, slotItemStack)) {
                return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStack, itemStack);
            }
            else if (slotItemStack == null) {
                itemContainer.internal_setSlot(slot, itemStack);
                return new ItemStackSlotTransaction(true, ActionType.ADD, slot, null, itemStack, null, allOrNothing, false, filter, false, itemStack, null);
            }
            else {
                final int quantity = slotItemStack.getQuantity();
                if (!itemStack.isStackableWith(slotItemStack)) {
                    return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStack, itemStack);
                }
                else {
                    final int quantityAdjustment = Math.min(slotItemStack.getItem().getMaxStack() - quantity, quantityRemaining);
                    final int newQuantity = quantity + quantityAdjustment;
                    final int quantityRemaining2 = quantityRemaining - quantityAdjustment;
                    if (allOrNothing && quantityRemaining2 > 0) {
                        return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStack, itemStack);
                    }
                    else if (quantityAdjustment <= 0) {
                        return new ItemStackSlotTransaction(false, ActionType.ADD, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStack, itemStack);
                    }
                    else {
                        final ItemStack newItemStack = slotItemStack.withQuantity(newQuantity);
                        itemContainer.internal_setSlot(slot, newItemStack);
                        final ItemStack remainder = itemStack.withQuantity(quantityRemaining2);
                        return new ItemStackSlotTransaction(true, ActionType.ADD, slot, slotItemStack, newItemStack, null, allOrNothing, false, filter, false, itemStack, remainder);
                    }
                }
            }
        });
    }
    
    @Nonnull
    protected static ItemStackSlotTransaction internal_setItemStackForSlot(@Nonnull final ItemContainer itemContainer, final short slot, final ItemStack itemStack, final boolean filter) {
        ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity());
        return itemContainer.writeAction(() -> {
            final ItemStack slotItemStack = itemContainer.internal_getSlot(slot);
            if (filter && itemContainer.cantAddToSlot(slot, itemStack, slotItemStack)) {
                return new ItemStackSlotTransaction(false, ActionType.SET, slot, slotItemStack, slotItemStack, null, false, false, filter, false, itemStack, itemStack);
            }
            else {
                final ItemStack oldItemStack = itemContainer.internal_setSlot(slot, itemStack);
                return new ItemStackSlotTransaction(true, ActionType.SET, slot, oldItemStack, itemStack, null, false, false, filter, false, itemStack, null);
            }
        });
    }
    
    @Nonnull
    protected static SlotTransaction internal_removeItemStackFromSlot(@Nonnull final ItemContainer itemContainer, final short slot, final boolean filter) {
        ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity());
        return itemContainer.writeAction(() -> {
            if (filter && itemContainer.cantRemoveFromSlot(slot)) {
                final ItemStack itemStack = itemContainer.internal_getSlot(slot);
                return new ItemStackSlotTransaction(false, ActionType.REMOVE, slot, itemStack, itemStack, null, false, false, filter, false, null, itemStack);
            }
            else {
                final ItemStack oldItemStack = itemContainer.internal_removeSlot(slot);
                return new SlotTransaction(true, ActionType.REMOVE, slot, oldItemStack, null, oldItemStack, false, false, false);
            }
        });
    }
    
    protected static ItemStackSlotTransaction internal_removeItemStackFromSlot(@Nonnull final ItemContainer itemContainer, final short slot, final int quantityToRemove, final boolean allOrNothing, final boolean filter) {
        ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity());
        ItemContainer.validateQuantity(quantityToRemove);
        return itemContainer.writeAction(() -> {
            final int quantityRemaining = quantityToRemove;
            if (filter && itemContainer.cantRemoveFromSlot(slot)) {
                final ItemStack itemStack = itemContainer.internal_getSlot(slot);
                return new ItemStackSlotTransaction(false, ActionType.REMOVE, slot, itemStack, itemStack, null, allOrNothing, false, filter, false, null, itemStack.withQuantity(quantityRemaining));
            }
            else {
                final ItemStack slotItemStack = itemContainer.internal_getSlot(slot);
                if (slotItemStack == null) {
                    return new ItemStackSlotTransaction(false, ActionType.REMOVE, slot, null, null, null, allOrNothing, false, filter, false, null, null);
                }
                else {
                    final int quantity = slotItemStack.getQuantity();
                    final int quantityAdjustment = Math.min(quantity, quantityRemaining);
                    final int newQuantity = quantity - quantityAdjustment;
                    final int quantityRemaining2 = quantityRemaining - quantityAdjustment;
                    if (allOrNothing && quantityRemaining2 > 0) {
                        return new ItemStackSlotTransaction(false, ActionType.REMOVE, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, null, slotItemStack.withQuantity(quantityRemaining2));
                    }
                    else {
                        final ItemStack itemStack2 = slotItemStack.withQuantity(newQuantity);
                        itemContainer.internal_setSlot(slot, itemStack2);
                        final ItemStack newStack = slotItemStack.withQuantity(quantityAdjustment);
                        final ItemStack remainder = slotItemStack.withQuantity(quantityRemaining2);
                        return new ItemStackSlotTransaction(true, ActionType.REMOVE, slot, slotItemStack, itemStack2, newStack, allOrNothing, false, filter, false, null, remainder);
                    }
                }
            }
        });
    }
    
    protected static ItemStackSlotTransaction internal_removeItemStackFromSlot(@Nonnull final ItemContainer itemContainer, final short slot, @Nullable final ItemStack itemStackToRemove, final int quantityToRemove, final boolean allOrNothing, final boolean filter) {
        return internal_removeItemStackFromSlot(itemContainer, slot, itemStackToRemove, quantityToRemove, allOrNothing, filter, (a, b) -> ItemStack.isStackableWith(a, b));
    }
    
    protected static ItemStackSlotTransaction internal_removeItemStackFromSlot(@Nonnull final ItemContainer itemContainer, final short slot, @Nullable final ItemStack itemStackToRemove, final int quantityToRemove, final boolean allOrNothing, final boolean filter, final BiPredicate<ItemStack, ItemStack> predicate) {
        ItemContainer.validateSlotIndex(slot, itemContainer.getCapacity());
        ItemContainer.validateQuantity(quantityToRemove);
        return itemContainer.writeAction(() -> {
            final int quantityRemaining = quantityToRemove;
            if (filter && itemContainer.cantRemoveFromSlot(slot)) {
                final ItemStack itemStack = itemContainer.internal_getSlot(slot);
                return new ItemStackSlotTransaction(false, ActionType.REMOVE, slot, itemStack, itemStack, null, allOrNothing, false, filter, false, itemStackToRemove, itemStackToRemove);
            }
            else {
                final ItemStack slotItemStack = itemContainer.internal_getSlot(slot);
                if ((slotItemStack == null && itemStackToRemove != null) || (slotItemStack != null && itemStackToRemove == null) || (slotItemStack != null && !predicate.test(slotItemStack, itemStackToRemove))) {
                    return new ItemStackSlotTransaction(false, ActionType.REMOVE, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStackToRemove, itemStackToRemove);
                }
                else if (slotItemStack == null) {
                    return new ItemStackSlotTransaction(true, ActionType.REMOVE, slot, null, null, null, allOrNothing, false, filter, false, itemStackToRemove, itemStackToRemove);
                }
                else {
                    final int quantity = slotItemStack.getQuantity();
                    final int quantityAdjustment = Math.min(quantity, quantityRemaining);
                    final int newQuantity = quantity - quantityAdjustment;
                    final int quantityRemaining2 = quantityRemaining - quantityAdjustment;
                    if (allOrNothing && quantityRemaining2 > 0) {
                        return new ItemStackSlotTransaction(false, ActionType.REMOVE, slot, slotItemStack, slotItemStack, null, allOrNothing, false, filter, false, itemStackToRemove, itemStackToRemove);
                    }
                    else {
                        final ItemStack itemStack2 = slotItemStack.withQuantity(newQuantity);
                        itemContainer.internal_setSlot(slot, itemStack2);
                        final ItemStack newStack = slotItemStack.withQuantity(quantityAdjustment);
                        final ItemStack remainder = itemStackToRemove.withQuantity(quantityRemaining2);
                        return new ItemStackSlotTransaction(true, ActionType.REMOVE, slot, slotItemStack, itemStack2, newStack, allOrNothing, false, filter, false, itemStackToRemove, remainder);
                    }
                }
            }
        });
    }
    
    protected static int testRemoveItemStackFromSlot(@Nonnull final ItemContainer container, final short slot, final ItemStack itemStack, final int testQuantityRemaining, final boolean filter) {
        return testRemoveItemStackFromSlot(container, slot, itemStack, testQuantityRemaining, filter, (a, b) -> ItemStack.isStackableWith(a, b));
    }
    
    protected static int testRemoveItemStackFromSlot(@Nonnull final ItemContainer container, final short slot, final ItemStack itemStack, int testQuantityRemaining, final boolean filter, final BiPredicate<ItemStack, ItemStack> predicate) {
        if (filter && container.cantRemoveFromSlot(slot)) {
            return testQuantityRemaining;
        }
        final ItemStack slotItemStack = container.internal_getSlot(slot);
        if (ItemStack.isEmpty(slotItemStack)) {
            return testQuantityRemaining;
        }
        if (!predicate.test(slotItemStack, itemStack)) {
            return testQuantityRemaining;
        }
        final int quantity = slotItemStack.getQuantity();
        final int quantityAdjustment = Math.min(quantity, testQuantityRemaining);
        testQuantityRemaining -= quantityAdjustment;
        return testQuantityRemaining;
    }
    
    protected static ItemStackTransaction internal_addItemStack(@Nonnull final ItemContainer itemContainer, @Nonnull final ItemStack itemStack, final boolean allOrNothing, final boolean fullStacks, final boolean filter) {
        final Item item = itemStack.getItem();
        if (item == null) {
            throw new IllegalArgumentException(itemStack.getItemId() + " is an invalid item!");
        }
        final int itemMaxStack = item.getMaxStack();
        return itemContainer.writeAction(() -> {
            if (allOrNothing) {
                int testQuantityRemaining = itemStack.getQuantity();
                if (!fullStacks) {
                    testQuantityRemaining = testAddToExistingItemStacks(itemContainer, itemStack, itemMaxStack, testQuantityRemaining, filter);
                }
                final int testQuantityRemaining2 = testAddToEmptySlots(itemContainer, itemStack, itemMaxStack, testQuantityRemaining, filter);
                if (testQuantityRemaining2 > 0) {
                    return new ItemStackTransaction(false, ActionType.ADD, itemStack, itemStack, allOrNothing, filter, Collections.emptyList());
                }
            }
            final ObjectArrayList<ItemStackSlotTransaction> list = new ObjectArrayList<ItemStackSlotTransaction>();
            ItemStack remaining = itemStack;
            if (!fullStacks) {
                ItemStackSlotTransaction transaction;
                for (short i = 0; i < itemContainer.getCapacity() && !ItemStack.isEmpty(remaining); remaining = transaction.getRemainder(), ++i) {
                    transaction = internal_addToExistingSlot(itemContainer, i, remaining, itemMaxStack, filter);
                    list.add(transaction);
                }
            }
            ItemStackSlotTransaction transaction2;
            for (short j = 0; j < itemContainer.getCapacity() && !ItemStack.isEmpty(remaining); remaining = transaction2.getRemainder(), ++j) {
                transaction2 = internal_addToEmptySlot(itemContainer, j, remaining, itemMaxStack, filter);
                list.add(transaction2);
            }
            return new ItemStackTransaction(true, ActionType.ADD, itemStack, remaining, allOrNothing, filter, list);
        });
    }
    
    protected static ListTransaction<ItemStackTransaction> internal_addItemStacks(@Nonnull final ItemContainer itemContainer, @Nullable final List<ItemStack> itemStacks, final boolean allOrNothing, final boolean fullStacks, final boolean filter) {
        if (itemStacks == null || itemStacks.isEmpty()) {
            return ListTransaction.getEmptyTransaction(true);
        }
        return itemContainer.writeAction(() -> {
            if (allOrNothing) {
                for (final ItemStack itemStack : itemStacks) {
                    final int itemMaxStack = itemStack.getItem().getMaxStack();
                    int testQuantityRemaining = itemStack.getQuantity();
                    if (!fullStacks) {
                        testQuantityRemaining = testAddToExistingItemStacks(itemContainer, itemStack, itemMaxStack, testQuantityRemaining, filter);
                    }
                    final int testQuantityRemaining2 = testAddToEmptySlots(itemContainer, itemStack, itemMaxStack, testQuantityRemaining, filter);
                    if (testQuantityRemaining2 > 0) {
                        return new ListTransaction(false, (List<Transaction>)itemStacks.stream().map(i -> new ItemStackTransaction(false, ActionType.ADD, itemStack, itemStack, allOrNothing, filter, Collections.emptyList())).collect(Collectors.toList()));
                    }
                }
            }
            final List<ItemStackTransaction> remainingItemStacks = new ObjectArrayList<ItemStackTransaction>();
            for (final ItemStack itemStack2 : itemStacks) {
                remainingItemStacks.add(internal_addItemStack(itemContainer, itemStack2, allOrNothing, fullStacks, filter));
            }
            return new ListTransaction(true, (List<Transaction>)remainingItemStacks);
        });
    }
    
    protected static ListTransaction<ItemStackSlotTransaction> internal_addItemStacksOrdered(@Nonnull final ItemContainer itemContainer, final short offset, @Nullable final List<ItemStack> itemStacks, final boolean allOrNothing, final boolean filter) {
        if (itemStacks == null || itemStacks.isEmpty()) {
            return ListTransaction.getEmptyTransaction(true);
        }
        ItemContainer.validateSlotIndex(offset, itemContainer.getCapacity());
        ItemContainer.validateSlotIndex((short)(offset + itemStacks.size()), itemContainer.getCapacity());
        return itemContainer.writeAction(() -> {
            if (allOrNothing) {
                short i = 0;
                while (i < itemStacks.size()) {
                    final short slot = (short)(offset + i);
                    final ItemStack itemStack = itemStacks.get(i);
                    final int itemMaxStack = itemStack.getItem().getMaxStack();
                    final int testQuantityRemaining = itemStack.getQuantity();
                    final int testQuantityRemaining2 = testAddToExistingSlot(itemContainer, slot, itemStack, itemMaxStack, testQuantityRemaining, filter);
                    if (testQuantityRemaining2 > 0) {
                        final List<ItemStackSlotTransaction> list = new ObjectArrayList<ItemStackSlotTransaction>();
                        for (short i2 = 0; i2 < itemStacks.size(); ++i2) {
                            final short islot = (short)(offset + i2);
                            list.add(new ItemStackSlotTransaction(false, ActionType.ADD, islot, null, null, null, allOrNothing, false, filter, false, itemStack, itemStack));
                        }
                        return new ListTransaction(false, (List<Transaction>)list);
                    }
                    else {
                        ++i;
                    }
                }
            }
            final List<ItemStackSlotTransaction> remainingItemStacks = new ObjectArrayList<ItemStackSlotTransaction>();
            for (short j = 0; j < itemStacks.size(); ++j) {
                final short slot2 = (short)(offset + j);
                remainingItemStacks.add(internal_addItemStackToSlot(itemContainer, slot2, itemStacks.get(j), allOrNothing, filter));
            }
            return new ListTransaction(true, (List<Transaction>)remainingItemStacks);
        });
    }
    
    protected static int testAddToExistingItemStacks(@Nonnull final ItemContainer container, final ItemStack itemStack, final int itemMaxStack, int testQuantityRemaining, final boolean filter) {
        for (short i = 0; i < container.getCapacity() && testQuantityRemaining > 0; testQuantityRemaining = testAddToExistingSlot(container, i, itemStack, itemMaxStack, testQuantityRemaining, filter), ++i) {}
        return testQuantityRemaining;
    }
    
    protected static ItemStackTransaction internal_removeItemStack(@Nonnull final ItemContainer itemContainer, @Nonnull final ItemStack itemStack, final boolean allOrNothing, final boolean filter) {
        final Item item = itemStack.getItem();
        if (item == null) {
            throw new IllegalArgumentException(itemStack.getItemId() + " is an invalid item!");
        }
        return itemContainer.writeAction(() -> {
            if (allOrNothing) {
                final int testQuantityRemaining = testRemoveItemStackFromItems(itemContainer, itemStack, itemStack.getQuantity(), filter);
                if (testQuantityRemaining > 0) {
                    return new ItemStackTransaction(false, ActionType.REMOVE, itemStack, itemStack, allOrNothing, filter, Collections.emptyList());
                }
            }
            final ObjectArrayList<ItemStackSlotTransaction> transactions = new ObjectArrayList<ItemStackSlotTransaction>();
            int quantityRemaining = itemStack.getQuantity();
            for (short i = 0; i < itemContainer.getCapacity() && quantityRemaining > 0; ++i) {
                final ItemStack slotItemStack = itemContainer.internal_getSlot(i);
                if (!ItemStack.isEmpty(slotItemStack)) {
                    if (!(!slotItemStack.isStackableWith(itemStack))) {
                        final ItemStackSlotTransaction transaction = internal_removeItemStackFromSlot(itemContainer, i, quantityRemaining, false, filter);
                        transactions.add(transaction);
                        quantityRemaining = ((transaction.getRemainder() != null) ? transaction.getRemainder().getQuantity() : 0);
                    }
                }
            }
            final ItemStack remainder = (quantityRemaining > 0) ? itemStack.withQuantity(quantityRemaining) : null;
            return new ItemStackTransaction(true, ActionType.REMOVE, itemStack, remainder, allOrNothing, filter, transactions);
        });
    }
    
    protected static ListTransaction<ItemStackTransaction> internal_removeItemStacks(@Nonnull final ItemContainer itemContainer, @Nullable final List<ItemStack> itemStacks, final boolean allOrNothing, final boolean filter) {
        if (itemStacks == null || itemStacks.isEmpty()) {
            return ListTransaction.getEmptyTransaction(true);
        }
        final Iterator<ItemStack> iterator = itemStacks.iterator();
        ItemStack itemStack = null;
        while (iterator.hasNext()) {
            itemStack = iterator.next();
            final Item item = itemStack.getItem();
            if (item == null) {
                throw new IllegalArgumentException(itemStack.getItemId() + " is an invalid item!");
            }
        }
        return itemContainer.writeAction(() -> {
            if (allOrNothing) {
                for (final ItemStack itemStack2 : itemStacks) {
                    final int testQuantityRemaining = testRemoveItemStackFromItems(itemContainer, itemStack2, itemStack2.getQuantity(), filter);
                    if (testQuantityRemaining > 0) {
                        return new ListTransaction(false, (List<Transaction>)itemStacks.stream().map(i -> new ItemStackTransaction(false, ActionType.ADD, itemStack, itemStack, allOrNothing, filter, Collections.emptyList())).collect(Collectors.toList()));
                    }
                }
            }
            final List<ItemStackTransaction> transactions = new ObjectArrayList<ItemStackTransaction>();
            for (short i = 0; i < itemStacks.size(); ++i) {
                transactions.add(internal_removeItemStack(itemContainer, itemStacks.get(i), allOrNothing, filter));
            }
            return new ListTransaction(true, (List<Transaction>)transactions);
        });
    }
    
    protected static int testRemoveItemStackFromItems(@Nonnull final ItemContainer container, final ItemStack itemStack, int testQuantityRemaining, final boolean filter) {
        for (short i = 0; i < container.getCapacity() && testQuantityRemaining > 0; ++i) {
            if (!filter || !container.cantRemoveFromSlot(i)) {
                final ItemStack slotItemStack = container.internal_getSlot(i);
                if (!ItemStack.isEmpty(slotItemStack)) {
                    if (slotItemStack.isStackableWith(itemStack)) {
                        final int quantity = slotItemStack.getQuantity();
                        final int quantityAdjustment = Math.min(quantity, testQuantityRemaining);
                        testQuantityRemaining -= quantityAdjustment;
                    }
                }
            }
        }
        return testQuantityRemaining;
    }
    
    protected static TestRemoveItemSlotResult testRemoveItemStackSlotFromItems(@Nonnull final ItemContainer container, final ItemStack itemStack, final int testQuantityRemaining, final boolean filter) {
        return testRemoveItemStackSlotFromItems(container, itemStack, testQuantityRemaining, filter, (a, b) -> ItemStack.isStackableWith(a, b));
    }
    
    protected static TestRemoveItemSlotResult testRemoveItemStackSlotFromItems(@Nonnull final ItemContainer container, final ItemStack itemStack, final int testQuantityRemaining, final boolean filter, final BiPredicate<ItemStack, ItemStack> predicate) {
        final TestRemoveItemSlotResult result = new TestRemoveItemSlotResult(testQuantityRemaining);
        for (short i = 0; i < container.getCapacity() && result.quantityRemaining > 0; ++i) {
            if (!filter || !container.cantRemoveFromSlot(i)) {
                final ItemStack slotItemStack = container.internal_getSlot(i);
                if (!ItemStack.isEmpty(slotItemStack)) {
                    if (predicate.test(slotItemStack, itemStack)) {
                        final int quantity = slotItemStack.getQuantity();
                        final int quantityAdjustment = Math.min(quantity, result.quantityRemaining);
                        final TestRemoveItemSlotResult testRemoveItemSlotResult = result;
                        testRemoveItemSlotResult.quantityRemaining -= quantityAdjustment;
                        result.picked.put(i, quantityAdjustment);
                    }
                }
            }
        }
        return result;
    }
}
