package speiger.src.coreengine.rendering.guiOld;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;

import org.lwjgl.glfw.GLFW;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.sets.ObjectLinkedOpenHashSet;
import speiger.src.coreengine.assets.AssetLocation;
import speiger.src.coreengine.math.bits.FlagHolder;
import speiger.src.coreengine.math.collision2d.Plane;
import speiger.src.coreengine.math.vector.doubles.Vec2d;
import speiger.src.coreengine.rendering.guiOld.base.GuiScreenBase;
import speiger.src.coreengine.rendering.guiOld.base.IButtonComponent;
import speiger.src.coreengine.rendering.guiOld.base.IKeyComponent;
import speiger.src.coreengine.rendering.guiOld.components.TextComponent;
import speiger.src.coreengine.rendering.guiOld.helper.Align;
import speiger.src.coreengine.rendering.guiOld.helper.animations.Animator;
import speiger.src.coreengine.rendering.guiOld.helper.box.GuiBox;
import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox;
import speiger.src.coreengine.rendering.guiOld.helper.constrains.Constrain;
import speiger.src.coreengine.rendering.guiOld.helper.constrains.Constrains;
import speiger.src.coreengine.rendering.guiOld.renderer.FontRenderer;
import speiger.src.coreengine.rendering.guiOld.renderer.IComponentRenderer;
import speiger.src.coreengine.rendering.guiOld.renderer.UIRenderer;
import speiger.src.coreengine.rendering.inputOld.Keyboard;
import speiger.src.coreengine.rendering.inputOld.bindings.utils.BindingType;
import speiger.src.coreengine.rendering.inputOld.bindings.utils.ModType;
import speiger.src.coreengine.rendering.inputOld.window.Window;
import speiger.src.coreengine.rendering.utils.Cursor;
import speiger.src.coreengine.rendering.utils.GLStateTracker;
import speiger.src.coreengine.utils.collections.CollectionUtils;

public abstract class GuiComponent extends FlagHolder
{
	public static final int FLAG_VISIBLE = 1;
	public static final int FLAG_ENABLED = 2;
	public static final int FLAG_IGNORE_PARENT_BOUNDS = 4;
	public static final int FLAG_ALWAYS_CLICKABLE = 8;
	public static final int FLAG_RENDER_ORDER = 16;
	public static final int FLAG_MANUAL_RENDER = 32;
	public static final int FLAG_MASS_CHANGE = 64;
	public static final int FLAG_SUPPORT_BINDING = 128;
	public static final int FLAG_TEST_SCISSORS = 256;
	static final int FLAG_CLOSING = 512;
	public static final int FLAG_RENDER_BOUNDS = 1024;
	
	public static final int LAST_FLAG = 1 << 19;// This is the last flag and then
											// anything behind this is custom
											// flags for components
	
	public static final int LISTENER_USER_ACTION = 0;
	public static final int LISTENER_ON_CHANGE = 1;
	public static final int LISTENER_CLOSED = 2;
	
	final IGuiBox box;
	GuiBase owner;
	GuiComponent parent;
	Constrains constraints = null;
	Animator animation = null;
	KeyBindAction binding = null;
	IComponentRenderer<GuiComponent> customRenderer;
	Set<Consumer<GuiComponent>>[] listeners = CollectionUtils.createSets(3, true);
	Set<GuiComponent> children = new ObjectLinkedOpenHashSet<>();
	Set<GuiComponent> popupChildren = new ObjectLinkedOpenHashSet<>();
	Tooltips tooltips = new Tooltips();
	UUID tooltipId;
	float zLevel = 0F;
	float totalZ = 0F;
	float visiblity = 1F;
	float totalVisibility = 1F;
	float brightness = 1F;
	boolean changed = false;
	boolean massRepaint = false;
	
	public GuiComponent(float x, float y, float width, float height)
	{
		this(new GuiBox(x, y, width, height));
	}
	
	public GuiComponent(IGuiBox box)
	{
		super(FLAG_VISIBLE | FLAG_ENABLED);
		this.box = box;
	}
	
