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

import java.util.function.Consumer;
import java.util.function.ObjIntConsumer;

import speiger.src.coreengine.math.color.ColorUtils;
import speiger.src.coreengine.math.misc.Facing;
import speiger.src.coreengine.math.misc.FacingList;
import speiger.src.coreengine.math.value.IValue;
import speiger.src.coreengine.math.value.LiniarValue;
import speiger.src.coreengine.math.vector.floats.Vec2f;
import speiger.src.coreengine.math.vector.ints.Vec2i;
import speiger.src.coreengine.rendering.guiOld.GuiComponent;
import speiger.src.coreengine.rendering.guiOld.base.IButtonComponent;
import speiger.src.coreengine.rendering.guiOld.components.icon.CrossIcon;
import speiger.src.coreengine.rendering.guiOld.components.icon.LineIcon;
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.Constrains;
import speiger.src.coreengine.rendering.guiOld.helper.constrains.ParentConstrain;
import speiger.src.coreengine.rendering.guiOld.helper.constrains.PixelConstrain;
import speiger.src.coreengine.rendering.utils.Cursor;
import speiger.src.coreengine.utils.functions.ConsumerConverter;
import speiger.src.coreengine.utils.helpers.InternalThreadPools;
import speiger.src.coreengine.utils.io.GameLog;

public class WindowComponent extends PanelComponent implements IButtonComponent, ObjIntConsumer<GuiComponent>
{
	public static final Vec2f DEFAULT_MINIMUM_BOUNDS = Vec2f.of(75F, 7.5F);
	public static final int FLAG_MINIMIZED = 1 << 20;
	public static final int FLAG_RESIZEABLE = 1 << 21;
	public static final int FLAG_MOVEABLE = 1 << 22;
	public static final int FLAG_RESIZEABLE_HORIZONTAL = 1 << 23;
	public static final int FLAG_RESIZEABLE_VERTICAL = 1 << 24;
	public static final int FLAG_RESIZE_INVERT = 1 << 25;
	
	public static final int WINDOW_FLAG_CLOSEABLE = 1;
	public static final int WINDOW_FLAG_MINIMIZEABLE = 2;
	
	public static final int WINDOW_FLAGS = WINDOW_FLAG_CLOSEABLE | WINDOW_FLAG_MINIMIZEABLE;
	
	public static final int DEFAULT_FLAGS = WINDOW_FLAGS | FLAG_RESIZEABLE | FLAG_MOVEABLE;
	public static final int FIXED_SIZE_WINDOW = WINDOW_FLAGS | FLAG_MOVEABLE;
	public static final int FIXED_SIZE_POPUP = WINDOW_FLAG_CLOSEABLE | FLAG_MOVEABLE;
	public static final int DYNAMIC_POPUP = FIXED_SIZE_POPUP | FLAG_RESIZEABLE;
	public static final int UNCLOSEABLE_WINDOW = WINDOW_FLAG_MINIMIZEABLE | FLAG_RESIZEABLE | FLAG_MOVEABLE;
	public static final int SUB_WINDOW = WINDOW_FLAG_MINIMIZEABLE | FLAG_RESIZEABLE | FLAG_RESIZE_INVERT;
	
	final int flags;
	FacingList facing = null;
	String name;
	int color = ColorUtils.WINDOW_DEFAULT_BACKGROUND;
	Vec2f lastSize = Vec2f.mutable();
	Vec2i lastClick = Vec2i.mutable();
	IValue animation = null;
	protected final Consumer<GuiComponent> closeListener = new ConsumerConverter<GuiComponent>(0, this);
	protected final Consumer<GuiComponent> minimizedListener = new ConsumerConverter<GuiComponent>(2, this);
	
	public WindowComponent(float x, float y, float width, float height, int flags, String name)
	{
		super(x, y, width, height);
		this.name = name;
		this.flags = flags & WINDOW_FLAGS;
		setFlag(flags &= ~(WINDOW_FLAGS));
		setFlag(FLAG_RESIZEABLE_HORIZONTAL | FLAG_RESIZEABLE_VERTICAL);
		lastSize.set(getBox().getBaseWidth(), getBox().getBaseHeight());
	}

