package codechicken.nei;
import codechicken.core.gui.GuiScrollSlot;
import codechicken.lib.gui.GuiDraw;
import codechicken.lib.vec.Rectangle4i;
import codechicken.nei.ItemList.AnyMultiItemFilter;
import codechicken.nei.ItemList.ItemsLoadedCallback;
import codechicken.nei.ItemList.NothingItemFilter;
import codechicken.nei.SearchField.ISearchProvider;
import codechicken.nei.api.API;
import codechicken.nei.api.ItemFilter;
import codechicken.nei.api.ItemFilter.ItemFilterProvider;
import codechicken.nei.api.ItemInfo;
import codechicken.nei.guihook.GuiContainerManager;
import net.minecraft.client.Minecraft;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.util.EnumChatFormatting;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
public class SubsetWidget extends Button implements ItemFilterProvider, ItemsLoadedCallback, ISearchProvider {
public static class SubsetState {
int state = 2;
ArrayList<ItemStack> items = new ArrayList<ItemStack>();
}
public static class SubsetTag {
protected class SubsetSlot extends GuiScrollSlot {
public SubsetSlot() {
super(0, 0, 0, 0);
setSmoothScroll(false);
}
@Override
public int getSlotHeight(int slot) {
return 18;
}
@Override
protected int getNumSlots() {
return children.size() + state.items.size();
}
@Override
protected void slotClicked(int slot, int button, int mx, int my, int count) {
if (slot < sorted.size()) {
SubsetTag tag = sorted.get(slot);
if (NEIClientUtils.shiftKey()) {
LayoutManager.searchField.setText("@" + tag.fullname);
} else if (button == 0 && count >= 2) {
SubsetWidget.showOnly(tag);
} else {
SubsetWidget.setHidden(tag, button == 1);
}
} else {
ItemStack item = state.items.get(slot - sorted.size());
if (NEIClientUtils.controlKey()) {
NEIClientUtils.cheatItem(item, button, -1);
} else {
SubsetWidget.setHidden(state.items.get(slot - sorted.size()), button == 1);
}
}
}
@Override
protected void drawSlot(int slot, int x, int y, int mx, int my, float frame) {
int w = windowBounds().width;
Rectangle4i r = new Rectangle4i(x, y, w, getSlotHeight(slot));
if (slot < sorted.size()) {
SubsetTag tag = sorted.get(slot);
LayoutManager.getLayoutStyle().drawSubsetTag(tag.displayName(), x, y, r.w, r.h, tag.state.state, r.contains(mx, my));
} else {
ItemStack stack = state.items.get(slot - sorted.size());
boolean hidden = SubsetWidget.isHidden(stack);
int itemx = w / 2 - 8;
int itemy = 1;
LayoutManager.getLayoutStyle().drawSubsetTag(null, x, y, r.w, r.h, hidden ? 0 : 2, false);
GuiContainerManager.drawItem(x + itemx, y + itemy, stack);
if (new Rectangle4i(itemx, itemy, 16, 16).contains(mx, my)) {
SubsetWidget.hoverStack = stack;
}
}
}
@Override
public void drawOverlay(float frame) {
}
@Override
public void drawBackground(float frame) {
drawRect(x, y, x + width, y + height, 0xFF202020);
}
@Override
public int scrollbarAlignment() {
return -1;
}
@Override
public void drawScrollbar(float frame) {
if (hasScrollbar()) {
super.drawScrollbar(frame);
}
}
@Override
public int scrollbarGuideAlignment() {
return 0;
}
}
public final String fullname;
public final ItemFilter filter;
public TreeMap<String, SubsetTag> children = new TreeMap<String, SubsetTag>();
public List<SubsetTag> sorted = Collections.emptyList();
private int childwidth;
protected String displayName;
protected SubsetState state = new SubsetState();
protected final SubsetSlot slot = new SubsetSlot();
private SubsetTag selectedChild;
private int visible;
public SubsetTag(String fullname) {
this(fullname, new NothingItemFilter());
}
public SubsetTag(String fullname, ItemFilter filter) {
assert filter != null : "Filter cannot be null";
this.fullname = EnumChatFormatting.getTextWithoutFormattingCodes(fullname);
this.filter = filter;
if (fullname != null) {
int idx = fullname.lastIndexOf('.');
displayName = idx < 0 ? fullname : fullname.substring(idx + 1);
}
}
public String displayName() {
return displayName;
}
public String name() {
int idx = fullname.indexOf('.');
return idx < 0 ? fullname : fullname.substring(idx + 1);
}
public String parent() {
int idx = fullname.lastIndexOf('.');
return idx < 0 ? null : fullname.substring(0, idx);
}
private SubsetTag getTag(String name) {
int idx = name.indexOf('.');
String childname = idx > 0 ? name.substring(0, idx) : name;
SubsetTag child = children.get(childname.toLowerCase());
if (child == null) {
return null;
}
return idx > 0 ? child.getTag(name.substring(idx + 1)) : child;
}
private void recacheChildren() {
sorted = new ArrayList<SubsetTag>(children.values());
childwidth = 0;
for (SubsetTag tag : sorted) {
childwidth = Math.max(childwidth, tag.nameWidth() + 2);
}
}
private void addTag(SubsetTag tag) {
String name = fullname == null ? tag.fullname : tag.fullname.substring(fullname.length() + 1);
int idx = name.indexOf('.');
if (idx < 0) {//add or replace tag
SubsetTag prev = children.put(name.toLowerCase(), tag);
if (prev != null) {//replaced, load children
tag.children = prev.children;
tag.sorted = prev.sorted;
}
recacheChildren();
} else {
String childname = name.substring(0, idx);
SubsetTag child = children.get(childname.toLowerCase());
if (child == null) {
children.put(childname.toLowerCase(), child = new SubsetTag(fullname == null ? childname : fullname + '.' + childname));
}
recacheChildren();
child.addTag(tag);
}
}
protected void cacheState() {
state = SubsetWidget.getState(this);
for (SubsetTag tag : sorted) {
tag.cacheState();
}
}
public void addFilters(List<ItemFilter> filters) {
if (filter != null) {
filters.add(filter);
}
for (SubsetTag child : sorted) {
child.addFilters(filters);
}
}
public void search(List<SubsetTag> tags, Pattern p) {
if (fullname != null && p.matcher(fullname.toLowerCase()).find()) {
tags.add(this);
} else {
for (SubsetTag child : sorted) {
child.search(tags, p);
}
}
}
public void updateVisiblity(int mx, int my) {
if (selectedChild != null) {
selectedChild.updateVisiblity(mx, my);
if (!selectedChild.isVisible()) {
selectedChild = null;
}
}
if (slot.contains(mx, my) && (selectedChild == null || !selectedChild.contains(mx, my))) {
int mslot = slot.getClickedSlot(my);
if (mslot >= 0 && mslot < sorted.size()) {
SubsetTag mtag = sorted.get(mslot);
if (mtag != null) {
if (mtag != selectedChild && selectedChild != null) {
selectedChild.setHidden();
}
selectedChild = mtag;
selectedChild.setVisible();
}
}
setVisible();
}
if (selectedChild == null) {
countdownVisible();
}
}
public void setHidden() {
visible = 0;
slot.mouseReleased(0, 0, 0);//cancel any scrolling
if (selectedChild != null) {
selectedChild.setHidden();
selectedChild = null;
}
}
public void setVisible() {
visible = 10;
cacheState();
}
private void countdownVisible() {
if (visible > 0 && --visible == 0) {
setHidden();
}
}
public void resize(int x, int pwidth, int y) {
int mheight = area.h;
int dheight = area.y2() - y;
int cheight = slot.contentHeight();
int height = cheight;
if (cheight > mheight) {
y = area.y;
height = mheight;
} else if (cheight > dheight) {
y = area.y2() - cheight;
}
height = height / slot.getSlotHeight(0) * slot.getSlotHeight(0);//floor to a multiple of slot height
int width = childwidth;
if (!state.items.isEmpty()) {
width = Math.max(childwidth, 18);
}
if (slot.contentHeight() > height) {
width += slot.scrollbarDim().width;
}
boolean fitLeft = x - width >= area.x1();
boolean fitRight = x + width + pwidth <= area.x2();
if (pwidth >= 0 ? !fitRight && fitLeft : !fitLeft) {
pwidth *= -1;//swap
}
x += pwidth >= 0 ? pwidth : -width;
slot.setSize(x, y, width, height);
slot.setMargins(slot.hasScrollbar() ? slot.scrollbarDim().width : 0, 0, 0, 0);
if (selectedChild != null) {
y = slot.getSlotY(sorted.indexOf(selectedChild)) - slot.scrolledPixels() + slot.y;
selectedChild.resize(x, pwidth >= 0 ? width : -width, y);
}
}
protected int nameWidth() {
return Minecraft.getMinecraft().fontRendererObj.getStringWidth(displayName());
}
public boolean isVisible() {
return visible > 0;
}
public void draw(int mx, int my) {
slot.draw(mx, my, 0);
if (selectedChild != null) {
selectedChild.draw(mx, my);
}
}
public boolean contains(int px, int py) {
return slot.contains(px, py) || selectedChild != null && selectedChild.contains(px, py);
}
public void mouseClicked(int mx, int my, int button) {
if (selectedChild != null && selectedChild.contains(mx, my)) {
selectedChild.mouseClicked(mx, my, button);
} else if (slot.contains(mx, my)) {
slot.mouseClicked(mx, my, button);
}
}
public void mouseDragged(int mx, int my, int button, long heldTime) {
slot.mouseDragged(mx, my, button, heldTime);
if (selectedChild != null) {
selectedChild.mouseDragged(mx, my, button, heldTime);
}
}
public void mouseUp(int mx, int my, int button) {
slot.mouseReleased(mx, my, button);
if (selectedChild != null) {
selectedChild.mouseUp(mx, my, button);
}
}
public boolean mouseScrolled(int mx, int my, int scroll) {
if (slot.hasScrollbar() && slot.contains(mx, my)) {
slot.scroll(scroll);
return true;
}
if (selectedChild != null && selectedChild.mouseScrolled(mx, my, scroll)) {
return true;
}
if (slot.hasScrollbar() && !contains(mx, my)) {
slot.scroll(scroll);
return true;
}
return false;
}
public boolean isScrolling() {
return slot.isScrolling() || selectedChild != null && selectedChild.isScrolling();
}
}
protected static final SubsetTag root = new SubsetTag(null);
public static Rectangle4i area = new Rectangle4i();
public static ItemStack hoverStack;
private static HashMap<String, SubsetState> subsetState = new HashMap<String, SubsetState>();
/**
* All operations on this variable should be synchronised.
*/
private static final ItemStackSet hiddenItems = new ItemStackSet();
private static final AtomicReference<NBTTagList> dirtyHiddenItems = new AtomicReference<NBTTagList>();
public static SubsetState getState(SubsetTag tag) {
SubsetState state = subsetState.get(tag.fullname);
return state == null ? new SubsetState() : state;
}
public static void addTag(SubsetTag tag) {
updateState.stop();
synchronized (root) {
root.addTag(tag);
updateState.reallocate();
}
}
public static SubsetTag getTag(String name) {
return name == null ? root : root.getTag(name);
}
public static boolean isHidden(ItemStack item) {
synchronized (hiddenItems) {
return hiddenItems.contains(item);
}
}
private static void _setHidden(SubsetTag tag, boolean hidden) {
for (ItemStack item : getState(tag).items) {
_setHidden(item, hidden);
}
for (SubsetTag child : tag.sorted) {
_setHidden(child, hidden);
}
}
private static void _setHidden(ItemStack item, boolean hidden) {
if (hidden) {
hiddenItems.add(item);
} else {
hiddenItems.remove(item);
}
}
public static void showOnly(SubsetTag tag) {
synchronized (hiddenItems) {
for (ItemStack item : ItemList.items) {
_setHidden(item, true);
}
setHidden(tag, false);
}
}
public static void setHidden(SubsetTag tag, boolean hidden) {
synchronized (hiddenItems) {
_setHidden(tag, hidden);
updateHiddenItems();
}
}
public static void setHidden(ItemStack item, boolean hidden) {
synchronized (hiddenItems) {
_setHidden(item, hidden);
updateHiddenItems();
}
}
public static void unhideAll() {
synchronized (hiddenItems) {
hiddenItems.clear();
updateHiddenItems();
}
}
private static void updateHiddenItems() {
prepareDirtyHiddenItems.restart();
updateState.restart();
}
public static void loadHidden() {
synchronized (hiddenItems) {
hiddenItems.clear();
}
List<ItemStack> itemList = new LinkedList<ItemStack>();
try {
NBTTagList list = NEIClientConfig.world.nbt.getTagList("hiddenItems", 10);
for (int i = 0; i < list.tagCount(); i++) {
itemList.add(ItemStack.loadItemStackFromNBT(list.getCompoundTagAt(i)));
}
} catch (Exception e) {
NEIClientConfig.logger.error("Error loading hiddenItems", e);
return;
}
synchronized (hiddenItems) {
for (ItemStack item : itemList) {
hiddenItems.add(item);
}
}
updateState.restart();
}
private static void saveHidden() {
NBTTagList list = dirtyHiddenItems.getAndSet(null);
if (list != null) {
NEIClientConfig.world.nbt.setTag("hiddenItems", list);
NEIClientConfig.world.saveNBT();
}
}
private static final RestartableTask prepareDirtyHiddenItems = new RestartableTask("NEI Subset Save Thread") {
private List<ItemStack> getList() {
synchronized (hiddenItems) {
return hiddenItems.values();
}
}
@Override
public void execute() {
NBTTagList list = new NBTTagList();
for (ItemStack item : getList()) {
if (interrupted()) {
return;
}
NBTTagCompound tag = new NBTTagCompound();
item.writeToNBT(tag);
list.appendTag(tag);
}
dirtyHiddenItems.set(list);
}
};
private static final UpdateStateTask updateState = new UpdateStateTask();
private static class UpdateStateTask extends RestartableTask {
private volatile boolean reallocate;
public UpdateStateTask() {
super("NEI Subset Item Allocation");
}
@Override
public void clearTasks() {
super.clearTasks();
reallocate = false;
}
public synchronized void reallocate() {
reallocate = true;
restart();
}
@Override
public void execute() {
HashMap<String, SubsetState> state = new HashMap<String, SubsetState>();
List<SubsetTag> tags = new LinkedList<SubsetTag>();
synchronized (root) {
cloneStates(root, tags, state);
if (interrupted()) {
return;
}
}
if (reallocate) {
for (ItemStack item : ItemList.items) {
if (interrupted()) {
return;
}
if (ItemInfo.isHidden(item)) {
continue;
}
for (SubsetTag tag : tags) {
if (tag.filter.matches(item)) {
state.get(tag.fullname).items.add(item);
}
}
}
}
synchronized (root) {
calculateVisibility(root, state);
if (interrupted()) {
return;
}
}
subsetState = state;
ItemList.updateFilter.restart();
}
private void cloneStates(SubsetTag tag, List<SubsetTag> tags, HashMap<String, SubsetState> state) {
for (SubsetTag child : tag.sorted) {
if (interrupted()) {
return;
}
cloneStates(child, tags, state);
}
tags.add(tag);
SubsetState sstate = new SubsetState();
if (!reallocate) {
sstate.items = SubsetWidget.getState(tag).items;
}
state.put(tag.fullname, sstate);
}
private void calculateVisibility(SubsetTag tag, Map<String, SubsetState> state) {
SubsetState sstate = state.get(tag.fullname);
int hidden = 0;
for (SubsetTag child : tag.sorted) {
if (interrupted()) {
return;
}
calculateVisibility(child, state);
int cstate = state.get(child.fullname).state;
if (cstate == 1) {
sstate.state = 1;
} else if (cstate == 0) {
hidden++;
}
}
if (sstate.state == 1) {
return;
}
List<ItemStack> items = sstate.items;
for (ItemStack item : items) {
if (interrupted()) {
return;
}
if (isHidden(item)) {
hidden++;
}
}
if (hidden == tag.sorted.size() + items.size()) {
sstate.state = 0;
} else if (hidden > 0) {
sstate.state = 1;
}
}
}
private long lastclicktime;
public SubsetWidget() {
super("NEI Subsets");
API.addItemFilter(this);
API.addSearchProvider(this);
ItemList.loadCallbacks.add(this);
}
@Override
public void draw(int mx, int my) {
super.draw(mx, my);
area.set(x, y + h, w, LayoutManager.searchField.y - h - y); //23 for the search box
hoverStack = null;
if (root.isVisible()) {
root.resize(area.x, 0, area.y);
root.cacheState();
root.draw(mx, my);
}
}
@Override
public void update() {
Point mouse = GuiDraw.getMousePosition();
updateVisiblity(mouse.x, mouse.y);
saveHidden();
}
private void updateVisiblity(int mx, int my) {
if (!root.isVisible() || root.isScrolling()) {
return;
}
root.updateVisiblity(mx, my);
if (!root.isVisible() && bounds().contains(mx, my)) {
root.setVisible();
}
}
@Override
public boolean contains(int px, int py) {
return super.contains(px, py) || root.isVisible() && root.contains(px, py);
}
@Override
public boolean handleClick(int mx, int my, int button) {
if (root.isVisible() && root.contains(mx, my)) {
root.mouseClicked(mx, my, button);
return true;
}
if (button == 0) {
if (System.currentTimeMillis() - lastclicktime < 500) {
unhideAll();
} else {
root.setVisible();
}
NEIClientUtils.playClickSound();
lastclicktime = System.currentTimeMillis();
}
return true;
}
/**
* Not called
*/
@Override
public boolean onButtonPress(boolean rightclick) {
return false;
}
@Override
public void mouseDragged(int mx, int my, int button, long heldTime) {
if (root.isVisible()) {
root.mouseDragged(mx, my, button, heldTime);
}
}
@Override
public void mouseUp(int mx, int my, int button) {
if (root.isVisible()) {
root.mouseUp(mx, my, button);
}
}
@Override
public boolean onMouseWheel(int i, int mx, int my) {
return root.isVisible() && root.mouseScrolled(mx, my, -i);
}
@Override
public void onGuiClick(int mx, int my) {
if (!contains(mx, my)) {
root.setHidden();
}
}
@Override
public ItemStack getStackMouseOver(int mx, int my) {
return hoverStack;
}
@Override
public ItemFilter getFilter() {
return new ItemFilter()//synchronise access on hiddenItems
{
@Override
public boolean matches(ItemStack item) {
synchronized (hiddenItems) {
return !hiddenItems.matches(item);
}
}
};
}
@Override
public boolean isPrimary() {
return true;
}
@Override
public ItemFilter getFilter(String searchText) {
if (!searchText.startsWith("@")) {
return null;
}
searchText = searchText.substring(1);
AnyMultiItemFilter filter = new AnyMultiItemFilter();
SubsetTag tag = getTag(searchText);
if (tag != null) {
tag.addFilters(filter.filters);
} else {
Pattern p = SearchField.getPattern(searchText);
if (p == null) {
return null;
}
List<SubsetTag> matching = new LinkedList<SubsetTag>();
root.search(matching, p);
if (matching.isEmpty()) {
return null;
}
for (SubsetTag tag2 : matching) {
tag2.addFilters(filter.filters);
}
}
return filter;
}
@Override
public void itemsLoaded() {
updateState.reallocate();
}
}