	public IGuiBox getBox()
	{
		return box;
	}
	
	public GuiBase getGui()
	{
		return owner;
	}
	
	public FontRenderer getFont()
	{
		return owner.getFont();
	}
	
	public void calculateActualBounds(float[] area, boolean start)
	{
		if(start)
		{
			area[0] = Float.MAX_VALUE;
			area[1] = Float.MAX_VALUE;
			area[2] = Float.MIN_VALUE;
			area[3] = Float.MIN_VALUE;
		}
		area[0] = Math.min(area[0], box.getMinX());
		area[1] = Math.min(area[1], box.getMinY());
		area[2] = Math.max(area[2], box.getMaxX());
		area[3] = Math.max(area[3], box.getMaxY());
		for(GuiComponent comp : children)
		{
			if(comp.isVisible()) comp.calculateActualBounds(area, false);
		}
	}
	
	public boolean isMouseOver(int mouseX, int mouseY)
	{
		return (parent == null || isFlagSet(FLAG_IGNORE_PARENT_BOUNDS) || parent.isMouseOver(mouseX, mouseY)) && isOverBox(mouseX, mouseY);
	}
	
	protected boolean isOverBox(int mouseX, int mouseY)
	{
		return box.isColiding(mouseX, mouseY);
	}
	
	public boolean isHovered(int mouseX, int mouseY)
	{
		return isParentVisible() && isAnyFlagSet(FLAG_ALWAYS_CLICKABLE | FLAG_ENABLED) && isMouseOver(mouseX, mouseY);
	}
	
	public boolean isTopHovered(int mouseX, int mouseY)
	{
		if(!isHovered(mouseX, mouseY)) return false;
		GuiComponent top = getTopComponent();
		return !getGui().hasComponentInTheWay(top, mouseX, mouseY) && top.isChildAtTop(this, mouseX, mouseY);
	}
	
	public boolean isChildAtTop(GuiComponent component, int mouseX, int mouseY)
	{
		float sourceHeight = component.calculateZLevel();
		if(children.isEmpty())
		{
			return true;
		}
		for(GuiComponent comp : children)
		{
			if(comp != component && comp.isOverChild(mouseX, mouseY) && (comp.calculateZLevel() > sourceHeight || !comp.isChildAtTop(component, mouseX, mouseY)))
			{
				return false;
			}
		}
		return true;
	}
	
	public boolean isOverChild(int mouseX, int mouseY)
	{
		if(children.isEmpty())
		{
			return false;
		}
		for(GuiComponent comp : children)
		{
			if(comp.isOverChild(mouseX, mouseY) || (comp instanceof IButtonComponent && ((IButtonComponent)comp).isComponentColliding(mouseX, mouseY)))
			{
				return true;
			}
		}
		return false;
	}
	
	public boolean hasFocus()
	{
		return getGui().isComponentFocused(getTopComponent());
	}
	
	public final boolean hasPopups()
	{
		return popupChildren.size() > 0;
	}
	
	public final void setOwner(GuiBase gui)
	{
		owner = gui;
		for(GuiComponent comp : children)
		{
			comp.setOwner(gui);
			if(comp instanceof IButtonComponent)
			{
				getGui().addButtonListener((IButtonComponent)comp);
			}
			if(comp instanceof IKeyComponent)
			{
				getGui().addKeyListener((IKeyComponent)comp);
			}
		}
		init();
		onChanged(true);
		if(binding != null)
		{
			gui.addKeyListener(binding);
		}
	}
	
	public abstract void init();
	
	public void onClosed()
	{
		if(!setFlag(FLAG_CLOSING, true))
		{
			return;
		}
		for(GuiComponent comp : children)
		{
			comp.onClosed();
			if(comp instanceof IButtonComponent)
			{
				getGui().removeButtonListener((IButtonComponent)comp);
			}
			if(comp instanceof IKeyComponent)
			{
				getGui().removeKeyListener((IKeyComponent)comp);
			}
		}
		children.clear();
		popupChildren.clear();
		box.clearChildren();
		if(binding != null)
		{
			owner.removeKeyListener(binding);
		}
		notifyListeners(LISTENER_CLOSED);
		clearFlag(FLAG_CLOSING);
	}
	
