public void deny(ItemStack template) {
   Key key = Key.of(template);
   Element e = _elements.get(key);
   log.debug("deny", "template", template, "key", key, "e", e);
   if (e == null || !e.whitelisted()) return;
   log.debug("deny: whitelisted: removing...", "empty", e.empty());
   e.whitelisted(false);
   _whitelist.remove(key);
   if (e.empty()) _elements.remove(key);
 }
 public void dump() {
   log.debug("DUMP", "total", _total);
   for (Element e : _elements.values())
     log.debug(
         "DUMP: element",
         "key",
         e.key().stack(),
         "whitelisted",
         e.whitelisted(),
         "count",
         e.count());
 }
 public ItemStack put(ItemStack stack) {
   log.debug("put", "stack", stack, "size", stack.stackSize);
   if (stack == null) return null;
   Key key = Key.of(stack);
   Element e = _elements.get(key);
   if (e == null) return stack;
   int amount = stack.stackSize;
   amount = Math.min(amount, _maxTotal - _total);
   amount = e.put(amount, _maxPerKey);
   if (amount == 0) return stack;
   _total += amount;
   log.debug("put: done", "amount", amount);
   return stack.splitStack(amount);
 }
 public ItemStack take(int amount) {
   log.debug("take", "amount", amount);
   for (Element e : _elements.values()) {
     if (e.empty()) continue;
     int taken = e.take(amount);
     if (taken > 0) {
       ItemStack stack = e.key().stack().copy();
       stack.stackSize = taken;
       _total = Math.max(0, _total - taken);
       if (e.empty() && !_whitelist.contains(e.key())) _elements.remove(e.key());
       log.debug("take: done", "amount", stack.stackSize);
       return stack;
     }
   }
   return null;
 }
 public int delete(ItemStack template, int amount) {
   Key key = Key.of(template);
   Element e = _elements.get(key);
   log.debug("take", "template", template, "key", key, "e", e);
   if (e == null) return 0;
   int taken = e.take(amount);
   _total = Math.max(0, _total - taken);
   return taken;
 }
 public boolean allowed(ItemStack stack) {
   log.trace("allowed", "template", stack, "size", stack == null ? 0 : stack.stackSize);
   Key key = Key.of(stack);
   Element e = _elements.get(key);
   if (e == null || !e.whitelisted()) return false;
   if (e.count() + stack.stackSize > _maxPerKey) return false;
   if (_total + stack.stackSize > _maxTotal) return false;
   return true;
 }
 @Override // IInventory
 public ItemStack decrStackSize(int slot, int amount) {
   log.debug("decrStackSize", "slot", slot, "amount", amount);
   switch (slot) {
     case SLOT_IMPORT:
     default:
       return null;
     case SLOT_EXPORT:
       return _inventory.takeExport(amount);
   }
 }
 public boolean allow(ItemStack template) {
   log.debug("allow", "template", template);
   if (_whitelist.size() >= _maxKeys) return false;
   Key key = Key.of(template);
   Element e = _elements.get(key);
   if (e == null) {
     e = Element.of(key, 0, true);
     _elements.put(key, e);
     _whitelist.add(key);
   } else if (!e.whitelisted()) {
     e.whitelisted(true);
     _whitelist.add(key);
   }
   return true;
 }
 @Override // IInventory
 public void setInventorySlotContents(int slot, ItemStack stack) {
   log.debug("setInventorySlotContents", "slot", slot, "stack", stack);
   switch (slot) {
     case SLOT_IMPORT:
       if (stack == null || stack.stackSize == 0) return;
       _inventory.put(stack);
       markDirty();
       break;
     case SLOT_EXPORT:
       _inventory.setExport(stack);
       break;
     default:
       break;
   }
 }
 public boolean has(ItemStack template) {
   Key key = Key.of(template);
   Element e = _elements.get(key);
   log.debug("has", "template", template, "key", key, "e", e);
   return e != null && !e.empty();
 }
 public int put(int amount, int max) {
   log.debug("put", "amount", amount, "max", max, "count", _count);
   amount = Math.min(amount, max - _count);
   _count += amount;
   return amount;
 }
