// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.event;

import java.util.concurrent.CompletionStage;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import java.util.function.Consumer;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import javax.annotation.Nullable;
import java.util.function.Function;
import com.hypixel.hytale.logger.HytaleLogger;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nonnull;

public class AsyncEventBusRegistry<KeyType, EventType extends IAsyncEvent<KeyType>> extends EventBusRegistry<KeyType, EventType, AsyncEventConsumerMap<EventType>>
{
    @Nonnull
    public static final IEventDispatcher NO_OP;
    @Nonnull
    private final IEventDispatcher<EventType, CompletableFuture<EventType>> globalDispatcher;
    
    public AsyncEventBusRegistry(@Nonnull final HytaleLogger logger, @Nonnull final Class<EventType> eventClass) {
        super(logger, eventClass, new AsyncEventConsumerMap(null), new AsyncEventConsumerMap(null));
        this.globalDispatcher = (IEventDispatcher<EventType, CompletableFuture<EventType>>)(event -> {
            final CompletableFuture<EventType> before;
            final CompletableFuture<EventType> future = before = CompletableFuture.completedFuture(event);
            CompletableFuture<EventType> future2 = this.dispatchGlobal(future);
            if (before == future2) {
                future2 = this.dispatchUnhandled(future2);
            }
            return future2;
        });
        final AsyncEventConsumerMap asyncEventConsumerMap = (AsyncEventConsumerMap)this.global;
        ((AsyncEventConsumerMap)this.unhandled).registry = this;
        asyncEventConsumerMap.registry = this;
    }
    
    @Nonnull
    public EventRegistration<KeyType, EventType> registerAsync(final short priority, @Nonnull final KeyType key, @Nonnull final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> function) {
        return this.registerAsync0(priority, key, function, function.toString());
    }
    
    @Nonnull
    private EventRegistration<KeyType, EventType> registerAsync0(final short priority, @Nullable final KeyType key, @Nonnull final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> function, @Nonnull final String consumerString) {
        if (this.shutdown) {
            throw new IllegalArgumentException("EventRegistry is shutdown!");
        }
        final KeyType k = (KeyType)((key != null) ? key : AsyncEventBusRegistry.NULL);
        final AsyncEventConsumerMap<EventType> eventMap = (AsyncEventConsumerMap<EventType>)this.map.computeIfAbsent((KeyType)k, o -> new AsyncEventConsumerMap(this));
        final AsyncEventConsumer<EventType> eventConsumer = new AsyncEventConsumer<EventType>(priority, consumerString, function);
        eventMap.add((AsyncEventConsumer<EventType>)eventConsumer);
        return new EventRegistration<KeyType, EventType>((Class<EventType>)this.eventClass, this::isAlive, () -> this.unregister(key, eventConsumer));
    }
    
    private void unregister(@Nullable final KeyType key, @Nonnull final AsyncEventConsumer<EventType> consumer) {
        if (this.shutdown) {
            throw new IllegalArgumentException("EventRegistry is shutdown!");
        }
        final KeyType k = (KeyType)((key != null) ? key : AsyncEventBusRegistry.NULL);
        final AsyncEventConsumerMap<EventType> eventMap = (AsyncEventConsumerMap<EventType>)this.map.get(k);
        if (eventMap != null && !eventMap.remove((AsyncEventConsumer<EventType>)consumer)) {
            throw new IllegalArgumentException(String.valueOf(consumer));
        }
    }
    
    @Nonnull
    public EventRegistration<KeyType, EventType> registerAsyncGlobal(final short priority, @Nonnull final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> function) {
        return this.registerAsyncGlobal0(priority, function, function.toString());
    }
    
    @Nonnull
    private EventRegistration<KeyType, EventType> registerAsyncGlobal0(final short priority, @Nonnull final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> function, @Nonnull final String consumerString) {
        if (this.shutdown) {
            throw new IllegalArgumentException("EventRegistry is shutdown!");
        }
        final AsyncEventConsumer<EventType> eventConsumer = new AsyncEventConsumer<EventType>(priority, consumerString, function);
        ((EventConsumerMap<EventType, AsyncEventConsumer<EventType>, ReturnType>)this.global).add(eventConsumer);
        return new EventRegistration<KeyType, EventType>((Class<EventType>)this.eventClass, this::isAlive, () -> this.unregisterGlobal(eventConsumer));
    }
    
