package speiger.src.coreengine.rendering.guiOld.base;

import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.IntToLongFunction;

import speiger.src.collections.ints.maps.impl.hash.Int2ObjectLinkedOpenHashMap;
import speiger.src.collections.ints.maps.impl.hash.Int2ObjectOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectOrderedMap;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.maps.impl.hash.Object2BooleanLinkedOpenHashMap;
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectLinkedOpenHashMap;
import speiger.src.collections.objects.maps.interfaces.Object2BooleanOrderedMap;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.collections.objects.sets.ObjectLinkedOpenHashSet;
import speiger.src.collections.objects.sets.ObjectOrderedSet;
import speiger.src.collections.objects.utils.maps.Object2ObjectMaps;
import speiger.src.coreengine.math.MathUtils;
import speiger.src.coreengine.math.color.ColorUtils;
import speiger.src.coreengine.rendering.guiOld.GuiBase;
import speiger.src.coreengine.rendering.guiOld.GuiComponent;
import speiger.src.coreengine.rendering.guiOld.components.TooltipPanel;
import speiger.src.coreengine.rendering.guiOld.helper.constrains.Constrains;
import speiger.src.coreengine.rendering.guiOld.renderer.UIRenderer;
import speiger.src.coreengine.utils.collections.iterators.IterableWrapper;

public class GuiScreenBase extends GuiBase
{
	Int2ObjectMap<GuiComponent> getters = new Int2ObjectOpenHashMap<>();
	Set<GuiComponent> components = new ObjectLinkedOpenHashSet<>();
	Set<IKeyComponent> keyOrder = new ObjectLinkedOpenHashSet<>();
	ObjectOrderedSet<GuiComponent> renderOrder = new ObjectLinkedOpenHashSet<>();
	Object2BooleanOrderedMap<IButtonComponent> buttonOrder = new Object2BooleanLinkedOpenHashMap<>();
	Int2ObjectOrderedMap<IButtonComponent> selectedButtons = new Int2ObjectLinkedOpenHashMap<>();
	Set<IButtonComponent> draggingButtons = new ObjectLinkedOpenHashSet<>();
	TooltipPanel tooltips = new TooltipPanel();
	int lastMouseX = -1;
	int lastMouseY = -1;
	long lastTooltipCheck = 0;
	boolean drawsTooltip = false;
	long delay = 200L;
	
	@Override
	public void onInit()
	{
		super.onInit();
		addComponent(tooltips);
	}
	
	public void setDelay(long delay, TimeUnit unit)
	{
		this.delay = Math.max(0, unit.toMillis(delay));
	}
	
	@Override
	public <T extends GuiComponent> T addComponent(T comp, Constrains contrains)
	{
		components.add(comp);
		renderOrder.addAndMoveToLast(comp);
		addConstrains(comp, contrains);
		comp.setOwner(this);
		if(comp instanceof IButtonComponent)
		{
			buttonOrder.put((IButtonComponent)comp, true);
			addButtonListener((IButtonComponent)comp);
		}
		if(comp instanceof IKeyComponent)
		{
			addKeyListener((IKeyComponent)comp);
		}
		return comp;
	}
	
	@Override
	public <T extends GuiComponent> T addComponent(int id, T comp, Constrains contrains)
	{
		getters.put(id, comp);
		return addComponent(comp, contrains);
	}
	
	@Override
	public void addButtonListener(IButtonComponent listener)
	{
		buttonOrder.putAndMoveToFirst(listener, false);
	}
	
	@Override
	public void addKeyListener(IKeyComponent listener)
	{
		keyOrder.add(listener);
	}
	
	@Override
	public GuiComponent getComponent(int id)
	{
		return getters.get(id);
	}
	
	@Override
	public void removeComponent(GuiComponent comp)
	{
		if(comp != null)
		{
			comp.onClosed();
		}
		components.remove(comp);
		renderOrder.remove(comp);
		getters.values().remove(comp);
		if(comp instanceof IButtonComponent)
		{
			removeButtonListener((IButtonComponent)comp);
		}
		if(comp instanceof IKeyComponent)
		{
			removeKeyListener((IKeyComponent)comp);
		}
	}
	
	@Override
	public void removeButtonListener(IButtonComponent listener)
	{
		buttonOrder.rem(listener);
	}
	
	@Override
	public void removeKeyListener(IKeyComponent listener)
	{
		keyOrder.remove(listener);
	}
	
	@Override
	public void onClosed()
	{
		super.onClosed();
		for(GuiComponent entry : components)
		{
			entry.onClosed();
		}
		getters.clear();
		renderOrder.clear();
		buttonOrder.clear();
	}
	
	@Override
	public void onScreenChanged()
	{
		super.onScreenChanged();
		for(GuiComponent entry : components)
		{
			entry.onChanged(true);
		}
	}
	
	@Override
	public void onFixedUpdate()
	{
		for(GuiComponent entry : renderOrder)
		{
			if(entry.isVisible())
			{
				entry.fixedUpdate();
			}
		}
	}
	