	@Override
	public void init()
	{
		super.init();
		LabelComponent label = new LabelComponent(name, ColorUtils.DARK_GRAY);
		label.getText().setTextScale(0.4F).horizontal(Align.LEFT_TOP).singleLine(true);
		addChild(label, new Constrains(null, null, new ParentConstrain(), new PixelConstrain(7.5F)));
		float offset = 9F;
		if((flags & WINDOW_FLAG_CLOSEABLE) != 0)
		{
			addChild(new IconButtonComponent(0F, 0F, 7.5F, 7.5F, ColorUtils.RED, new CrossIcon(ColorUtils.WHITE).setPadding(2.5F, 2F)).onAction(new ConsumerConverter<>(0, this)).setZOffset(0.001F), new Constrains(new PixelConstrain(offset).setInverted(), null, null, null));
			offset += 7.5F;
		}
		if((flags & WINDOW_FLAG_MINIMIZEABLE) != 0)
		{
			addChild(new IconButtonComponent(0F, 0F, 7.5F, 7.5F, ColorUtils.GRAY, new LineIcon(ColorUtils.WHITE, 0.7F, 0.25F)).onAction(new ConsumerConverter<>(1, this)).setZOffset(0.001F), new Constrains(new PixelConstrain(offset).setInverted(), null, null, null));
		}
		if(canMoveIntoForground())
		{
			setFlag(FLAG_RENDER_ORDER);
		}
	}
	
	protected void updateMinizedState(boolean value)
	{
		if(setFlag(FLAG_MINIMIZED, value))
		{
			onChanged(false);
		}
	}
	
	public final WindowComponent setMinimized(boolean value)
	{
		if((flags & WINDOW_FLAG_MINIMIZEABLE) != 0 && setFlag(FLAG_MINIMIZED, value))
		{
			if(value)
			{
				Vec2f last = InternalThreadPools.VEC2F.get().set(lastSize);
				bounds(last.x(), isFlagSet(FLAG_MINIMIZED) ? getMinimizedY() : last.y());
				lastSize.set(last);
				InternalThreadPools.VEC2F.accept(last.negate());
			}
			else bounds(lastSize.x(), lastSize.y());
			onChanged(false);
		}
		return this;
	}
	
	public final boolean isMinimized()
	{
		return isFlagSet(FLAG_MINIMIZED);
	}
	
	public Vec2f getMinimumBounds()
	{
		return DEFAULT_MINIMUM_BOUNDS;
	}
	
	public float getMinimizedY()
	{
		return 7.5F;
	}
	
	public WindowComponent setColor(int color)
	{
		this.color = color;
		return this;
	}
	
	@Override
	public void calculateActualBounds(float[] area, boolean start)
	{
		if(animation != null)
		{
			float scale = getBox().getScale();
			area[0] = Math.min(area[0], getBox().getMinX());
			area[1] = Math.min(area[1], getBox().getMinY());
			area[2] = Math.max(area[2], lastSize.x() * scale);
			area[3] = Math.max(area[3], animation.get(getMinimizedY(), lastSize.y()) * scale);
			return;
		}
		super.calculateActualBounds(area, start);
	}
	
	@Override
	protected void updateState()
	{
		if(animation == null)
		{
			if(isFlagSet(FLAG_MINIMIZED))
			{
				lastSize.x(getBox().getBaseWidth());
				return;
			}
			lastSize.set(getBox().getBaseWidth(), getBox().getBaseHeight());
		}
	}
	
	@Override
	protected boolean updateSelf(int mouseX, int mouseY, float particalTicks)
	{
		if(animation != null)
		{
			animation.update(particalTicks);
			if(animation.isDone())
			{
				if(animation.get() < 0.1F)
				{
					updateMinizedState(true);
				}
				Vec2f last = InternalThreadPools.VEC2F.get().set(lastSize);
				bounds(last.x(), isFlagSet(FLAG_MINIMIZED) ? getMinimizedY() : last.y());
				lastSize.set(last);
				InternalThreadPools.VEC2F.accept(last.negate());
				animation = null;
			}
			notifyListeners(LISTENER_ON_CHANGE);
		}
		else if(isFlagSet(FLAG_RESIZEABLE) && !isOverChild(mouseX, mouseY) && !getGui().hasComponentInTheWay(getTopComponent(), mouseX, mouseY))
		{
			FacingList list = getBox().isColiding(mouseX, mouseY) ? getBox().getColidingBorder(mouseX, mouseY, 2F) : null;
			if(list != null)
			{
				if(isFlagNotSet(FLAG_RESIZEABLE_HORIZONTAL)) list = list.remove(FacingList.HORIZONTAL);
				if(isFlagNotSet(FLAG_RESIZEABLE_VERTICAL)) list = list.remove(FacingList.VERTICAL);
				bindCursor(list.containsAny(FacingList.VERTICAL) && isFlagNotSet(FLAG_MINIMIZED) ? Cursor.CURSOR_VRESIZE : (list.containsAny(FacingList.HORIZONTAL) ? Cursor.CURSOR_HRESIZE : null));
			}
		}
		if(isPopup() && !hasChildPopups() && !getGui().isComponentInFront(this))
		{
			requestFocus();
		}
		return true;
	}
	
