package crazypants.enderio.machine.invpanel;

import java.util.ArrayList;
import java.util.HashMap;

import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;

public abstract class InventoryDatabase<ItemEntry extends ItemEntryBase> {

  private static final Item META_EXTRACTOR = new Item();

  private static final int SIMPLE_ITEMID_BITS = 12;
  private static final int SIMPLE_META_BITS = 4;
  private static final int SIMPLE_MAX_ITEMID = 1 << SIMPLE_ITEMID_BITS;
  private static final int SIMPLE_MAX_META = 1 << SIMPLE_META_BITS;
  private static final int SIMPLE_META_MASK = SIMPLE_MAX_META - 1;

  protected static final int COMPLEX_DBINDEX_START = 1 << (SIMPLE_ITEMID_BITS + SIMPLE_META_BITS);

  protected final HashMap<Integer, ItemEntry> simpleRegsitry;
  protected final HashMap<ItemEntry, ItemEntry> complexRegistry;
  protected final ArrayList<ItemEntry> complexItems;

  protected int generation;

  public InventoryDatabase() {
    simpleRegsitry = new HashMap<Integer, ItemEntry>();
    complexRegistry = new HashMap<ItemEntry, ItemEntry>();
    complexItems = new ArrayList<ItemEntry>();
  }

  public int getGeneration() {
    return generation;
  }

  public ItemEntry lookupItem(ItemStack stack, ItemEntry hint, boolean create) {
    if(stack == null || stack.getItem() == null) {
      return null;
    }
    
    int itemID = Item.getIdFromItem(stack.getItem());
    int meta = META_EXTRACTOR.getDamage(stack);
    NBTTagCompound nbt = stack.stackTagCompound;
    
    if(nbt != null && nbt.hasNoTags()) {
      nbt = null;
    }

    if(hint != null && hint.equals(itemID, meta, nbt)) {
      return hint;
    }

    if(nbt == null && itemID < SIMPLE_MAX_ITEMID && meta < SIMPLE_MAX_META) {
      return getSimpleItem(itemID, meta, create);
    } else {
      return getComplexItem(itemID, meta, nbt);
    }
  }

  protected abstract ItemEntry createItemEntry(int dbId, int hash, int itemID, int meta, NBTTagCompound nbt);

  protected ItemEntry createItemEntry(int dbId, int itemID, int meta, NBTTagCompound nbt) {
    int hash = computeComplexHash(itemID, meta, nbt);
    return createItemEntry(dbId, hash, itemID, meta, nbt);
  }

  private ItemEntry getComplexItem(int itemID, int meta, NBTTagCompound nbt) {
    int hash = computeComplexHash(itemID, meta, nbt);
    ItemEntryKey key = new ItemEntryKey(hash, itemID, meta, nbt);
    ItemEntry entry = complexRegistry.get(key);
    if(entry == null) {
      if(nbt != null) {
        nbt = (NBTTagCompound) nbt.copy();
      }
      entry = createItemEntry(COMPLEX_DBINDEX_START + complexItems.size(), hash, itemID, meta, nbt);
      complexItems.add(entry);
      complexRegistry.put(entry, entry);
    }
    return entry;
  }

  private static int computeComplexHash(int itemID, int meta, NBTTagCompound nbt) {
    int hash = ((itemID * 257) ^ meta) * 17;
    if(nbt != null) {
      hash ^= nbt.hashCode();
    }
    return hash;
  }

  private ItemEntry getSimpleItem(int itemID, int meta, boolean create) {
    Integer dbID = (itemID << SIMPLE_META_BITS) | meta;
    ItemEntry entry = simpleRegsitry.get(dbID);
    if(entry == null && create) {
      entry = createItemEntry(dbID, dbID, itemID, meta, null);
      simpleRegsitry.put(dbID, entry);
    }
    return entry;
  }

  protected ItemEntry getSimpleItem(int dbID) {
    int itemID = dbID >> SIMPLE_META_BITS;
    int meta = dbID & SIMPLE_META_MASK;
    return getSimpleItem(itemID, meta, true);
  }

  public ItemEntry getItem(int dbID) {
    if(dbID < COMPLEX_DBINDEX_START) {
      return getSimpleItem(dbID);
    }

    int dbIndex = dbID - COMPLEX_DBINDEX_START;
    if(dbIndex < complexItems.size()) {
      return complexItems.get(dbIndex);
    }
    return null;
  }

  public ItemEntry getExistingItem(int dbID) {
    if(dbID < COMPLEX_DBINDEX_START) {
      return simpleRegsitry.get(dbID);
    }
    return getItem(dbID);
  }

  static final class ItemEntryKey {
    public final int hash;
    public final int itemID;
    public final int meta;
    public final NBTTagCompound nbt;

    ItemEntryKey(int hash, int itemID, int meta, NBTTagCompound nbt) {
      this.hash = hash;
      this.itemID = itemID;
      this.meta = meta;
      this.nbt = nbt;
    }

    @Override
    public int hashCode() {
      return hash;
    }

    @Override
    public boolean equals(Object obj) {
      if(obj instanceof ItemEntryBase) {
        final ItemEntryBase other = (ItemEntryBase) obj;
        return other.equals(itemID, meta, nbt);
      }
      return false;
    }
  }
}

@@ -1864,24 +1864,32 @@
 D, meta, nbt
+, create
 );%0A    %7D%0A  %7D
@@ -2278,32 +2278,48 @@
 TTagCompound nbt
+, boolean create
 ) %7B%0A    int hash
@@ -2486,32 +2486,42 @@
 if(entry == null
+ && create
 ) %7B%0A      if(nbt

Fix for inventory panel creating 'phantom items' (#3508)