	@Override
	protected void update(int mouseX, int mouseY, float particalTicks)
	{
		for(GuiComponent entry : renderOrder)
		{
			if(entry.isVisible())
			{
				entry.update(mouseX, mouseY, particalTicks);
			}
		}
	}
	
	@Override
	protected void render(int mouseX, int mouseY, float particalTicks)
	{
		layers = (1 + getBaseLayer());
		UIRenderer render = getRenderer();
		float biggestZ = 0.0F;
		float extra = 1.0F;
		Object2ObjectMap<UUID, GuiComponent> components = hasFocus() ? new Object2ObjectLinkedOpenHashMap<>() : Object2ObjectMaps.empty();
		for(GuiComponent base : renderOrder)
		{
			if(base.isVisible() && !base.isManualRender())
			{
				float z = base.getZOffset();
				boolean layer = base.usesRenderOrder();
				render.translate(0.0F, 0.0F, layers + z + (layer ? extra : 0.0F));
				base.render(mouseX, mouseY, particalTicks);
				render.resetTransform();
				biggestZ = Math.max(biggestZ, z);
				if(layer)
				{
					extra += 1.0F;
				}
			}
			if(hasFocus())
			{
				base.collectTooltips(mouseX, mouseY, particalTicks, components);
			}
		}
		layers += MathUtils.floor(biggestZ + extra);
		if(hasFocus())
		{
			if(!drawsTooltip && (lastMouseX != mouseX || lastMouseY != mouseY) || components.isEmpty())
			{
				lastTooltipCheck = System.currentTimeMillis();
				lastMouseX = mouseX;
				lastMouseY = mouseY;
				if(components.isEmpty())
				{
					tooltips.updateTooltips(components);
					drawsTooltip = false;
				}
			}
			else if(System.currentTimeMillis() - lastTooltipCheck >= delay)
			{
				drawsTooltip = true;
				tooltips.updateTooltips(components);
				tooltips.set(mouseX+tooltips.isOutsideScreen(mouseX, width), mouseY);
				render.translate(0.0F, 0.0F, layers + 50F);
				tooltips.render(mouseX, mouseY, particalTicks);
				render.resetTransform();
			}
		}
	    render.resetEffects();
	}
	
	public void drawBox(GuiComponent comp)
	{
		if(!getUIManager().isRenderUIBoxes() && comp.isFlagNotSet(GuiComponent.FLAG_RENDER_BOUNDS)) return;
		UIRenderer render = getRenderer();
		render.translate(0F, 0F, 100F);
		render.drawFrame(comp.getBox(), ColorUtils.RED);
		render.translate(0F, 0F, -100F);
	}
	
	@Override
	public boolean isAllowingMovement()
	{
		for(IKeyComponent comp : findKeyPopups())
		{
			if(comp.isAcceptingInput() && comp.isBlockingMovement())
			{
				return false;
			}
		}
		return true;
	}
	
	@Override
	public boolean onKeyTyped(char letter, int codepoint)
	{
		for(IKeyComponent comp : findKeyPopups())
		{
			if(comp.isAcceptingInput() && comp.onKeyTyped(letter, codepoint))
			{
				return true;
			}
		}
		return false;
	}
	
	@Override
	public boolean onKeyPressed(int key)
	{
		for(IKeyComponent comp : findKeyPopups())
		{
			if(comp.isAcceptingInput() && comp.onKeyPressed(key))
			{
				return true;
			}
		}
		return false;
	}
	
	@Override
	public boolean onMousePressed(int button, int mouseX, int mouseY)
	{
		if(buttonOrder.isEmpty()) return false;
		List<IButtonComponent> components = new ObjectArrayList<>();
		for(Iterator<IButtonComponent> iter = findPopups();iter.hasNext();)
		{
			IButtonComponent base = iter.next();
			if(!base.isValidButton(button)) continue;
			if(!base.isComponentColliding(mouseX, mouseY))
			{
				components.add(base);
				continue;
			}
			if(!base.onClick(button, mouseX, mouseY)) continue;
			buttonOrder.getAndMoveToFirst(base);
			selectedButtons.put(button, base);
			draggingButtons.add(base);
			GuiComponent top = getTopComponent(base);
			if(base.canMoveIntoForground())
			{
				if(buttonOrder.getAndMoveToFirst(base) && base instanceof GuiComponent)
				{
					requestComponentFocus(((GuiComponent)base).getTopComponent());
				}
				else
				{
					requestComponentFocus(top);
				}
			}
			else if(top instanceof IButtonComponent && ((IButtonComponent)top).canMoveIntoForground())
			{
				requestComponentFocus(top);
			}
			for(int i = 0,m=components.size();i<m;i++)
			{
				components.get(i).onFocusLost();
			}
			while(iter.hasNext())
			{
				IButtonComponent com = iter.next();
				if(com == null || com == base)
				{
					continue;
				}
				com.onFocusLost();
			}
			return true;
		}
		for(int i = 0,m=components.size();i<m;i++)
		{
			components.get(i).onFocusLost();
		}
		return false;
	}
	
