package speiger.src.coreengine.core.events.utility;

import java.util.function.Consumer;

import speiger.src.collections.objects.maps.impl.hash.Object2LongLinkedOpenHashMap;
import speiger.src.collections.objects.maps.interfaces.Object2LongMap;
import speiger.src.coreengine.core.events.api.Event;
import speiger.src.coreengine.core.events.api.ICancelableEvent;

public interface IEventDispatcher {
	public static final IEventDispatcher DIRECT = new Direct();
	
	void post(Consumer<Event>[] listeners, Event toPost, IEventExceptionHandler exceptions);
	
	
	public static class Direct implements IEventDispatcher {
		@Override
		public void post(Consumer<Event>[] listeners, Event toPost, IEventExceptionHandler exceptions) {
			int index = 0;
			try {
				if(toPost instanceof ICancelableEvent cancelable) {
					for(;index<listeners.length;index++) {
						listeners[index].accept(toPost);
						if(cancelable.isCanceled()) return;
					}
					return;
				}
				for(;index<listeners.length;index++) {
					listeners[index].accept(toPost);					
				}
			}
			catch(Throwable e) {
				exceptions.handleException(toPost, listeners, index, e);
			}
		}
	}
	
	public static class Profiled implements IEventDispatcher {
		Consumer<ProfileOutput> output;
		
		public Profiled(Consumer<ProfileOutput> output) {
			this.output = output;
		}

		@Override
		@SuppressWarnings("unchecked")
		public void post(Consumer<Event>[] listeners, Event toPost, IEventExceptionHandler exceptions) {
			long start = 0L;
			long[] results = new long[listeners.length];
			int index = 0;
			try {
				if(toPost instanceof ICancelableEvent cancelable) {
					for(;index<listeners.length;index++) {
						long time = handle(listeners[index], toPost);
						start += time;
						results[index] = time;
						if(cancelable.isCanceled()) return;
					}
					return;
				}
				for(;index<listeners.length;index++) {
					long time = handle(listeners[index], toPost);
					start += time;
					results[index] = time;				
				}
			}
			catch(Throwable e) {
				exceptions.handleException(toPost, listeners, index, e);
			}
			finally {
				output.accept(new ProfileOutput(start, (Class<Event>)toPost.getClass(), new Object2LongLinkedOpenHashMap<>(listeners, results)));
			}
		}
		
		private long handle(Consumer<Event> listener, Event event) {
			long time = System.nanoTime();
			listener.accept(event);
			return System.nanoTime() - time;
		}
		
	}
	
	public record ProfileOutput(long total, Class<Event> clz, Object2LongMap<Consumer<Event>> perEvent) {}
}
