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

package com.hypixel.hytale.event;

import java.util.Arrays;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
import com.hypixel.fastutil.shorts.Short2ObjectConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import com.hypixel.hytale.metrics.metric.Metric;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
import com.hypixel.hytale.logger.HytaleLogger;
import javax.annotation.Nonnull;

public abstract class EventBusRegistry<KeyType, EventType extends IBaseEvent<KeyType>, ConsumerMapType extends EventConsumerMap<EventType, ?, ?>>
{
    @Nonnull
    protected static final Object NULL;
    @Nonnull
    protected final HytaleLogger logger;
    @Nonnull
    protected final Class<EventType> eventClass;
    @Nonnull
    protected final Map<KeyType, ConsumerMapType> map;
    @Nonnull
    protected final ConsumerMapType global;
    @Nonnull
    protected final ConsumerMapType unhandled;
    protected boolean timeEvents;
    protected boolean shutdown;
    
    public EventBusRegistry(@Nonnull final HytaleLogger logger, @Nonnull final Class<EventType> eventClass, @Nonnull final ConsumerMapType global, @Nonnull final ConsumerMapType unhandled) {
        this.map = new ConcurrentHashMap<KeyType, ConsumerMapType>();
        this.logger = logger;
        this.eventClass = eventClass;
        this.global = global;
        this.unhandled = unhandled;
    }
    
    @Nonnull
    public Class<EventType> getEventClass() {
        return this.eventClass;
    }
    
    public boolean isTimeEvents() {
        return this.timeEvents;
    }
    
    public void setTimeEvents(final boolean timeEvents) {
        this.timeEvents = timeEvents;
    }
    
    public void shutdown() {
        this.shutdown = true;
        this.map.clear();
    }
    
    public boolean isAlive() {
        return !this.shutdown;
    }
    
    public abstract EventRegistration<KeyType, EventType> register(final short p0, @Nullable final KeyType p1, @Nonnull final Consumer<EventType> p2);
    
    public abstract EventRegistration<KeyType, EventType> registerGlobal(final short p0, @Nonnull final Consumer<EventType> p1);
    
    public abstract EventRegistration<KeyType, EventType> registerUnhandled(final short p0, @Nonnull final Consumer<EventType> p1);
    
    public abstract IEventDispatcher<EventType, ?> dispatchFor(final KeyType p0);
    
    static {
        NULL = new Object();
    }
    
    public abstract static class EventConsumer
    {
        @Nonnull
        protected static final AtomicInteger consumerIndex;
        protected final int index;
        protected final short priority;
        @Nonnull
        protected final String consumerString;
        @Nonnull
        protected final Metric timer;
        
        public EventConsumer(final short priority, @Nonnull final String consumerString) {
            this.timer = new Metric();
            this.priority = priority;
            this.consumerString = consumerString;
            this.index = EventConsumer.consumerIndex.getAndIncrement();
        }
        
        public int getIndex() {
            return this.index;
        }
        
        public short getPriority() {
            return this.priority;
        }
        
        @Nonnull
        public String getConsumerString() {
            return this.consumerString;
        }
        
        @Nonnull
        public Metric getTimer() {
            return this.timer;
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "EventConsumer{index=" + this.index + ", priority=" + this.priority + ", consumerString='" + this.consumerString + "', timer=" + String.valueOf(this.timer);
        }
        
        static {
            consumerIndex = new AtomicInteger();
        }
    }
    
    public abstract static class EventConsumerMap<EventType extends IBaseEvent, ConsumerType extends EventConsumer, ReturnType> implements IEventDispatcher<EventType, ReturnType>
    {
        private static final short[] EMPTY_SHORT_ARRAY;
        private final AtomicReference<short[]> prioritiesRef;
        @Nonnull
        private final Short2ObjectConcurrentHashMap<List<ConsumerType>> map;
        
        public EventConsumerMap() {
            this.prioritiesRef = new AtomicReference<short[]>(EventConsumerMap.EMPTY_SHORT_ARRAY);
            this.map = new Short2ObjectConcurrentHashMap<List<ConsumerType>>(true, (short)(-32768));
        }
        
        public boolean isEmpty() {
            return this.map.isEmpty();
        }
        
        public void add(@Nonnull final ConsumerType eventConsumer) {
            final short priority = eventConsumer.getPriority();
            final boolean[] wasPriorityAdded = { false };
            this.map.computeIfAbsent(priority, s -> {
                wasPriorityAdded[0] = true;
                return new CopyOnWriteArrayList();
            }).add(eventConsumer);
            if (wasPriorityAdded[0]) {
                this.addPriority(priority);
            }
        }
        
        public boolean remove(@Nonnull final ConsumerType consumer) {
            final short priority = consumer.getPriority();
            final boolean[] wasRemoved = { false, false };
            this.map.computeIfPresent(priority, (key, obj) -> {
                wasRemoved[0] = obj.remove(consumer);
                if (!obj.isEmpty()) {
                    return obj;
                }
                else {
                    wasRemoved[1] = true;
                    return null;
                }
            });
            if (wasRemoved[1]) {
                this.removePriority(priority);
            }
            return wasRemoved[0];
        }
        
        public short[] getPriorities() {
            return this.prioritiesRef.get();
        }
        
        @Nullable
        public List<ConsumerType> get(final short priority) {
            return this.map.get(priority);
        }
        
        private void addPriority(final short priority) {
            while (this.map.containsKey(priority)) {
                final short[] currentPriorities = this.prioritiesRef.get();
                final int index = Arrays.binarySearch(currentPriorities, priority);
                if (index >= 0) {
                    return;
                }
                final int insertionPoint = -(index + 1);
                final int newLength = currentPriorities.length + 1;
                final short[] newPriorities = new short[newLength];
                System.arraycopy(currentPriorities, 0, newPriorities, 0, insertionPoint);
                newPriorities[insertionPoint] = priority;
                System.arraycopy(currentPriorities, insertionPoint, newPriorities, insertionPoint + 1, currentPriorities.length - insertionPoint);
                if (this.prioritiesRef.compareAndSet(currentPriorities, newPriorities)) {
                    return;
                }
            }
        }
        
        private void removePriority(final short priority) {
            while (!this.map.containsKey(priority)) {
                final short[] currentPriorities = this.prioritiesRef.get();
                final int index = Arrays.binarySearch(currentPriorities, priority);
                if (index < 0) {
                    return;
                }
                final int newLength = currentPriorities.length - 1;
                final short[] newPriorities = new short[newLength];
                System.arraycopy(currentPriorities, 0, newPriorities, 0, index);
                System.arraycopy(currentPriorities, index + 1, newPriorities, index, newLength - index);
                if (this.prioritiesRef.compareAndSet(currentPriorities, newPriorities)) {
                    return;
                }
            }
        }
        
        static {
            EMPTY_SHORT_ARRAY = new short[0];
        }
    }
}