	public final float getZOffset()
	{
		return zLevel;
	}
	
	public final float calculateZLevel()
	{
		return totalZ;
	}
	
	public final float getVisibility()
	{
		return visiblity;
	}
	
	public final float getTotalVisibility()
	{
		return totalVisibility;
	}
	
	public final float getBrightness()
	{
		return brightness;
	}
	
	public final boolean hasConstraints()
	{
		return constraints != null;
	}
	
	public final boolean isEnabled()
	{
		return isFlagSet(FLAG_ENABLED);
	}
	
	public final boolean isVisible()
	{
		return isFlagSet(FLAG_VISIBLE);
	}
	
	public final boolean usesRenderOrder()
	{
		return isFlagSet(FLAG_RENDER_ORDER);
	}
	
	public final boolean isManualRender()
	{
		return isFlagSet(FLAG_MANUAL_RENDER);
	}
	
	public boolean isTestingScissors()
	{
		return isFlagSet(FLAG_TEST_SCISSORS);
	}
	
	public final boolean isParentVisible()
	{
		return isVisible() && (parent == null || parent.isVisible());
	}
	
	public final GuiComponent setMassChanging()
	{
		setFlag(FLAG_MASS_CHANGE);
		return this;
	}
	
	@SuppressWarnings("unchecked")
	public final <T extends GuiComponent> T setMassChanging(Class<T> clz)
	{
		setMassChanging();
		return (T)this;
	}
	
	public final GuiComponent finishMassChanging()
	{
		return finishMassChanging(false);
	}
	
	public final GuiComponent finishMassChanging(boolean quiet)
	{
		if(isFlagSet(FLAG_MASS_CHANGE))
		{
			clearFlag(FLAG_MASS_CHANGE);
			if(changed && !quiet)
			{
				onChanged(massRepaint);
			}
		}
		return this;
	}
	
	public final GuiComponent setEnabled(boolean value)
	{
		if(!setFlag(FLAG_ENABLED, value)) return this;
		for(GuiComponent comp : children)
		{
			comp.setEnabled(value);
		}
		return this;
	}
	
	public final GuiComponent setRenderBounds()
	{
		setFlag(FLAG_RENDER_BOUNDS);
		return this;
	}
	
	public final GuiComponent setVisible(boolean value)
	{
		if(!setFlag(FLAG_VISIBLE, value)) return this;
		for(GuiComponent comp : children)
		{
			comp.setVisible(value);
		}
		return this;
	}
	
	public final GuiComponent setManualRenderer(boolean value)
	{
		setFlag(FLAG_MANUAL_RENDER, value);
		return this;
	}
	
	public GuiComponent setIgnoreBounds(boolean value)
	{
		setFlag(FLAG_IGNORE_PARENT_BOUNDS, value);
		return this;
	}
	
	public final GuiComponent setScissorsTest(boolean value)
	{
		setFlag(FLAG_TEST_SCISSORS, value);
		return this;
	}
	
	public final GuiComponent setScale(float value)
	{
		if(getBox().getBaseScale() != value)
		{
			getBox().setScale(value);
			onChanged(true);
		}
		return this;
	}
	
	public final GuiComponent setZOffset(float value)
	{
		zLevel = value;
		return this;
	}
	
	public final GuiComponent setVisibilty(float value)
	{
		if(visiblity == value) return this;
		visiblity = value;
		updateVisibility();
		return this;
	}
	
	public final GuiComponent setBrightness(float value)
	{
		brightness = value;
		return this;
	}
	
	public final GuiComponent changeVisibility(float value)
	{
		return value == 1F ? this : setVisibilty(visiblity * value);
	}
	
	protected void updateVisibility()
	{
		totalVisibility = (parent != null ? parent.totalVisibility : 1F) * visiblity;
		for(GuiComponent comp : children)
		{
			comp.updateVisibility();
		}
	}
	