public class BlocksmithMachineTile extends TileEntity
    implements ITilePacketHandler, ITilePacketDistributor, IInventory, ISidedInventory {
  private static final Log log = Log.create(BlocksmithMachineTile.class);

  public static final String NAME = "blocksmithMachine";

  private static final int SLOT_COUNT = 2;
  private static final int SLOT_STACK_LIMIT = 64;

  private static final int SLOT_IMPORT = 0;
  private static final int SLOT_EXPORT = 1;

  private static final int INVENTORY_MAX_KEYS = 16;
  private static final int INVENTORY_MAX_ITEMS_PER_KEY = 512;
  private static final int INVENTORY_MAX_EXPORTS = 2048;

  private static final int[] ACCESSIBLE_SLOTS = cacheAccessibleSlots();

  private static int[] cacheAccessibleSlots() {
    int[] slots = new int[SLOT_COUNT];
    for (int i = 0; i < SLOT_COUNT; ++i) slots[i] = i;
    return slots;
  }

  private final TilePacketDistributorAdapter _packetAdapter = new TilePacketDistributorAdapter();
  //    private final Subscribee<Counts> _countSubscribee = new Subscribee<>();

  private final Inventory _inventory = new Inventory();

  private PendingStack _pending = null;

  public static void load() {
    GameRegistry.registerTileEntity(BlocksmithMachineTile.class, NAME);
  }

  public Inventory inventory() {
    return _inventory;
  }

  @Override // ITilePacketHandler
  public void handleTilePacket(IMessage msg, EntityPlayer player, Side side) {
    _packetAdapter.post(msg, player, side);
  }

  @Override // ITilePacketHandler
  public void subscribeToTilePackets(ITilePacketHandler handler) {
    _packetAdapter.subscribe(handler);
  }

  @Override // ITilePacketHandler
  public void unsubscribeFromTilePackets(ITilePacketHandler handler) {
    _packetAdapter.unsubscribe(handler);
  }

  @Override // TileEntity
  public void updateEntity() {
    if (worldObj.isRemote) return;
    _inventory.fillExport();
  }

  @Override // TileEntity
  public void onDataPacket(NetworkManager net, S35PacketUpdateTileEntity pkt) {
    _inventory.fromNbt(pkt.func_148857_g());
  }

  @Override // TileEntity
  public Packet getDescriptionPacket() {
    return new S35PacketUpdateTileEntity(xCoord, yCoord, zCoord, 0, _inventory.toNbt());
  }

  public void syncTo(EntityPlayer player) {
    if (worldObj.isRemote) return;
    if (EntityPlayer.class.isAssignableFrom(player.getClass()))
      ((EntityPlayerMP) player).playerNetServerHandler.sendPacket(getDescriptionPacket());
  }

  @Override // TileEntity
  public void readFromNBT(NBTTagCompound tag) {
    super.readFromNBT(tag);
    _inventory.fromNbt(tag.getCompoundTag("inventory"));
  }

  @Override // TileEntity
  public void writeToNBT(NBTTagCompound tag) {
    super.writeToNBT(tag);
    tag.setTag("inventory", _inventory.toNbt());
  }

  // TileEntity
  @Override
  public void validate() {
    if (worldObj.isRemote) return;
  }

  @Override
  public void invalidate() {}

  @Override
  public void onChunkUnload() {}

  public void dump() {
    _inventory.dump();
  }

  // IInventory
  @Override
  public boolean hasCustomInventoryName() {
    return true;
  }

  @Override
  public String getInventoryName() {
    return "blocksmithMachineInventory";
  }

  @Override
  public void openInventory() {}

  @Override
  public void closeInventory() {}

  @Override
  public int getSizeInventory() {
    return SLOT_COUNT;
  }

  @Override
  public int getInventoryStackLimit() {
    return SLOT_STACK_LIMIT;
  }

  @Override
  public boolean isUseableByPlayer(EntityPlayer p) {
    return true;
  }

  @Override // IInventory
  public ItemStack getStackInSlot(int slot) {
    switch (slot) {
      case SLOT_IMPORT:
      default:
        return null;
      case SLOT_EXPORT:
        return _inventory.getExport();
    }
  }

  @Override // IInventory
  public ItemStack decrStackSize(int slot, int amount) {
    log.debug("decrStackSize", "slot", slot, "amount", amount);
    switch (slot) {
      case SLOT_IMPORT:
      default:
        return null;
      case SLOT_EXPORT:
        return _inventory.takeExport(amount);
    }
  }

  @Override // IInventory
  public ItemStack getStackInSlotOnClosing(int slot) {
    log.debug("getStackInSlotOnClosing", "slot", slot);
    return null;
  }

  @Override // IInventory
  public void setInventorySlotContents(int slot, ItemStack stack) {
    log.debug("setInventorySlotContents", "slot", slot, "stack", stack);
    switch (slot) {
      case SLOT_IMPORT:
        if (stack == null || stack.stackSize == 0) return;
        _inventory.put(stack);
        markDirty();
        break;
      case SLOT_EXPORT:
        _inventory.setExport(stack);
        break;
      default:
        break;
    }
  }

  @Override // IInventory
  public boolean isItemValidForSlot(int slot, ItemStack stack) {
    switch (slot) {
      case SLOT_IMPORT:
        return _inventory.allowed(stack);
      case SLOT_EXPORT:
      default:
        return false;
    }
  }

  @Override // ISidedInventory
  public int[] getAccessibleSlotsFromSide(int side) {
    return ACCESSIBLE_SLOTS;
  }

  @Override // ISidedInventory
  public boolean canInsertItem(int slot, ItemStack stack, int side) {
    switch (slot) {
      case SLOT_IMPORT:
        return _inventory.allowed(stack);
      case SLOT_EXPORT:
      default:
        return false;
    }
  }

  @Override // ISidedInventory
  public boolean canExtractItem(int slot, ItemStack stack, int side) {
    switch (slot) {
      case SLOT_IMPORT:
      default:
        return false;
      case SLOT_EXPORT:
        return ItemHelper.itemsIdentical(_inventory.getExport(), stack);
    }
  }

  public class PendingStack {
    public ItemStack stack;

    public PendingStack(ItemStack stack) {
      this.stack = stack;
    }

    public void done(boolean creative) {
      if (_pending != null) {
        _pending = null;
        if (!creative) {
          if (_inventory.delete(stack, 1) > 0) markDirty();
        }
      }
    }

    public void abort() {
      _pending = null;
    }
  }

  public PendingStack pending(ItemStack template, boolean creative) {
    if (_pending != null) return null;
    if (creative && !_inventory.allowed(template)) return null;
    if (!creative && !_inventory.has(template)) return null;
    template = template.copy();
    template.stackSize = 1;
    return _pending = new PendingStack(template);
  }

  private static class Key {
    private final ItemStack _stack;

    private Key(ItemStack stack) {
      _stack = stack.copy();
      _stack.stackSize = 0;
    }

    public static Key of(ItemStack stack) {
      return new Key(stack);
    }

    public ItemStack stack() {
      return _stack;
    }

    @Override
    public boolean equals(Object o) {
      if (o == null || !Key.class.isAssignableFrom(o.getClass())) return false;
      return ItemHelper.itemsIdentical(_stack, ((Key) o).stack());
    }

    @Override
    public int hashCode() {
      return Objects.hash(_stack.getItem(), _stack.getItemDamage(), _stack.getTagCompound());
    }

    public NBTTagCompound toNbt() {
      return _stack.writeToNBT(new NBTTagCompound());
    }

    public static Key fromNbt(NBTTagCompound tag) {
      return Key.of(ItemHelper.readItemStackFromNBT(tag));
    }
  }

  private static class Element {
    private final Key _key;
    private int _count;
    private boolean _whitelisted;

    private Element(Key key, int count, boolean whitelisted) {
      _key = key;
      _count = count;
      _whitelisted = whitelisted;
    }

    public static Element of(Key key, int count, boolean whitelisted) {
      return new Element(key, count, whitelisted);
    }

    public Key key() {
      return _key;
    }

    public int count() {
      return _count;
    }

    public boolean whitelisted() {
      return _whitelisted;
    }

    public Element whitelisted(boolean whitelisted) {
      _whitelisted = whitelisted;
      return this;
    }

    public boolean empty() {
      return _count == 0;
    }

    public boolean full(int max) {
      return _count >= max;
    }

    public int get(int amount) {
      return Math.min(_key.stack().getMaxStackSize(), Math.min(amount, _count));
    }

    public int take(int amount) {
      amount = get(amount);
      _count -= amount;
      return amount;
    }

    public int put(int amount, int max) {
      log.debug("put", "amount", amount, "max", max, "count", _count);
      amount = Math.min(amount, max - _count);
      _count += amount;
      return amount;
    }

    public NBTTagCompound toNbt() {
      NBTTagCompound tag = new NBTTagCompound();
      tag.setTag("key", _key.toNbt());
      tag.setInteger("count", _count);
      tag.setBoolean("whitelisted", _whitelisted);
      return tag;
    }

    public static Element fromNbt(NBTTagCompound tag) {
      return Element.of(
          Key.fromNbt(tag.getCompoundTag("key")),
          tag.getInteger("count"),
          tag.getBoolean("whitelisted"));
    }
  }

  public static class ItemMap {
    private final Map<Key, Integer> _elements = new HashMap<>();
    private final int _max;

    private int _size;

    public ItemMap(int max) {
      _max = max;
    }

    public int size() {
      return _size;
    }

    public boolean empty() {
      return _size == 0;
    }

    public void clear() {
      _elements.clear();
      _size = 0;
    }

    /** Inserts the target stack and returns what didn't fit. */
    public ItemStack put(ItemStack stack) {
      int amount = Math.min(_max - _size, stack.stackSize);
      if (amount <= 0) return stack;
      Key k = Key.of(stack);
      Integer current = _elements.get(k);
      _elements.put(k, current == null ? amount : current + amount);
      _size += amount;
      stack.splitStack(amount);
      return stack.stackSize > 0 ? stack : null;
    }

    public ItemStack take() {
      if (_elements.isEmpty()) return null;
      Map.Entry<Key, Integer> e = _elements.entrySet().iterator().next();
      return take(_elements.keySet().iterator().next().stack(), SLOT_STACK_LIMIT);
    }

    public ItemStack take(ItemStack template, int amount) {
      Key k = Key.of(template);
      Integer count = _elements.get(k);
      if (count == null) return null;
      ItemStack stack = k.stack().copy();
      stack.stackSize =
          Math.min(stack.getMaxStackSize(), Math.min(SLOT_STACK_LIMIT, Math.min(amount, count)));
      _size -= stack.stackSize;
      if (stack.stackSize >= count) _elements.remove(k);
      else _elements.put(k, count - stack.stackSize);
      return stack;
    }

    public NBTTagCompound toNbt() {
      NBTTagCompound tag = new NBTTagCompound();
      NBTTagList elements = new NBTTagList();
      for (Map.Entry<Key, Integer> e : _elements.entrySet()) {
        NBTTagCompound element = new NBTTagCompound();
        element.setTag("key", e.getKey().toNbt());
        element.setInteger("count", e.getValue());
        elements.appendTag(element);
      }
      tag.setTag("elements", elements);
      tag.setInteger("size", _size);
      return tag;
    }

    public void fromNbt(NBTTagCompound tag) {
      clear();
      NBTTagList elements = tag.getTagList("elements", Constants.NBT.TAG_COMPOUND);
      for (int i = 0; i < elements.tagCount(); ++i) {
        NBTTagCompound element = elements.getCompoundTagAt(i);
        Key k = Key.fromNbt(element.getCompoundTag("key"));
        Integer count = element.getInteger("count");
        _elements.put(k, count);
      }
      _size = tag.getInteger("size");
    }
  }

  public static class Inventory {
    private final Map<Key, Element> _elements = new HashMap<>();
    private final Set<Key> _whitelist = new HashSet<>();
    private final ItemMap _exportable = new ItemMap(INVENTORY_MAX_EXPORTS);

    private final int _maxKeys;
    private final int _maxPerKey;
    private final int _maxTotal;

    private int _total;
    private ItemStack _export;

    private Inventory() {
      _maxKeys = INVENTORY_MAX_KEYS;
      _maxPerKey = INVENTORY_MAX_ITEMS_PER_KEY;
      _maxTotal = _maxKeys * _maxPerKey;
      _total = 0;
    }

    public int maxKeys() {
      return _maxKeys;
    }

    public int maxPerKey() {
      return _maxPerKey;
    }

    public int maxTotal() {
      return _maxTotal;
    }

    public int total() {
      return _total;
    }

    public Inventory clear() {
      _elements.clear();
      _whitelist.clear();
      _exportable.clear();
      _export = null;
      _total = 0;
      return this;
    }

    public Collection<ItemStack> allowed() {
      List<ItemStack> list = new LinkedList<>();
      for (Key k : _whitelist) list.add(k.stack());
      return list;
    }

    public boolean allowed(ItemStack stack) {
      log.trace("allowed", "template", stack, "size", stack == null ? 0 : stack.stackSize);
      Key key = Key.of(stack);
      Element e = _elements.get(key);
      if (e == null || !e.whitelisted()) return false;
      if (e.count() + stack.stackSize > _maxPerKey) return false;
      if (_total + stack.stackSize > _maxTotal) return false;
      return true;
    }

    public boolean allow(ItemStack template) {
      log.debug("allow", "template", template);
      if (_whitelist.size() >= _maxKeys) return false;
      Key key = Key.of(template);
      Element e = _elements.get(key);
      if (e == null) {
        e = Element.of(key, 0, true);
        _elements.put(key, e);
        _whitelist.add(key);
      } else if (!e.whitelisted()) {
        e.whitelisted(true);
        _whitelist.add(key);
      }
      return true;
    }

    public void deny(ItemStack template) {
      Key key = Key.of(template);
      Element e = _elements.get(key);
      log.debug("deny", "template", template, "key", key, "e", e);
      if (e == null || !e.whitelisted()) return;
      log.debug("deny: whitelisted: removing...", "empty", e.empty());
      e.whitelisted(false);
      _whitelist.remove(key);
      if (e.empty()) _elements.remove(key);
    }

    public ItemStack get() {
      for (Element e : _elements.values()) {
        if (e.empty()) continue;
        int gotten = e.get(Integer.MAX_VALUE);
        if (gotten > 0) {
          ItemStack stack = e.key().stack().copy();
          stack.stackSize = gotten;
          return stack;
        }
      }
      return null;
    }

    public boolean has(ItemStack template) {
      Key key = Key.of(template);
      Element e = _elements.get(key);
      log.debug("has", "template", template, "key", key, "e", e);
      return e != null && !e.empty();
    }

    public int delete(ItemStack template, int amount) {
      Key key = Key.of(template);
      Element e = _elements.get(key);
      log.debug("take", "template", template, "key", key, "e", e);
      if (e == null) return 0;
      int taken = e.take(amount);
      _total = Math.max(0, _total - taken);
      return taken;
    }

    public ItemStack take(int amount) {
      log.debug("take", "amount", amount);
      for (Element e : _elements.values()) {
        if (e.empty()) continue;
        int taken = e.take(amount);
        if (taken > 0) {
          ItemStack stack = e.key().stack().copy();
          stack.stackSize = taken;
          _total = Math.max(0, _total - taken);
          if (e.empty() && !_whitelist.contains(e.key())) _elements.remove(e.key());
          log.debug("take: done", "amount", stack.stackSize);
          return stack;
        }
      }
      return null;
    }

    public ItemStack put(ItemStack stack) {
      log.debug("put", "stack", stack, "size", stack.stackSize);
      if (stack == null) return null;
      Key key = Key.of(stack);
      Element e = _elements.get(key);
      if (e == null) return stack;
      int amount = stack.stackSize;
      amount = Math.min(amount, _maxTotal - _total);
      amount = e.put(amount, _maxPerKey);
      if (amount == 0) return stack;
      _total += amount;
      log.debug("put: done", "amount", amount);
      return stack.splitStack(amount);
    }

    public ItemStack putExportable(ItemStack stack) {
      return _exportable.put(stack);
    }

    public void setExport(ItemStack stack) {
      _export = stack;
    }

    public ItemStack getExport() {
      return _export;
    }

    public ItemStack takeExport(int amount) {
      if (_export == null) return null;
      ItemStack taken = _export.splitStack(amount);
      if (_export.stackSize == 0) _export = null;
      return taken.stackSize == 0 ? null : taken;
    }

    public void fillExport() {
      if (_exportable.empty()) return;
      if (_export == null) {
        _export = _exportable.take();
      } else {
        int request = Math.min(SLOT_STACK_LIMIT, _export.getMaxStackSize()) - _export.stackSize;
        ItemStack taken = _exportable.take(_export, request);
        if (taken != null) _export.stackSize += taken.stackSize;
        else _export = _exportable.take();
      }
    }

    public NBTTagCompound toNbt() {
      NBTTagCompound tag = new NBTTagCompound();
      NBTTagList elements = new NBTTagList();
      for (Map.Entry<Key, Element> e : _elements.entrySet()) {
        NBTTagCompound element = new NBTTagCompound();
        element.setTag("key", e.getKey().toNbt());
        element.setTag("element", e.getValue().toNbt());
        elements.appendTag(element);
      }
      tag.setTag("elements", elements);
      tag.setTag("exportable", _exportable.toNbt());
      if (_export != null)
        tag.setTag("export", ItemHelper.writeItemStackToNBT(_export, new NBTTagCompound()));
      tag.setInteger("total", _total);
      return tag;
    }

    public void fromNbt(NBTTagCompound tag) {
      clear();
      NBTTagList elements = tag.getTagList("elements", Constants.NBT.TAG_COMPOUND);
      for (int i = 0; i < elements.tagCount(); ++i) {
        NBTTagCompound element = elements.getCompoundTagAt(i);
        Key k = Key.fromNbt(element.getCompoundTag("key"));
        Element e = Element.fromNbt(element.getCompoundTag("element"));
        _elements.put(k, e);
        if (e.whitelisted()) _whitelist.add(k);
      }
      _exportable.fromNbt(tag.getCompoundTag("exportable"));
      if (tag.hasKey("export"))
        _export = ItemHelper.readItemStackFromNBT(tag.getCompoundTag("export"));
      _total = tag.getInteger("total");
    }

    public void dump() {
      log.debug("DUMP", "total", _total);
      for (Element e : _elements.values())
        log.debug(
            "DUMP: element",
            "key",
            e.key().stack(),
            "whitelisted",
            e.whitelisted(),
            "count",
            e.count());
    }
  }
}
 @Override // IInventory
 public ItemStack getStackInSlotOnClosing(int slot) {
   log.debug("getStackInSlotOnClosing", "slot", slot);
   return null;
 }