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);
  }
}

@@ -964,29 +964,31 @@
 utput%22;%0A  pr
-ivate
+otected
  final ItemS
@@ -1010,21 +1010,23 @@
 ut;%0A  pr
-ivate
+otected
  final I
@@ -1300,21 +1300,23 @@
  */%0A  pr
-ivate
+otected
  %3CT%3E T w
@@ -1794,21 +1794,23 @@
  */%0A  pr
-ivate
+otected
  void wi
@@ -1807,31 +1807,24 @@
 ed void with
-Correct
 HandlerV(int
@@ -3044,15 +3044,8 @@
 with
-Correct
 Hand
@@ -3508,37 +3508,39 @@
 alInterface%0A  pr
-ivate
+otected
  interface Handl
@@ -3605,35 +3605,34 @@
 slot, boolean is
-Out
+In
 put);%0A  %7D%0A%0A  @Fu
@@ -3657,13 +3657,15 @@
   pr
-ivate
+otected
  int
@@ -3754,19 +3754,18 @@
 olean is
-Out
+In
 put);%0A  

Make handler methods protected for subclassing