package speiger.src.coreengine.utils.eventbus;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.maps.impl.concurrent.Object2ObjectConcurrentOpenHashMap;
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectOpenHashMap;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.collections.objects.utils.ObjectLists;

public class EventBus
{
	private static final Lookup LOOKUP = MethodHandles.lookup();
	Map<Class<? extends Event>, Listeners> listeners = new Object2ObjectOpenHashMap<Class<? extends Event>, Listeners>().synchronize();
	Object2ObjectMap<Object, List<EventListener>> instances = new Object2ObjectConcurrentOpenHashMap<>();
	
	public <T extends Event> void register(Class<T> event, Consumer<T> listener) {
		register(event, EventPriority.MEDIUM, listener);
	}
	
	public <T extends Event> void register(Class<T> event, EventPriority priority, Consumer<T> listener) {
		if(instances.containsKey(listener)) return;
		getListeners(event).addListener(priority, cast(listener));
		instances.put(listener, ObjectLists.singleton(new EventListener(event, cast(listener))));
	}
	
	public void register(Object obj) {
		register(obj, false);
	}
	
	public void register(Object obj, boolean superClasses) {
		if(instances.containsKey(obj)) return;
		final List<EventListener> list = new ObjectArrayList<>();
		try {
			register(obj.getClass().getDeclaredAnnotation(SubscribeEvent.class), obj, list);
			find(obj.getClass(), superClasses, Class::getDeclaredMethods, t -> {
				try {
					t.setAccessible(true);
					SubscribeEvent data = t.getAnnotation(SubscribeEvent.class);
					if(data == null) return;
					Consumer<Event> listener = new MethodListener(LOOKUP.unreflect(t).bindTo(obj));
					Class<Event> clz = castClass(t.getParameterTypes()[0]); 
					getListeners(clz).addListener(data.priority(), listener);
					list.add(new EventListener(clz, listener));
				}
				catch(Exception e) { e.printStackTrace(); }
			});
			find(obj.getClass(), superClasses, Class::getDeclaredFields, t -> {
				try {
					t.setAccessible(true);
					register(t.getAnnotation(SubscribeEvent.class), t.get(obj), list);						
				}
				catch(Exception e) { e.printStackTrace(); }
			});
		}
		catch(Exception e) { e.printStackTrace(); }
		if(list.isEmpty()) return;
		instances.put(obj, list);
	}
	
	private void register(SubscribeEvent data, Object obj, List<EventListener> listeners) {
		if(data == null || !(obj instanceof Consumer)) return;
		getListeners(data.value()).addListener(data.priority(), cast(obj));
		listeners.add(new EventListener(data.value(), cast(obj)));
	}
	
	private <T> void find(Class<?> clz, boolean superClasses, Function<Class<?>, T[]> mapper, Consumer<T> result) {
		do {
			for(T value : mapper.apply(clz)) result.accept(value);
			clz = clz.getSuperclass();
		}
		while(clz != Object.class && superClasses);
	}
		
	public void unregister(Object obj) {
		for(EventListener entry : instances.remOrDefault(obj, ObjectLists.empty())) {
			getListeners(entry.event()).removeListeners(entry.listener());
		}
	}
	
	public void post(Event event) {
		Consumer<Event>[] listeners = getListeners(event.getClass()).getListeners();
		if(listeners.length <= 0) return;
		int index = 0;
		try {
			for(;index<listeners.length && !event.isCanceled();index++) {
				listeners[index].accept(event);
			}
		}
		catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	protected Listeners getListeners(Class<? extends Event> event) {
		Listeners result = listeners.get(event);
		if(result == null) {
			result = event == Event.class ? new Listeners() : new Listeners(getListeners(castClass(event.getSuperclass())));
			listeners.put(event, result);
		}
		return result;
	}
	
	@SuppressWarnings("unchecked")
	private <T> Consumer<T> cast(Object obj) {
		return (Consumer<T>) obj;
	}
	
	@SuppressWarnings("unchecked")
	private <T> Class<T> castClass(Class<?> clz) {
		return (Class<T>) clz;
	}
	
	public static record EventListener(Class<? extends Event> event, Consumer<Event> listener) {}
	public static record MethodListener(MethodHandle handle) implements Consumer<Event> {
		@Override
		public void accept(Event t) {
			try { handle.invoke(t); }
			catch(Throwable e) { e.printStackTrace(); }
		}
	}
}