	public GuiComponent setUserKey(int keyBind){return setUserKey(keyBind, ModType.IGNORE, false);}
	public GuiComponent setUserKey(int keyBind, boolean block){return setUserKey(keyBind, ModType.IGNORE, block);}
	public GuiComponent setUserKey(int keyBind, int mod){return setUserKey(keyBind, mod, false);}
	public GuiComponent setUserKey(int keyBind, int mod, boolean block)
	{
		if(isFlagNotSet(FLAG_SUPPORT_BINDING)) return this;
		if(owner != null)
		{
			if(binding != null)
			{
				owner.removeKeyListener(binding);
				tooltips.removeTooltip(binding.getTooltip());
			}
			binding = new KeyBindAction(keyBind, mod, block);
			owner.addKeyListener(binding);
			addBindingTooltip();
			return this;
		}
		binding = new KeyBindAction(keyBind, mod, block);
		addBindingTooltip();
		return this;
	}
	
	@SuppressWarnings("unchecked")
	public <T extends GuiComponent> GuiComponent setCustomRenderer(IComponentRenderer<T> renderer)
	{
		customRenderer = (IComponentRenderer<GuiComponent>)renderer;
		return this;
	}
	
	private void addBindingTooltip()
	{
		tooltips.addComponent(binding.getTooltip(), new TextComponent(0F, 0F, 200F, 0F, "Key: "+ModType.getMods(binding.mod)+BindingType.KEYBOARD.getName(binding.key)).limitHeight(false).align(Align.LEFT_TOP, Align.LEFT_TOP).setScale(0.5F));
	}
	
	protected boolean onUserKey()
	{
		return false;
	}
	
	public <T extends GuiComponent> T setRelativeTo(T component)
	{
		return setRelativeTo(component, Align.CENTER, Align.CENTER);
	}
	
	public <T extends GuiComponent> T setRelativeTo(T component, Align horizontal, Align vertical)
	{
		return component.set(box.getMinX() + horizontal.align(box.getWidth(), component.getBox().getWidth()), box.getMinY() + vertical.align(box.getHeight(), component.getBox().getHeight())).cast();
	}
	
	public <T extends GuiComponent> T centerComponent(T component)
	{
		return getGui().centerComponent(component);
	}
	
	public <T extends GuiComponent> T center()
	{
		return getGui().centerComponent(this).cast();
	}
	
	public <T extends GuiComponent> T addChild(T comp)
	{
		return addChild(comp, null);
	}
	
	public <T extends GuiComponent> T addChild(T comp, Constrain xPos, Constrain yPos, Constrain width, Constrain height)
	{
		return addChild(comp, new Constrains(xPos, yPos, width, height));
	}
	
	public <T extends GuiComponent> T addChild(T comp, Constrains constrains)
	{
		comp.constraints = constrains;
		comp.parent = this;
		children.add(comp);
		box.addChild(comp.getBox());
		if(constrains != null)
		{
			constrains.setOwner(comp, this);
			constrains.onComponentChanged();
		}
		if(owner != null)
		{
			comp.setOwner(owner);
			if(comp instanceof IButtonComponent)
			{
				owner.addButtonListener((IButtonComponent)comp);
			}
			if(comp instanceof IKeyComponent)
			{
				owner.addKeyListener((IKeyComponent)comp);
			}
		}
		return comp;
	}
	
	public <T extends GuiComponent> T addPopup(T popup)
	{
		popupChildren.add(popup.onClose(popupChildren::remove));
		getGui().addComponent(popup);
		return popup;
	}
	
	protected void addConstrains(GuiComponent comp, Constrains constrains)
	{
		comp.constraints = constrains;
		if(constrains != null)
		{
			constrains.setOwner(comp, this);
			constrains.onComponentChanged();
		}
	}
	