    private void unregisterGlobal(@Nonnull final AsyncEventConsumer<EventType> consumer) {
        if (this.shutdown) {
            throw new IllegalArgumentException("EventRegistry is shutdown!");
        }
        if (!((EventConsumerMap<EventType, AsyncEventConsumer<EventType>, ReturnType>)this.global).remove(consumer)) {
            throw new IllegalArgumentException(String.valueOf(consumer));
        }
    }
    
    @Nonnull
    public EventRegistration<KeyType, EventType> registerAsyncUnhandled(final short priority, @Nonnull final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> function) {
        return this.registerAsyncUnhandled0(priority, function, function.toString());
    }
    
    @Nonnull
    private EventRegistration<KeyType, EventType> registerAsyncUnhandled0(final short priority, @Nonnull final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> function, @Nonnull final String consumerString) {
        if (this.shutdown) {
            throw new IllegalArgumentException("EventRegistry is shutdown!");
        }
        final AsyncEventConsumer<EventType> eventConsumer = new AsyncEventConsumer<EventType>(priority, consumerString, function);
        ((EventConsumerMap<EventType, AsyncEventConsumer<EventType>, ReturnType>)this.unhandled).add(eventConsumer);
        return new EventRegistration<KeyType, EventType>((Class<EventType>)this.eventClass, this::isAlive, () -> this.unregisterUnhandled(eventConsumer));
    }
    
    private void unregisterUnhandled(@Nonnull final AsyncEventConsumer<EventType> consumer) {
        if (this.shutdown) {
            throw new IllegalArgumentException("EventRegistry is shutdown!");
        }
        if (!((EventConsumerMap<EventType, AsyncEventConsumer<EventType>, ReturnType>)this.unhandled).remove(consumer)) {
            throw new IllegalArgumentException(String.valueOf(consumer));
        }
    }
    
    private CompletableFuture<EventType> dispatchGlobal(@Nonnull final CompletableFuture<EventType> future) {
        return this.dispatchEventMap(future, (AsyncEventConsumerMap<EventType>)this.global, "Failed to dispatch event (global)");
    }
    
    private CompletableFuture<EventType> dispatchUnhandled(@Nonnull final CompletableFuture<EventType> future) {
        return this.dispatchEventMap(future, (AsyncEventConsumerMap<EventType>)this.unhandled, "Failed to dispatch event (unhandled)");
    }
    
    private CompletableFuture<EventType> dispatchEventMap(@Nonnull CompletableFuture<EventType> future, @Nonnull final AsyncEventConsumerMap<EventType> eventMap, @Nonnull final String s) {
        for (final short priority : eventMap.getPriorities()) {
            final List<AsyncEventConsumer<EventType>> consumers = (List<AsyncEventConsumer<EventType>>)eventMap.get(priority);
            if (consumers != null) {
                for (final AsyncEventConsumer<EventType> consumer : consumers) {
                    try {
                        final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> theConsumer = this.timeEvents ? consumer.getTimedFunction() : consumer.getFunction();
                        future = theConsumer.apply(future).whenComplete((event, throwable) -> {
                            if (event instanceof final IProcessedEvent processedEvent) {
                                processedEvent.processEvent(consumer.getConsumerString());
                            }
                            if (throwable != null) {
                                this.logger.at(Level.SEVERE).withCause(throwable).log("%s %s to %s", s, event, consumer);
                            }
                            return;
                        });
                    }
                    catch (final Throwable t) {
                        this.logger.at(Level.SEVERE).withCause(t).log("%s %s to %s", s, future, consumer);
                    }
                }
            }
        }
        return future;
    }
    
    @Nonnull
    @Override
    public EventRegistration<KeyType, EventType> register(final short priority, final KeyType key, @Nonnull final Consumer<EventType> consumer) {
        return this.registerAsync0(priority, key, f -> f.thenApply(e -> {
            consumer.accept(e);
            return e;
        }), consumer.toString());
    }
    
    @Nonnull
    @Override
    public EventRegistration<KeyType, EventType> registerGlobal(final short priority, @Nonnull final Consumer<EventType> consumer) {
        return this.registerAsyncGlobal0(priority, f -> f.thenApply(e -> {
            consumer.accept(e);
            return e;
        }), consumer.toString());
    }
    
