package speiger.src.coreengine.rendering.input;

import org.lwjgl.glfw.GLFW;

import speiger.src.collections.ints.sets.IntLinkedOpenHashSet;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.coreengine.math.vector.ints.Vec2i;
import speiger.src.coreengine.rendering.input.bindings.InputBinding;
import speiger.src.coreengine.rendering.input.bindings.utils.BindingType;
import speiger.src.coreengine.rendering.input.camera.Camera;
import speiger.src.coreengine.rendering.input.events.MouseEvent;
import speiger.src.coreengine.rendering.input.events.MouseEvent.MouseClickEvent;
import speiger.src.coreengine.rendering.input.events.MouseEvent.MouseMoveEvent;
import speiger.src.coreengine.rendering.input.events.MouseEvent.MouseScrollEvent;
import speiger.src.coreengine.rendering.input.window.Window;
import speiger.src.coreengine.utils.eventbus.EventBus;

public class Mouse
{
	public static final Mouse INSTANCE = new Mouse();
	
	MouseRay ray;
	EventBus bus;
	IntSet pressedButton = new IntLinkedOpenHashSet();
	Vec2i position = Vec2i.newMutable();
	Vec2i movement = Vec2i.newMutable();
	Vec2i scroll = Vec2i.newMutable();
	boolean active = true;
	
	public void init(EventBus bus, Window window, Camera cam)
	{
		this.bus = bus;
		ray = new MouseRay(cam, window);
		window.addCallback(T -> GLFW.glfwSetCursorPosCallback(T, Mouse.this::onDrag));
		window.addCallback(T -> GLFW.glfwSetMouseButtonCallback(T, Mouse.this::onClick));
		window.addCallback(T -> GLFW.glfwSetScrollCallback(T, Mouse.this::onScroll));
		window.addCallback(T -> GLFW.glfwSetCursorEnterCallback(T, (K, V) -> active = V));		
	}
	
	protected void onDrag(long window, double xpos, double ypos)
	{
		int xChange = (int)xpos - position.getX();
		int yChange = (int)ypos - position.getY();
		position.set((int)xpos, (int)ypos);
		pushEvent(new MouseMoveEvent(position.getX(), position.getY(), xChange, yChange));
	}
	
	protected void onScroll(long window, double xScroll, double yScroll)
	{
		pushEvent(new MouseScrollEvent(position.getX(), position.getY(), (int)xScroll, (int)yScroll));
	}
	
	protected void onClick(long window, int button, int action, int mods)
	{
		if(action == 1) pressedButton.add(button);
		else pressedButton.remove(button);
		pushEvent(new MouseClickEvent(position.getX(), position.getY(), button, action == 1));
	}
	
	public static void update()
	{
		INSTANCE.scroll.negate();
		INSTANCE.movement.negate();
		INSTANCE.ray.update();
	}
	
	public static boolean isButtonPressed(int button)
	{
		return INSTANCE.pressedButton.contains(button);
	}
	
	public static int getX()
	{
		return INSTANCE.position.getX();
	}
	
	public static int getY()
	{
		return INSTANCE.position.getY();
	}
	
	public static Vec2i getMovement()
	{
		return INSTANCE.movement;
	}
	
	public static boolean isActive()
	{
		return INSTANCE.active;
	}
	
	public static MouseRay getRayCast()
	{
		return INSTANCE.ray;
	}
	
	public Vec2i getScroll()
	{
		return scroll;
	}
	
	public void pushEvent(MouseEvent event)
	{
		if(active)
		{
			bus.post(event);
			if(!event.isCanceled() || event.isForced())
			{
				if(event instanceof MouseMoveEvent)
				{
					MouseMoveEvent move = (MouseMoveEvent)event;
					movement.add(move.xMove, move.yMove);
				}
				else if(event instanceof MouseClickEvent)
				{
					MouseClickEvent click = (MouseClickEvent)event;
					InputBinding.BINDINGS.updatePressing(BindingType.MOUSE, click.button, click.press);
				}
				else if(event instanceof MouseScrollEvent)
				{
					MouseScrollEvent scroll = (MouseScrollEvent)event;
					this.scroll.add(scroll.scrollX, scroll.scrollY);
				}
			}
		}
	}
}