	@Override
	public boolean onMouseDragged(IntSet activeButtons, int mouseX, int mouseY, IntToLongFunction timeMap)
	{
		boolean result = false;
		for(IButtonComponent button : draggingButtons)
		{
			result |= button.onDrag(mouseX, mouseY);
		}
		return result;
	}
	
	@Override
	public boolean onMouseReleased(int button, int mouseX, int mouseY)
	{
		IButtonComponent buttonObj = selectedButtons.remove(button);
		if(buttonObj != null)
		{
			if(buttonObj.isComponentColliding(mouseX, mouseY))
			{
				buttonObj.onRelease(button, mouseX, mouseY);
				draggingButtons.retainAll(selectedButtons.values());
				return true;				
			}
			buttonObj.onFocusLost();
		}
		return false;
	}
	
	@Override
	public boolean onMouseScroll(int mouseX, int mouseY, int scrollX)
	{
		if(buttonOrder.isEmpty())
		{
			return false;
		}
		for(IButtonComponent base : IterableWrapper.wrap(findPopups()))
		{
			if(!base.isComponentColliding(mouseX, mouseY))
			{
				continue;
			}
			if(base.onScroll(scrollX, mouseX, mouseY))
			{
				return true;
			}
		}
		return false;
	}
	
	public Iterator<IButtonComponent> findPopups()
	{
		List<IButtonComponent> popups = new ObjectArrayList<IButtonComponent>();
		for(IButtonComponent button : buttonOrder.keySet())
		{
			if(button.isPopup() && !button.hasChildPopups())
			{
				popups.add(button);
				continue;
			}
			GuiComponent comp = getTopComponent(button);
			if(comp instanceof IButtonComponent && ((IButtonComponent)comp).isPopup() && !((IButtonComponent)comp).hasChildPopups())
			{
				popups.add(button);
			}
		}
		return sortByHeight(popups.isEmpty() ? new ObjectArrayList<IButtonComponent>(buttonOrder.keySet()) : popups);
	}
	
	protected Iterator<IButtonComponent> sortByHeight(List<IButtonComponent> comp)
	{
		comp.sort(ButtonSorter.SORTER);
		return comp.iterator();
	}
	
	protected Set<IKeyComponent> findKeyPopups()
	{
		Set<IKeyComponent> components = new ObjectLinkedOpenHashSet<IKeyComponent>();
		for(IKeyComponent key : keyOrder)
		{
			if(key.isPopup() && !key.hasChildPopups())
			{
				components.add(key);
				continue;
			}
			GuiComponent comp = getTopComponent(key);
			if((comp instanceof IKeyComponent && ((IKeyComponent)comp).isPopup() && !((IKeyComponent)comp).hasChildPopups()) || (comp instanceof IButtonComponent && ((IButtonComponent)comp).isPopup()) && !((IButtonComponent)comp).hasChildPopups())
			{
				components.add(key);
			}
		}
		return components.isEmpty() ? keyOrder : components;
	}
	
	protected GuiComponent getTopComponent(IKeyComponent button)
	{
		return button instanceof GuiComponent ? ((GuiComponent)button).getTopComponent() : null;
	}
	
	protected GuiComponent getTopComponent(IButtonComponent button)
	{
		return button instanceof GuiComponent ? ((GuiComponent)button).getTopComponent() : null;
	}
	
	@Override
	public void requestComponentFocus(GuiComponent comp)
	{
		if(!comp.hasPopups())
		{
			renderOrder.moveToLast(comp);
		}
	}
	
	@Override
	public boolean hasComponentInTheWay(GuiComponent comp, int mouseX, int mouseY)
	{
		for(GuiComponent other : renderOrder)
		{
			if(other == comp) break;
			if(other.usesRenderOrder() && other.isHovered(mouseX, mouseY)) return true;
		}
		return false;
	}
	
	@Override
	public boolean isComponentInWay(GuiComponent comp, int mouseX, int mouseY)
	{
		for(IButtonComponent entry : buttonOrder.keySet())
		{
			if(entry != comp && entry instanceof GuiComponent && ((GuiComponent)entry).isHovered(mouseX, mouseY))
			{
				return true;
			}
		}
		return false;
	}
	
	@Override
	public boolean isComponentFocused(GuiComponent comp)
	{
		return hasFocus() && buttonOrder.size() > 0 && ((buttonOrder.size() == 1 && buttonOrder.containsKey((IButtonComponent)comp)) || buttonOrder.firstKey() == comp);
	}
	
	@Override
	public boolean isComponentInFront(GuiComponent comp)
	{
		if(!hasFocus() || renderOrder.getLast() == comp || (renderOrder.getLast() instanceof IButtonComponent && ((IButtonComponent)renderOrder.getLast()).isPopup()))
		{
			return true;
		}
		return false;
	}
	
	@Override
	public boolean hasComponentPressed(int mouseButton)
	{
		return selectedButtons.containsKey(mouseButton);
	}
	
	static final class ButtonSorter implements Comparator<IButtonComponent>
	{
		public static final ButtonSorter SORTER = new ButtonSorter();
		@Override
		public int compare(IButtonComponent o1, IButtonComponent o2)
		{
			return Float.compare(o2.getComponentZ(), o1.getComponentZ());
		}
		
	}
}