    @Nonnull
    @Override
    public EventRegistration<KeyType, EventType> registerUnhandled(final short priority, @Nonnull final Consumer<EventType> consumer) {
        return this.registerAsyncUnhandled0(priority, f -> f.thenApply(e -> {
            consumer.accept(e);
            return e;
        }), consumer.toString());
    }
    
    @Nonnull
    @Override
    public IEventDispatcher<EventType, CompletableFuture<EventType>> dispatchFor(@Nullable final KeyType key) {
        if (this.shutdown) {
            throw new IllegalArgumentException("EventRegistry is shutdown!");
        }
        final KeyType k = (KeyType)((key != null) ? key : AsyncEventBusRegistry.NULL);
        final AsyncEventConsumerMap<EventType> eventMap = (AsyncEventConsumerMap<EventType>)this.map.get(k);
        if (eventMap != null && !eventMap.isEmpty()) {
            return (IEventDispatcher<EventType, CompletableFuture<EventType>>)eventMap;
        }
        if (this.global.isEmpty() && this.unhandled.isEmpty()) {
            return AsyncEventBusRegistry.NO_OP;
        }
        return this.globalDispatcher;
    }
    
    static {
        NO_OP = new IEventDispatcher<IAsyncEvent, CompletableFuture<IAsyncEvent>>() {
            @Override
            public boolean hasListener() {
                return false;
            }
            
            @Nonnull
            @Override
            public CompletableFuture<IAsyncEvent> dispatch(final IAsyncEvent event) {
                return (CompletableFuture<IAsyncEvent>)CompletableFuture.completedFuture(event);
            }
        };
    }
    
    protected static class AsyncEventConsumer<EventType extends IAsyncEvent> extends EventConsumer
    {
        @Nonnull
        private final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> function;
        @Nonnull
        private final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> timedFunction;
        
        public AsyncEventConsumer(final short priority, @Nonnull final String consumerString, @Nonnull final Function<CompletableFuture<EventType>, CompletableFuture<EventType>> function) {
            super(priority, consumerString);
            this.function = function;
            this.timedFunction = (Function<CompletableFuture<EventType>, CompletableFuture<EventType>>)(f -> {
                final long before = System.nanoTime();
                return function.apply(f).whenComplete((eventType, throwable) -> {
                    final long after = System.nanoTime();
                    this.timer.add(after - before);
                    if (throwable != null) {
                        throw SneakyThrow.sneakyThrow(throwable);
                    }
                });
            });
        }
        
        @Nonnull
        public Function<CompletableFuture<EventType>, CompletableFuture<EventType>> getFunction() {
            return this.function;
        }
        
        @Nonnull
        public Function<CompletableFuture<EventType>, CompletableFuture<EventType>> getTimedFunction() {
            return this.timedFunction;
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "AsyncEventConsumer{function=" + String.valueOf(this.function) + ", timedFunction=" + String.valueOf(this.timedFunction) + "} " + super.toString();
        }
    }
    
    protected static class AsyncEventConsumerMap<EventType extends IAsyncEvent> extends EventConsumerMap<EventType, AsyncEventConsumer<EventType>, CompletableFuture<EventType>>
    {
        protected AsyncEventBusRegistry registry;
        
        public AsyncEventConsumerMap(final AsyncEventBusRegistry registry) {
            this.registry = registry;
        }
        
        @Nonnull
        @Override
        public CompletableFuture<EventType> dispatch(final EventType event) {
            return CompletableFuture.completedFuture(event).thenComposeAsync((Function<? super EventType, ? extends CompletionStage<EventType>>)this::dispatch0);
        }
        
        private CompletableFuture<EventType> dispatch0(final EventType event) {
            final CompletableFuture<EventType> before;
            CompletableFuture<EventType> future = before = CompletableFuture.completedFuture(event);
            final CompletableFuture<EventType> beforeGlobal;
            future = (beforeGlobal = this.registry.dispatchEventMap(future, this, "Failed to dispatch event"));
            future = this.registry.dispatchGlobal(future);
            if (beforeGlobal == future && before == beforeGlobal) {
                future = this.registry.dispatchUnhandled(future);
            }
            return future;
        }
    }
}
