package speiger.src.coreengine.rendering.guiOld;

import java.util.function.IntToLongFunction;

import speiger.src.collections.ints.maps.impl.hash.Int2LongOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2LongMap;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.coreengine.rendering.guiOld.base.DebugOverlay;
import speiger.src.coreengine.rendering.guiOld.base.IButtonComponent;
import speiger.src.coreengine.rendering.guiOld.base.IKeyComponent;
import speiger.src.coreengine.rendering.guiOld.helper.Align;
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.UIRenderer;
import speiger.src.coreengine.rendering.inputOld.events.MouseEvent;
import speiger.src.coreengine.rendering.inputOld.events.MouseEvent.MouseClickEvent;
import speiger.src.coreengine.rendering.inputOld.events.MouseEvent.MouseMoveEvent;
import speiger.src.coreengine.rendering.inputOld.events.MouseEvent.MouseScrollEvent;
import speiger.src.coreengine.rendering.inputOld.window.ScaledResolution;
import speiger.src.coreengine.rendering.inputOld.window.Window;
import speiger.src.coreengine.utils.profiler.IProfiler;

public abstract class GuiBase
{
	ButtonMap pressedButtons = new ButtonMap();
	public GuiManager manager;
	public int width;
	public int height;
	boolean focus = false;
	public int layers = 0;
	int baseLayer = 0;
	
	GuiBase prevGui;
	
	public final void setGuiBounds(GuiManager newManager, int screenWidth, int screenHeight)
	{
		manager = newManager;
		width = screenWidth;
		height = screenHeight;
	}
	
	public void onInit()
	{
		
	}
	
	public void onScreenChanged()
	{
		
	}
	
	public void onFocusLost()
	{
		
	}
	
	public void onClosed()
	{
		
	}
	
	public <T extends GuiComponent> T addComponent(T comp)
	{
		return addComponent(comp, null);
	}
	
	public <T extends GuiComponent> T addComponent(int id, T comp)
	{
		return addComponent(id, comp, null);
	}
	
	public <T extends GuiComponent> T addComponent(T comp, Constrain xPos, Constrain yPos, Constrain width, Constrain height)
	{
		return addComponent(comp, new Constrains(xPos, yPos, width, height));
	}
	
	public <T extends GuiComponent> T addComponent(int id, T comp, Constrain xPos, Constrain yPos, Constrain width, Constrain height)
	{
		return addComponent(id, comp, new Constrains(xPos, yPos, width, height));
	}
	
	public abstract <T extends GuiComponent> T addComponent(T comp, Constrains contrains);
	
	public abstract <T extends GuiComponent> T addComponent(int id, T comp, Constrains contrains);
	
	public <T extends GuiComponent> T centerComponent(T comp)
	{
		IGuiBox box = comp.getBox();
		comp.set(Align.CENTER.align(width, box.getWidth()), Align.CENTER.align(height, box.getHeight()));
		return comp;
	}
	
	public abstract void addButtonListener(IButtonComponent listener);
	
	public abstract void addKeyListener(IKeyComponent listener);
	
	public abstract GuiComponent getComponent(int id);
	
	public <T extends GuiComponent> T getCastedComponent(int id) { 
		GuiComponent component = getComponent(id);
		return component == null ? null : component.cast(); 
	}
	
	public <T extends GuiComponent> T getCastedComponent(int id, Class<T> clz) {
		GuiComponent component = getComponent(id);
		return component == null ? null : component.cast(clz); 
	}
	
	public abstract void removeComponent(GuiComponent comp);
	
	public abstract void removeButtonListener(IButtonComponent listener);
	
	public abstract void removeKeyListener(IKeyComponent listener);
	
	protected void addConstrains(GuiComponent comp, Constrains contrains)
	{
		comp.constraints = contrains;
		if(contrains != null)
		{
			contrains.setOwner(comp, null);
		}
	}
	
	public final void draw(int mouseX, int mouseY, float particalTicks)
	{
		IProfiler prov = manager.getGPUProfiler();
		prov.start(this instanceof DebugOverlay ? "Debug Overlay" : "Current UI");
		prov.start("Update");
		update(mouseX, mouseY, particalTicks);
		prov.next("Render");
		render(mouseX, mouseY, particalTicks);
		prov.stop();
		prov.stop();
	}
	
	public void onFixedUpdate()
	{
		
	}
	
	protected void update(int mouseX, int mouseY, float particalTicks)
	{
		
	}
	
	protected void render(int mouseX, int mouseY, float particalTicks)
	{
		
	}
	
	public boolean isAllowingMovement()
	{
		return true;
	}
	