	protected void updateConstrains(GuiComponent comp, Constrains constrains)
	{
		comp.constraints = constrains;
		if(constrains != null)
		{
			constrains.setOwner(comp, comp.parent);
			constrains.onComponentChanged();
		}
	}
	
	public final UUID getTooltipId()
	{
		return tooltipId;
	}
	
	public Tooltips getTooltips()
	{
		return tooltips;
	}
	
	public GuiComponent addTooltip(String s, float width)
	{
		return addTooltip(s, width, 0F);
	}
	
	public GuiComponent addTooltip(String s, float width, float height)
	{
		return addTooltip(s, width, height, 0.5F);
	}
	
	public GuiComponent addTooltip(String s, float width, float height, float scale)
	{
		tooltips.addComponent(new TextComponent(0F, 0F, width, height, s).limitHeight(height != 0F).align(Align.LEFT_TOP, Align.LEFT_TOP).setScale(scale));
		return this;
	}
	
	public GuiComponent addTooltip(GuiComponent comp)
	{
		tooltips.addComponent(comp);
		return this;
	}
	
	public GuiComponent addTooltip(UUID id, GuiComponent comp)
	{
		tooltips.addComponent(id, comp);
		return this;
	}
	
	public boolean containsTooltip(UUID id)
	{
		return tooltips.contains(id);
	}
	
	public boolean isTooltip()
	{
		return tooltipId != null;
	}
	
	public GuiComponent removeTooltip(GuiComponent comp)
	{
		return removeTooltip(comp.getTooltipId());
	}
	
	public GuiComponent removeTooltip(UUID id)
	{
		return tooltips.removeTooltip(id);
	}
	
	public List<GuiComponent> getChildren()
	{
		return new ObjectArrayList<GuiComponent>(children);
	}
	
	public GuiComponent getParent()
	{
		return parent;
	}
	
	public GuiComponent removeChild(GuiComponent comp)
	{
		comp.onClosed();
		children.remove(comp);
		box.removeChild(comp.getBox());
		if(comp instanceof IButtonComponent)
		{
			owner.removeButtonListener((IButtonComponent)comp);
		}
		if(comp instanceof IKeyComponent)
		{
			owner.removeKeyListener((IKeyComponent)comp);
		}
		return this;
	}
	
	public GuiComponent removeChildren()
	{
		for(GuiComponent comp : children)
		{
			comp.onClosed();
			if(comp instanceof IButtonComponent)
			{
				owner.removeButtonListener((IButtonComponent)comp);
			}
			if(comp instanceof IKeyComponent)
			{
				owner.removeKeyListener((IKeyComponent)comp);
			}
		}
		children.clear();
		box.clearChildren();
		return this;
	}
	
	public IGuiBox addBox(IGuiBox box)
	{
		this.box.addChild(box);
		return box;
	}
	
	public GuiComponent removeBox(IGuiBox box)
	{
		this.box.removeChild(box);
		return this;
	}
	
	public final GuiComponent onAction(Consumer<GuiComponent> listener)
	{
		return addListener(listener, GuiComponent.LISTENER_USER_ACTION);
	}
	
	public final GuiComponent onAction(Runnable listener)
	{
		return addListener(listener, GuiComponent.LISTENER_USER_ACTION);
	}
	
	public final GuiComponent onChange(Consumer<GuiComponent> listener)
	{
		return addListener(listener, GuiComponent.LISTENER_ON_CHANGE);
	}
	
	public final GuiComponent onChange(Runnable listener)
	{
		return addListener(listener, GuiComponent.LISTENER_ON_CHANGE);
	}
	
	public final GuiComponent onClose(Consumer<GuiComponent> listener)
	{
		return addListener(listener, GuiComponent.LISTENER_CLOSED);
	}
	
	public final GuiComponent onClose(Runnable listener)
	{
		return addListener(listener, GuiComponent.LISTENER_CLOSED);
	}
	
	public final GuiComponent addListener(Runnable runnable, int index)
	{
		listeners[index].add(_ -> runnable.run());
		return this;
	}
	
