package speiger.src.coreengine.rendering.gui.components.base;

import java.util.List;
import java.util.function.Consumer;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.math.bits.FlagObject;
import speiger.src.coreengine.rendering.gui.animation.GuiAnimator;
import speiger.src.coreengine.rendering.gui.layout.constraints.ConstrainedContext;
import speiger.src.coreengine.rendering.gui.layout.constraints.ConstraintContainer;
import speiger.src.coreengine.rendering.gui.renderer.IUIRenderer;
import speiger.src.coreengine.rendering.guiOld.helper.box.IGuiBox;
import speiger.src.coreengine.utils.collections.CollectionUtils;
import speiger.src.coreengine.utils.misc.ICastable;

public abstract non-sealed class GuiComponent extends FlagObject implements ICastable, ILayoutComponent, IListableComponent {
	private static final int FLAG_FOCUSED = 1 << 0;
	private static final int FLAG_ENABLED = 1 << 1;
	private static final int FLAG_VISIBLE = 1 << 2;
	private static final int FLAG_MANUAL_MANAGED = 1 << 3;
	private static final int FLAG_SCISSORED = 1 << 4;
	final IGuiBox box;
	IComponentScreen screen;
	ConstraintContainer constraints;
	ConstrainedContext constraintedContext = new ConstrainedContext();
	GuiAnimator animator;
	GuiComponent parent;
	List<GuiComponent> children = new ObjectArrayList<>();
	InteractionContainer interactions = new InteractionContainer(this::isInteractable);
	List<Consumer<GuiComponent>>[] listeners = CollectionUtils.createList(IListableComponent.MAX_LISTENER_TYPES);
	IComponentRenderer<GuiComponent> renderer;
	
	public GuiComponent(float x, float y, float width, float height) {
		this(IGuiBox.of(x, y, width, height));
	}
	
	public GuiComponent(IGuiBox box) {
		this.box = box;
		if(this instanceof IInteractable actor) { 
			interactions.add(actor); 
		}
	}
	
	@Override
	public void calculateBounds(ILayoutScanner output) {
		output.accept(getBox());
		for(GuiComponent child : children) {
			if(child.isVisible() || output.acceptsInvisble()) {
				child.calculateBounds(output);
			}
		}
	}
	
	public void setScreen(IComponentScreen screen) {
		this.screen = screen;
		for(GuiComponent child : children) {
			child.setScreen(screen);
		}
	}
	
	public abstract void init();
	public void close() {
		notifyListeners(LISTENER_CLOSED);
	}
	
	public void updateAnimations(float partialTicks) {
		if(animator == null || animator.update(partialTicks)) return;
		animator = null;
	}
	
	protected void updateComponent() {
	}
	
	protected void renderComponent(IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) {
		renderChildren(renderer, mouseX, mouseY, partialTicks);
	}
	
	protected void renderChildren(IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) {
		for(GuiComponent child : children) {
			if(child.isManualManaged()) continue;
			renderComponent(child, renderer, mouseX, mouseY, partialTicks);
		}
	}
	
	public static void tickComponent(GuiComponent comp) {
		if(comp.renderer != null && comp.renderer.overrideTick()) {
			comp.renderer.updateComponent(comp);
		}
		else comp.updateComponent();
		comp.children.forEach(GuiComponent::tickComponent);
	}
	
	public static boolean renderComponent(GuiComponent comp, IUIRenderer renderer, int mouseX, int mouseY, float partialTicks) {
		if(!comp.isVisible()) return false;
		comp.updateAnimations(partialTicks);
		if(comp.isScissored()) {
			if(!renderer.isInScissors(comp.getBox())) return false;
			renderer.pushScissors(comp.getBox());
			if(comp.renderer != null) {
				comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, partialTicks);
				comp.renderChildren(renderer, mouseX, mouseY, partialTicks);
			}
			else comp.renderComponent(renderer, mouseX, mouseY, partialTicks);
			renderer.popScissors();
		}
		else if(comp.renderer != null) {
			comp.renderer.renderComponent(comp, renderer, mouseX, mouseY, partialTicks);
			comp.renderChildren(renderer, mouseX, mouseY, partialTicks);
		}
		else comp.renderComponent(renderer, mouseX, mouseY, partialTicks);
		return true;
	}
	
	private ConstrainedContext parentContext() {
		return parent != null ? parent.constraintedContext : null;
	}
	
	public GuiComponent withConstraints(ConstraintContainer container) {
		this.constraints = container;
		if((screen != null || parent != null) && constraints != null) constraints.apply(this, parent, parentContext());
		return this;
	}
	
	public ConstraintContainer constraint() {
		return constraints;
	}
	
	public GuiComponent addChild(GuiComponent child) {
		if(child.parent != null) throw new IllegalArgumentException("A Child can not have multiple parents");
		child.parent = this;
		children.add(child);
		box.addChild(child.getBox());
		interactions.add(child.interactions);
		child.setScreen(screen);
		child.init();
		child.onChanged(true);
		return child;
	}
	
	public boolean containsChild(GuiComponent child) {
		return child.parent == this && children.contains(child);
	}
	
	public void removeChild(GuiComponent child) {
		if(child.parent != this) throw new IllegalArgumentException("Child isn't owned by this Component");
		child.close();
		child.parent = null;
		child.screen = null;
		children.remove(child);
		box.removeChild(child.getBox());
		interactions.remove(child.interactions);
	}
	
	@Override
	public GuiComponent addListener(Consumer<GuiComponent> listener, int index) {
		listeners[index].add(listener);
		return this;
	}
	
	@Override
	public GuiComponent removeListener(Consumer<GuiComponent> listener, int index) {
		listeners[index].remove(listener);
		return this;
	}
	
	@Override
	public void notifyListeners(int index) {
		if(listeners[index].isEmpty()) return;
		for(Consumer<GuiComponent> comp : listeners[index]) {
			comp.accept(this);
		}
	}
	
	public void onChanged(boolean repaint) {
		if(constraints != null) constraints.apply(this, parent, parentContext());
		if(animator != null) animator.apply();
		box.onChanged();
		constraintedContext.update(this, children);
		notifyListeners(LISTENER_ON_CHANGE);
		if(repaint) repaint();
		if(children.isEmpty()) return;
		int index = 0;
		for(GuiComponent comp : children) {
			constraintedContext.setCurrent(index++);
			comp.onChanged(repaint);
		}
	}
	
	protected void repaint() {}
	
	public IComponentScreen screen() { return screen; }
	@Override
	public IGuiBox getBox() { return box; }
	public IInteractable interactContainer() { return interactions; }
	
	
	@Override
	public GuiComponent set(float x, float y) {
		if(box.getBaseX() != x || box.getBaseY() != y) {
			box.setXY(x, y);
			onChanged(false);
		}
		return this;
	}
	
	@Override
	public GuiComponent bounds(float width, float height) {
		if(box.getBaseWidth() != width || box.getBaseHeight() != height) {
			box.setBounds(width, height);
			onChanged(true);
		}
		return this;
	}
	
	@Override
	public GuiComponent resize(float width, float height) {
		if(width != 0F || height != 0F) {
			box.resize(width, height);
			onChanged(true);
		}
		return this;
	}
	
	@Override
	public GuiComponent move(float moveX, float moveY) {
		if(moveX != 0F || moveY != 0F) {
			box.move(moveX, moveY);
			onChanged(false);
		}
		return this;
	}
	
	public final GuiComponent scale(float newScale) {
		if(newScale != box.getBaseScale()) {
			box.setScale(newScale);
			onChanged(true);
		}
		return this;
	}
	
	//@formatter:off
	private final boolean isInteractable() { return isFlagSet(FLAG_ENABLED | FLAG_VISIBLE); }
	public final boolean isFocused() { return isFlagSet(FLAG_FOCUSED); }
	public final boolean isEnabled() { return isFlagSet(FLAG_ENABLED); }
	public final boolean isVisible() { return isFlagSet(FLAG_VISIBLE); }
	public final boolean isManualManaged() { return isFlagSet(FLAG_MANUAL_MANAGED); }
	public final boolean isScissored() { return isFlagSet(FLAG_SCISSORED); }
	//@formatter:on
	
	public final void setFocused(boolean value) {
		setFlag(FLAG_FOCUSED, value);
	}
	
	public final GuiComponent setEnabled(boolean value) {
		setFlag(FLAG_ENABLED, value);
		return this;
	}
	
	public final GuiComponent setVisible(boolean value) {
		setFlag(FLAG_VISIBLE, value);
		return this;
	}
	
	public final GuiComponent setManualManaged(boolean value) {
		setFlag(FLAG_MANUAL_MANAGED, value);
		return this;
	}
	
	public final GuiComponent setScissored(boolean value) {
		setFlag(FLAG_SCISSORED, value);
		return this;
	}
}