	public boolean onKeyTyped(char letter, int codepoint)
	{
		return false;
	}
	
	public boolean onKeyPressed(int key)
	{
		return false;
	}
	
	public final void onMouseEvent(MouseEvent evt)
	{
		if(evt instanceof MouseClickEvent click)
		{
			if(click.isPress()) {
				pressedButtons.add(click.getButton());
				if(onMousePressed(click.getButton(), evt.getX(), evt.getY()))
					evt.cancel();
			}
			else
			{
				pressedButtons.remove(click.getButton());
				if(onMouseReleased(click.getButton(), evt.getX(), evt.getY()))
					evt.cancel();
			}
		}
		else if(evt instanceof MouseMoveEvent && pressedButtons.size() > 0) {
			if(onMouseDragged(pressedButtons.getButtons(), evt.getX(), evt.getY(), pressedButtons))
				evt.cancel();
		}
		else if(evt instanceof MouseScrollEvent scroll) {
			if(onMouseScroll(evt.getX(), evt.getY(), scroll.getScrollY()))
				evt.cancel();
		}
	}
	
	public boolean onMousePressed(int button, int mouseX, int mouseY)
	{
		return false;
	}
	
	public boolean onMouseReleased(int button, int mouseX, int mouseY)
	{
		return false;
	}
	
	public boolean onMouseDragged(IntSet activeButtons, int mouseX, int mouseY, IntToLongFunction timeMap)
	{
		return false;
	}
	
	public boolean onMouseScroll(int mouseX, int mouseY, int scrollX)
	{
		return false;
	}
	
	public boolean isButtonPressed(int mouseButton)
	{
		return pressedButtons.getButtons().contains(mouseButton);
	}
	
	public long getPressedTime(int mouseButton)
	{
		return pressedButtons.applyAsLong(mouseButton);
	}
	
	public boolean hasComponentPressed(int mouseButton)
	{
		return false;
	}
	
	public boolean hasFocus()
	{
		return focus;
	}
	
	public void requestComponentFocus(GuiComponent comp)
	{
	}
	
	public boolean hasComponentInTheWay(GuiComponent comp, int mouseX, int mouseY)
	{
		return false;
	}
	
	public boolean isComponentInWay(GuiComponent comp, int mouseX, int mouseY)
	{
		return false;
	}
	
	public boolean isComponentFocused(GuiComponent comp)
	{
		return false;
	}
	
	public boolean isComponentInFront(GuiComponent comp)
	{
		return false;
	}
	
	public Window getWindow()
	{
		return manager.window;
	}
	
	public FontRenderer getFont()
	{
		return getUIManager().font;
	}
	
	public final long getGlobalClock()
	{
		return manager.globalClock;
	}
	
	public void closeGui()
	{
		manager.closeGui();
	}
	
	public void openGui(GuiBase gui)
	{
		manager.showGui(gui, false);
	}
	
	public void openGuiWithPrev(GuiBase base)
	{
		manager.showGui(base, true);
	}
	
	public boolean hasPrevGui()
	{
		return prevGui != null;
	}
	
	public void setLastGui(GuiBase newLast)
	{
		prevGui = newLast;
	}
	
	public void openPrevGui()
	{
		openGui(prevGui);
	}
	
	public void openPrevGui(boolean keepPrev)
	{
		manager.showGui(prevGui, keepPrev);
	}
	
	public GuiManager getUIManager()
	{
		return manager;
	}
	
	public UIRenderer getRenderer()
	{
		return manager.renderer;
	}
	
	public DebugOverlay getOverlay()
	{
		return manager.getDebug();
	}
	
	public ScaledResolution getRes()
	{
		return manager.getRes();
	}
	
	public final int getBaseLayer()
	{
		return baseLayer;
	}
	
	@SuppressWarnings("unchecked")
	public <T extends GuiBase> T cast()
	{
		return (T)this;
	}
	
	@SuppressWarnings("unchecked")
	public <T extends GuiBase> T cast(Class<? extends T> clz)
	{
		return (T)this;
	}
	
	public static class ButtonMap implements IntToLongFunction
	{
		Int2LongMap timeMap = new Int2LongOpenHashMap();
		
		public void add(int button)
		{
			timeMap.put(button, System.nanoTime());
		}
		
		public void remove(int button)
		{
			timeMap.remove(button);
		}
		
		public int size()
		{
			return timeMap.size();
		}
		
		public IntSet getButtons()
		{
			return timeMap.keySet();
		}
		
		@Override
		public long applyAsLong(int value)
		{
			return System.nanoTime() - timeMap.get(value);
		}
	}
}