	public final GuiComponent addListener(Consumer<GuiComponent> listener, int index)
	{
		listeners[index].add(listener);
		return this;
	}
	
	protected final void notifyListeners(int index)
	{
		if(listeners[index].size() > 0)
		{
			for(Consumer<GuiComponent> comp : listeners[index])
			{
				comp.accept(this);
			}
		}
	}
	
	public final GuiComponent removeUserActionListener(Consumer<GuiComponent> listener)
	{
		return removeListener(listener, GuiComponent.LISTENER_USER_ACTION);
	}
	
	public final GuiComponent removeChangeListener(Consumer<GuiComponent> listener)
	{
		return removeListener(listener, GuiComponent.LISTENER_ON_CHANGE);
	}
	
	public final GuiComponent removeCloseListener(Consumer<GuiComponent> listener)
	{
		return removeListener(listener, GuiComponent.LISTENER_CLOSED);
	}
	
	public final GuiComponent removeListener(Consumer<GuiComponent> listener, int index)
	{
		listeners[index].remove(listener);
		return this;
	}
	
	public GuiComponent move(float x, float y)
	{
		if(x == 0F && y == 0F || constraints != null) return this;
		box.move(x, y);
		onChanged(false);
		return this;
	}
	
	public GuiComponent set(float x, float y)
	{
		if(box.getBaseX() == x && box.getBaseY() == y || constraints != null) return this;
		box.setXY(x, y);
		onChanged(false);
		return this;
	}
	
	public GuiComponent resize(float moveX, float moveY)
	{
		if(moveX == 0F && moveY == 0F || constraints != null) return this;
		box.resize(moveX, moveY);
		onChanged(true);
		return this;
	}
	
	public GuiComponent bounds(float width, float height)
	{
		if(box.getBaseWidth() == width && box.getBaseHeight() == height || constraints != null) return this;
		box.setBounds(width, height);
		onChanged(true);
		return this;
	}
	
	public final void onChanged(boolean repaint)
	{
		if(owner == null) return;
		if(isFlagSet(FLAG_MASS_CHANGE))
		{
			changed = true;
			massRepaint |= repaint;
			return;
		}
		massRepaint = false;
		changed = false;
		if(constraints != null)
		{
			constraints.onComponentChanged();
			if(animation != null) animation.applyValues(false);
		}
		box.onChanged();
		totalZ = 0F;
		GuiComponent zComp = this;
		while(zComp != null)
		{
			totalZ += 0.01F + zComp.getZOffset();
			zComp = zComp.parent;
		}
		notifyListeners(LISTENER_ON_CHANGE);
		updateState();
		if(repaint) repaint();
		if(children.isEmpty()) return;
		for(GuiComponent comp : children)
		{
			comp.onChanged(repaint);
		}
	}
	
	protected void updateState()
	{
		
	}
	
	protected void repaint()
	{
		
	}
	
	public final void fixedUpdate()
	{
		if(fixedUpdateSelf()) fixedUpdateChildren();
	}
	
	public final void update(int mouseX, int mouseY, float particalTicks)
	{
		if(animation != null) animation.update(particalTicks);
		if(updateSelf(mouseX, mouseY, particalTicks)) updateChildren(mouseX, mouseY, particalTicks);
	}
	
	protected void preRender()
	{
		
	}
	
	public final void render(int mouseX, int mouseY, float particalTicks)
	{
		if(customRenderer != null)
		{
			customRenderer.onPreRender(this);
			getRenderer().setVisibility(totalVisibility).setBrightness(brightness);
			if(customRenderer.render(this)) renderChildren(mouseX, mouseY, particalTicks);
			customRenderer.onPostRender(this);
			getRenderer().resetEffects();			
		}
		else
		{
			preRender();
			getRenderer().setVisibility(totalVisibility).setBrightness(brightness);
			if(renderSelf(mouseX, mouseY, particalTicks)) renderChildren(mouseX, mouseY, particalTicks);
			postRender();
			getRenderer().resetEffects();			
		}
		if(getGui() instanceof GuiScreenBase)
		{
			((GuiScreenBase)getGui()).drawBox(this);
		}
	}
	
