package speiger.src.coreengine.platform.input.device;

import java.util.Deque;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;

import speiger.src.collections.longs.collections.LongIterable;
import speiger.src.collections.longs.maps.impl.concurrent.Long2ObjectConcurrentOpenHashMap;
import speiger.src.collections.longs.maps.interfaces.Long2ObjectMap;
import speiger.src.coreengine.core.events.api.Event;
import speiger.src.coreengine.core.events.api.ICancelableEvent;
import speiger.src.coreengine.core.events.bus.EventBus;
import speiger.src.coreengine.platform.input.window.IWindowListener.Reason;
import speiger.src.coreengine.platform.input.window.Window;

public abstract class AbstractDevice<T, E> implements InputDevice {
	protected Long2ObjectMap<Deque<E>> queues = new Long2ObjectConcurrentOpenHashMap<>();
	protected Long2ObjectMap<T> windowData = new Long2ObjectConcurrentOpenHashMap<>();
	protected EventBus bus;
	
	public void init(EventBus bus) {
		this.bus = bus;
	}
	
	protected LongIterable knownWindows() {
		return queues.keySet();
	}
	
	public void register(Window window) {
		registerCallbacks(window);
		long id = window.id();
		queues.putIfAbsent(id, new ConcurrentLinkedDeque<>());
		T data = createData(id);
		if(data == null) return;
		windowData.put(id, data);
	}
	
	protected void registerCallbacks(Window window) {
		window.addListener(this::onClose);
	}
	
	private void onClose(Window window, Reason reason) {
		if(reason == Reason.CLOSING) {
			queues.remove(window.id());
			windowData.remove(window.id());
		}
	}
	
	protected abstract T createData(long windowId);
	protected abstract void process(E task);
	
	protected boolean pushEvent(Event event) {
		if(bus == null) return true;
		bus.post(event);
		return event instanceof ICancelableEvent cancel && cancel.isCanceled();
	}
	
	protected void push(long windowId, E task) {
		Objects.requireNonNull(task);
		Deque<E> queue = queues.get(windowId);
		if(queue == null) return;
		queue.add(task);
	}
	
	@Override
	public void processInput(long windowId) {
		Deque<E> queue = queues.get(windowId);
		if(queue == null) return;
		while(!queue.isEmpty()) {
			process(queue.poll());
		}
	}
	
	public T get(long windowId) {
		return windowData.get(windowId);
	}
}