	@Override
	protected void preRender()
	{
		if(animation != null)
		{
			float scale = getBox().getScale();
			enableScissors(getBox().getMinX(), getBox().getMinY(), lastSize.x() * scale, animation.get(getMinimizedY(), lastSize.y()) * scale);
		}
	}
	
	@Override
	protected boolean renderSelf(int mouseX, int mouseY, float particalTicks)
	{
		getRenderer().drawQuad(getBox(), color);
		return true;
	}
	
	@Override
	protected void postRender()
	{
		if(animation != null)
		{
			disableScissors();
		}
	}
	
	@Override
	public void accept(GuiComponent value, int index)
	{
		switch(index)
		{
			case 0: 
				getGui().removeComponent(this);
				break;
			case 1:
				if(animation != null || (flags & WINDOW_FLAG_MINIMIZEABLE) == 0)
				{
					break;
				}
				animation = (isMinimized() ? new LiniarValue(1F, 0F, 1F) :  new LiniarValue(1F, 1F, 0F)).setSmooth();
				if(isMinimized())
				{
					bounds(lastSize.x(), lastSize.y());
				}
				updateMinizedState(false);
				break;
			case 2: 
				value.setVisible(!isMinimized());
				break;
		}
	}
	
	@Override
	public boolean onClick(int button, int mouseX, int mouseY)
	{
		if(getGui().hasComponentInTheWay(getTopComponent(), mouseX, mouseY)) 
		{
			return false;
		}
		facing = getBox().getColidingBorder(mouseX, mouseY, 2F);
		lastClick.set(mouseX, mouseY);
		if(facing != null)
		{
			GameLog.info("Testing: "+facing.getCode());
			if(isFlagNotSet(FLAG_RESIZEABLE_HORIZONTAL)) facing = facing.remove(FacingList.HORIZONTAL);
			if(isFlagNotSet(FLAG_RESIZEABLE_VERTICAL)) facing = facing.remove(FacingList.VERTICAL);
			if(isOverChild(mouseX, mouseY) && facing.isEmpty())
			{
				facing = null;
				return false;
			}
			return true;
		}
		return false;
	}

	@Override
	public boolean onDrag(int mouseX, int mouseY)
	{
		if(facing != null)
		{
			if(facing.isEmpty() && isFlagSet(FLAG_MOVEABLE))
			{
				move(mouseX - lastClick.x(), mouseY - lastClick.y());
			}
			else if(!facing.isEmpty() && isFlagSet(FLAG_RESIZEABLE))
			{
				float scale = getBox().getScale();
				float xChange = (mouseX - lastClick.x()) * (facing.containsAny(FacingList.HORIZONTAL) ? 1F : 0F);
				float yChange = (mouseY - lastClick.y()) * (facing.containsAny(FacingList.VERTICAL) ? 1F : 0F);
				if(isFlagSet(FLAG_RESIZE_INVERT))
				{
					xChange *= -1F;
					yChange *= -1F;
				}
				setMassChanging();
				if(facing.contains(Facing.NORTH) && isFlagNotSet(FLAG_MINIMIZED))
				{
					resize(0F, -(yChange / scale));
					move(0F, yChange);
				}
				else if(facing.contains(Facing.SOUTH) && isFlagNotSet(FLAG_MINIMIZED))
				{
					resize(0F, yChange / scale);
				}
				if(facing.contains(Facing.WEST))
				{
					resize(-(xChange / scale) - 1F, 0F);
					move(xChange, 0F);
				}
				else if(facing.contains(Facing.EAST))
				{
					resize(xChange / scale, 0F);
				}
				ensureMinimumBounds();
				finishMassChanging();
				if(xChange > 0F || yChange > 0F)
				{
					onChanged(true);
				}
			}
			lastClick.set(mouseX, mouseY);
			return true;
		}
		return false;
	}

	@Override
	public void onRelease(int button, int mouseX, int mouseY)
	{
		facing = null;
	}

	@Override
	public boolean canMoveIntoForground()
	{
		return true;
	}

	@Override
	public void onFocusLost()
	{
		facing = null;
	}
	
	protected void ensureMinimumBounds()
	{
		IGuiBox box = getBox();
		Vec2f bounds = getMinimumBounds();
		if(box.getBaseWidth() < bounds.x())
		{
			box.setWidth(bounds.x());
			onChanged(true);
		}
		if(box.getBaseHeight() < bounds.y())
		{
			box.setHeight(bounds.y());
			onChanged(true);
		}
	}
}