	protected void postRender()
	{
		
	}
	
	protected boolean fixedUpdateSelf()
	{
		return true;
	}
	
	protected boolean updateSelf(int mouseX, int mouseY, float particalTicks)
	{
		return true;
	}
	
	protected boolean renderSelf(int mouseX, int mouseY, float particalTicks)
	{
		return true;
	}
	
	public void collectTooltips(int mouseX, int mouseY, float particalTicks, Map<UUID, GuiComponent> collector)
	{
		if(isParentVisible())
		{
			if(isHovered(mouseX, mouseY))
			{
				tooltips.merge(collector);
			}
			if(children.size() > 0)
			{
				for(GuiComponent entry : children)
				{
					entry.collectTooltips(mouseX, mouseY, particalTicks, collector);
				}
			}
		}
	}
	
	public void fixedUpdateChildren()
	{
		for(GuiComponent entry : children)
		{
			if(entry.isVisible())
			{
				entry.fixedUpdate();
			}
		}
	}
	
	public void updateChildren(int mouseX, int mouseY, float particalTicks)
	{
		for(GuiComponent entry : children)
		{
			if(entry.isVisible())
			{
				entry.update(mouseX, mouseY, particalTicks);
			}
		}
	}
	
	public void renderChildren(int mouseX, int mouseY, float particalTicks)
	{
		for(GuiComponent entry : children)
		{
			if(!entry.isManualRender() && entry.isVisible() && (!isTestingScissors() || isInScissors(entry.getBox())))
			{
				float zOffset = entry.getZOffset() + 0.01F;
				getRenderer().push();
				getRenderer().translate(0F, 0F, zOffset);
				entry.preRender();
				entry.render(mouseX, mouseY, particalTicks);
				entry.postRender();
				getRenderer().translate(0F, 0F, -zOffset);
				getRenderer().pop();
			}
		}
	}
	
