package mezz.jei.bookmarks;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import mezz.jei.api.ingredients.IIngredientHelper;
import mezz.jei.api.ingredients.subtypes.UidContext;
import mezz.jei.config.BookmarkConfig;
import mezz.jei.gui.overlay.IIngredientGridSource;
import mezz.jei.ingredients.IngredientManager;

public class BookmarkList implements IIngredientGridSource {
	private final List<Object> list = new LinkedList<>();
	private final IngredientManager ingredientManager;
	private final BookmarkConfig bookmarkConfig;
	private final List<IIngredientGridSource.Listener> listeners = new ArrayList<>();

	public BookmarkList(IngredientManager ingredientManager, BookmarkConfig bookmarkConfig) {
		this.ingredientManager = ingredientManager;
		this.bookmarkConfig = bookmarkConfig;
	}

	public <T> boolean add(T ingredient) {
		IIngredientHelper<T> ingredientHelper = ingredientManager.getIngredientHelper(ingredient);
		Object normalized = ingredientHelper.normalizeIngredient(ingredient);
		if (!contains(normalized)) {
			if (addToList(normalized, true)) {
				notifyListenersOfChange();
				bookmarkConfig.saveBookmarks(ingredientManager, list);
				return true;
			}
		}
		return false;
	}

	private boolean contains(Object ingredient) {
		// We cannot assume that ingredients have a working equals() implementation. Even ItemStack doesn't have one...
		IIngredientHelper<Object> ingredientHelper = ingredientManager.getIngredientHelper(ingredient);
		for (Object existing : list) {
			if (ingredient == existing) {
				return true;
			}
			if (existing != null && existing.getClass() == ingredient.getClass()) {
				if (equalUids(ingredientHelper, existing, ingredient)) {
					return true;
				}
			}
		}
		return false;
	}

	private static boolean equalUids(IIngredientHelper<Object> ingredientHelper, Object a, Object b) {
		String uidA = ingredientHelper.getUniqueId(a, UidContext.Ingredient);
		String uidB = ingredientHelper.getUniqueId(b, UidContext.Ingredient);
		return uidA.equals(uidB);
	}

	public boolean remove(Object ingredient) {
		int index = 0;
		for (Object existing : list) {
			if (ingredient == existing) {
				list.remove(index);
				notifyListenersOfChange();
				bookmarkConfig.saveBookmarks(ingredientManager, list);
				return true;
			}
			index++;
		}
		return false;
	}

	public <T> boolean addToList(T ingredient, boolean addToFront) {
		if (addToFront) {
			list.add(0, ingredient);
		} else {
			list.add(ingredient);
		}
		return true;
	}

	@Override
	public List<?> getIngredientList(String filterText) {
		return list;
	}

	public boolean isEmpty() {
		return list.isEmpty();
	}

	@Override
	public void addListener(IIngredientGridSource.Listener listener) {
		listeners.add(listener);
	}

	public void notifyListenersOfChange() {
		for (IIngredientGridSource.Listener listener : listeners) {
			listener.onChange();
		}
	}
}

@@ -343,16 +343,59 @@
 Manager;
+%0Aimport net.minecraft.world.item.ItemStack;
 %0A%0Apublic
@@ -1285,16 +1285,20 @@
 %09private
+ %3CT%3E
  boolean
@@ -1307,22 +1307,17 @@
 ontains(
-Object
+T
  ingredi
@@ -1449,38 +1449,33 @@
 ngredientHelper%3C
-Object
+T
 %3E ingredientHelp
@@ -1629,75 +1629,253 @@
 if (
-existing != null && existing.getClass() == ingredient.getClass()) %7B
+ingredient.getClass().isInstance(existing)) %7B%0A%09%09%09%09@SuppressWarnings(%22unchecked%22)%0A%09%09%09%09T castExisting = (T) existing;%0A%09%09%09%09if (ingredient instanceof ItemStack) %7B%0A%09%09%09%09%09return ItemStack.matches((ItemStack) ingredient, (ItemStack) castExisting);%0A%09%09%09%09%7D
 %0A%09%09%09
@@ -1907,17 +1907,21 @@
 Helper, 
-e
+castE
 xisting,
@@ -2004,16 +2004,20 @@
 e static
+ %3CT%3E
  boolean
@@ -2045,22 +2045,17 @@
 tHelper%3C
-Object
+T
 %3E ingred
@@ -2070,24 +2070,14 @@
 er, 
-Object a, Object
+T a, T
  b) 

Fix #1940 Allow bookmarking the same item with different NBT