package com.lothrazar.cyclic.capability;

import net.minecraft.item.ItemStack;
import net.minecraftforge.items.ItemStackHandler;

public class ItemStackHandlerWrapper extends ItemStackHandler {

  private ItemStackHandler output;
  private ItemStackHandler input;

  public ItemStackHandlerWrapper(ItemStackHandler input, ItemStackHandler output) {
    super(input.getSlots() + output.getSlots());
    this.input = input;
    this.output = output;
  }

  @Override
  public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) {
    if (slot >= input.getSlots()) {
      return stack;
    }
    return input.insertItem(slot, stack, simulate);
  }

  @Override
  public ItemStack extractItem(int slot, int amount, boolean simulate) {
    if (slot >= output.getSlots()) {
      return ItemStack.EMPTY;
    }
    return output.extractItem(slot, amount, simulate);
  }
}

package com.lothrazar.cyclic.capability;

import net.minecraft.item.ItemStack;
import net.minecraft.nbt.CompoundNBT;
import net.minecraftforge.common.util.INBTSerializable;
import net.minecraftforge.items.IItemHandler;
import net.minecraftforge.items.IItemHandlerModifiable;
import net.minecraftforge.items.ItemStackHandler;

/**
 * Wraps two {@link ItemStackHandler}s: Input and Output.
 * Input's slots come first then the Output's slots come after.
 * Items can only be inserted into Input.
 * Items can only be extracted from Output.
 * Note that the above only applies to operations on the wrapper, the backing handlers are not restricted.
 * For persistence, either the backing {@link ItemStackHandler}s can be saved, or the wrapper itself.
 */
public class ItemStackHandlerWrapper implements IItemHandler, IItemHandlerModifiable, INBTSerializable<CompoundNBT> {
  public static final String NBT_INPUT = "Input";
  public static final String NBT_OUTPUT = "Output";
  private final ItemStackHandler input;
  private final ItemStackHandler output;

  public ItemStackHandlerWrapper(ItemStackHandler input, ItemStackHandler output) {
    this.input = input;
    this.output = output;
  }

  /**
   * Calls with the correct handler, slot for the handler and if it matches the input handler.
   */
  private <T> T withHandler(int externalSlot, HandlerCallback<T> callback) {
    int numInputSlots = input.getSlots();
    boolean isInput = externalSlot < numInputSlots;
    int internalSlot = isInput ? externalSlot : externalSlot - numInputSlots;
    ItemStackHandler handler = isInput ? input : output;
    return callback.apply(handler, internalSlot, isInput);
  }

  /**
   * For functions that return void.
   *
   * @see ItemStackHandlerWrapper#withHandler(int, HandlerCallback)
   */
  private void withCorrectHandlerV(int slot, HandlerCallbackVoid func) {
    withHandler(slot, (h, s, isInput) -> {
      func.apply(h, s, isInput);
      return false; // Because generics can't be void >.<
    });
  }

  @Override
  public int getSlots() {
    return input.getSlots() + output.getSlots();
  }

  @Override
  public ItemStack getStackInSlot(int slot) {
    return withHandler(slot, (h, s, isInput) -> h.getStackInSlot(s));
  }

  @Override
  public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) {
    return withHandler(slot, (h, s, isInput) ->
            isInput ? h.insertItem(s, stack, simulate) : stack
    );
  }

  @Override
  public ItemStack extractItem(int slot, int amount, boolean simulate) {
    return withHandler(slot, (h, s, isInput) ->
            isInput ? ItemStack.EMPTY : h.extractItem(s, amount, simulate)
    );
  }

  @Override
  public int getSlotLimit(int slot) {
    return withHandler(slot, (h, s, isInput) -> h.getSlotLimit(s));
  }

  @Override
  public boolean isItemValid(int slot, ItemStack stack) {
    return withHandler(slot, (h, s, isInput) ->
            isInput && h.isItemValid(s, stack)
    );
  }

  @Override
  public void setStackInSlot(int slot, ItemStack stack) {
    withCorrectHandlerV(slot, (h, s, isInput) -> h.setStackInSlot(s, stack));
  }

  @Override
  public CompoundNBT serializeNBT() {
    CompoundNBT cmp = new CompoundNBT();
    cmp.put(NBT_INPUT, input.serializeNBT());
    cmp.put(NBT_OUTPUT, output.serializeNBT());
    return cmp;
  }

  @Override
  public void deserializeNBT(CompoundNBT nbt) {
    input.deserializeNBT(nbt.getCompound(NBT_INPUT));
    output.deserializeNBT(nbt.getCompound(NBT_OUTPUT));
  }

  @FunctionalInterface
  private interface HandlerCallback<T> {
    T apply(ItemStackHandler handler, int slot, boolean isOutput);
  }

  @FunctionalInterface
  private interface HandlerCallbackVoid {
    void apply(ItemStackHandler handler, int slot, boolean isOutput);
  }
}

Fix second part of #1661 by fully implementing wrapper