	public Animator getAnimator()
	{
		if(animation == null)
		{
			animation = new Animator(this);
		}
		return animation;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T cast()
	{
		return (T)this;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T cast(Class<T> clz)
	{
		return (T)this;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T tryCast(Class<T> clz)
	{
		return clz.isInstance(this) ? (T)this : null;
	}
	
	public GuiComponent getTopComponent()
	{
		GuiComponent top = this;
		while(top.parent != null)
		{
			top = top.parent;
		}
		return top;
	}
	
	protected void requestFocus()
	{
		getGui().requestComponentFocus(this);
	}
	
	protected boolean isFocused()
	{
		return getGui().isComponentFocused(this);
	}
	
	protected boolean isFocusedOrChilds()
	{
		return isFocused() || isChildFocused();
	}
	
	protected boolean isChildFocused()
	{
		for(GuiComponent comp : children)
		{
			if(comp.isFocusedOrChilds())
			{
				return true;
			}
		}
		return false;
	}
	
	protected UIRenderer getRenderer()
	{
		return owner.getRenderer();
	}
	
	protected Window getWindow()
	{
		return owner.getWindow();
	}
	
	protected boolean isSelect(int keyCode)
	{
		return keyCode == GLFW.GLFW_KEY_A && Keyboard.isCtrlDown() && !Keyboard.isShiftDown() && !Keyboard.isAltDown();
	}
	
	protected boolean isCopy(int keyCode)
	{
		return keyCode == GLFW.GLFW_KEY_C && Keyboard.isCtrlDown() && !Keyboard.isShiftDown() && !Keyboard.isAltDown();
	}
	
	protected boolean isPaste(int keyCode)
	{
		return keyCode == GLFW.GLFW_KEY_V && Keyboard.isCtrlDown() && !Keyboard.isShiftDown() && !Keyboard.isAltDown();
	}
	
	protected boolean isCut(int keyCode)
	{
		return keyCode == GLFW.GLFW_KEY_X && Keyboard.isCtrlDown() && !Keyboard.isShiftDown() && !Keyboard.isAltDown();
	}
	
	protected final float getBrightness(int mouseX, int mouseY)
	{
		return isEnabled() ? (isHovered(mouseX, mouseY) ? 0.7F : 1F) : 0.5F;
	}
	
	public final float getActiveBrightness()
	{
		return isEnabled() ? 1F : 0.5F;
	}
	
	public long getGlobalClock()
	{
		return getGui().getGlobalClock();
	}
	
	protected final void bindCursor(AssetLocation location)
	{
		Cursor.INSTANCE.bindCursor(location, getWindow());
	}
	
	protected final void clearCursor()
	{
		Cursor.INSTANCE.clearCursor(getWindow());
	}
	
	protected final void enableScissors(Plane box)
	{
		enableScissors(box.getMinX(), box.getMinY(), box.getWidth(), box.getHeight());
	}
	
	protected final void enableScissors(IGuiBox box)
	{
		enableScissors((int)box.getMinX(), (int)box.getMinY(), (int)box.getWidth(), (int)box.getHeight());
	}
	
	protected final void enableScissorsBox(float minX, float minY, float maxX, float maxY)
	{
		enableScissors((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
	}
	
	protected final void enableScissors(float x, float y, float width, float height)
	{
		enableScissors((int)x, (int)y, (int)width, (int)height);
	}
	
	protected final void enableScissors(int x, int y, int width, int height)
	{
		getRenderer().flush();
		int bottom = y + height;
		Window window = owner.getWindow();
		Vec2d vec = owner.getUIManager().res.getScaleVec();
		GLStateTracker.instance().scissors.push((int)(x * vec.x()), (int)(window.getHeight() - bottom * vec.y()), (int)(width * vec.x()), (int)(height * vec.y()));
	}
	
	protected final boolean isInScissors(Plane box)
	{
		return isInScissors(box.getMinX(), box.getMinY(), box.getWidth(), box.getHeight());
	}
	
	protected final boolean isInScissors(IGuiBox box)
	{
		return isInScissors((int)box.getMinX(), (int)box.getMinY(), (int)box.getWidth(), (int)box.getHeight());
	}
	
	protected final boolean isInScissors(float minX, float minY, float maxX, float maxY)
	{
		return isInScissors((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
	}
	
	protected final boolean isInScissors(int x, int y, int width, int height)
	{
		int bottom = y + height;
		Window window = owner.getWindow();
		Vec2d vec = owner.getUIManager().res.getScaleVec();
		return GLStateTracker.instance().scissors.contains((int)(x * vec.x()), (int)(window.getHeight() - bottom * vec.y()), (int)(width * vec.x()), (int)(height * vec.y()));
	}
	
	public final void disableScissors()
	{
		getRenderer().flush();
		GLStateTracker.instance().scissors.pop();
	}
	
	class KeyBindAction implements IKeyComponent
	{
		int key;
		int mod;
		boolean block;
		UUID tooltip = UUID.randomUUID();
		
		public KeyBindAction(int key, int mod, boolean block)
		{
			this.key = key;
			this.mod = mod;
			this.block = block;
		}
		
		public UUID getTooltip()
		{
			return tooltip;
		}
		
		@Override
		public boolean isAcceptingInput()
		{
			return isAnyFlagSet(FLAG_ALWAYS_CLICKABLE | FLAG_ENABLED);
		}
		
		@Override
		public boolean isBlockingMovement()
		{
			return block;
		}
		
		@Override
		public boolean isPopup()
		{
			return isPopupButton(GuiComponent.this) || isPopupButton(getTopComponent());
		}
		
		@Override
		public boolean hasChildPopups()
		{
			return hasPopups();
		}
		
		private boolean isPopupButton(GuiComponent comp)
		{
			return comp instanceof IButtonComponent ? ((IButtonComponent)comp).isPopup() : false;
		}
		
		@Override
		public boolean onKeyPressed(int key)
		{
			if(key == this.key && ModType.isActive(mod))
			{
				return onUserKey();
			}
			return false;
		}
	}
}
