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

package io.netty.channel.kqueue;

import io.netty.channel.IoEvent;
import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.channel.IoOps;
import io.netty.util.internal.StringUtil;
import io.netty.channel.IoRegistration;
import io.netty.channel.IoHandle;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Collections;
import io.netty.channel.Channel;
import java.util.List;
import java.io.IOException;
import io.netty.channel.IoHandlerContext;
import io.netty.util.collection.LongObjectHashMap;
import java.util.ArrayDeque;
import io.netty.util.internal.ObjectUtil;
import io.netty.channel.SelectStrategyFactory;
import io.netty.channel.DefaultSelectStrategyFactory;
import io.netty.channel.IoHandlerFactory;
import io.netty.util.collection.LongObjectMap;
import java.util.Queue;
import io.netty.util.concurrent.ThreadAwareExecutor;
import io.netty.util.IntSupplier;
import io.netty.channel.SelectStrategy;
import io.netty.channel.unix.FileDescriptor;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.channel.IoHandler;

public final class KQueueIoHandler implements IoHandler
{
    private static final InternalLogger logger;
    private static final AtomicIntegerFieldUpdater<KQueueIoHandler> WAKEN_UP_UPDATER;
    private static final int KQUEUE_WAKE_UP_IDENT = 0;
    private static final int KQUEUE_MAX_TIMEOUT_SECONDS = 86399;
    private final boolean allowGrowing;
    private final FileDescriptor kqueueFd;
    private final KQueueEventArray changeList;
    private final KQueueEventArray eventList;
    private final SelectStrategy selectStrategy;
    private final NativeArrays nativeArrays;
    private final IntSupplier selectNowSupplier;
    private final ThreadAwareExecutor executor;
    private final Queue<DefaultKqueueIoRegistration> cancelledRegistrations;
    private final LongObjectMap<DefaultKqueueIoRegistration> registrations;
    private int numChannels;
    private long nextId;
    private volatile int wakenUp;
    
    private long generateNextId() {
        boolean reset = false;
        while (true) {
            if (this.nextId == Long.MAX_VALUE) {
                if (reset) {
                    throw new IllegalStateException("All possible ids in use");
                }
                reset = true;
            }
            ++this.nextId;
            if (this.nextId == 0L) {
                continue;
            }
            if (!this.registrations.containsKey(this.nextId)) {
                return this.nextId;
            }
        }
    }
    
    public static IoHandlerFactory newFactory() {
        return newFactory(0, DefaultSelectStrategyFactory.INSTANCE);
    }
    
    public static IoHandlerFactory newFactory(final int maxEvents, final SelectStrategyFactory selectStrategyFactory) {
        KQueue.ensureAvailability();
        ObjectUtil.checkPositiveOrZero(maxEvents, "maxEvents");
        ObjectUtil.checkNotNull(selectStrategyFactory, "selectStrategyFactory");
        return new IoHandlerFactory() {
            @Override
            public IoHandler newHandler(final ThreadAwareExecutor executor) {
                return new KQueueIoHandler(executor, maxEvents, selectStrategyFactory.newSelectStrategy(), null);
            }
            
            @Override
            public boolean isChangingThreadSupported() {
                return true;
            }
        };
    }
    
    private KQueueIoHandler(final ThreadAwareExecutor executor, int maxEvents, final SelectStrategy strategy) {
        KQueue.ensureAvailability();
        this.selectNowSupplier = new IntSupplier() {
            @Override
            public int get() throws Exception {
                return KQueueIoHandler.this.kqueueWaitNow();
            }
        };
        this.cancelledRegistrations = new ArrayDeque<DefaultKqueueIoRegistration>();
        this.registrations = new LongObjectHashMap<DefaultKqueueIoRegistration>(4096);
        this.executor = ObjectUtil.checkNotNull(executor, "executor");
        this.selectStrategy = ObjectUtil.checkNotNull(strategy, "strategy");
        this.kqueueFd = Native.newKQueue();
        if (maxEvents == 0) {
            this.allowGrowing = true;
            maxEvents = 4096;
        }
        else {
            this.allowGrowing = false;
        }
        this.changeList = new KQueueEventArray(maxEvents);
        this.eventList = new KQueueEventArray(maxEvents);
        this.nativeArrays = new NativeArrays();
        final int result = Native.keventAddUserEvent(this.kqueueFd.intValue(), 0);
        if (result < 0) {
            this.destroy();
            throw new IllegalStateException("kevent failed to add user event with errno: " + -result);
        }
    }
    
    @Override
    public void wakeup() {
        if (!this.executor.isExecutorThread(Thread.currentThread()) && KQueueIoHandler.WAKEN_UP_UPDATER.compareAndSet(this, 0, 1)) {
            this.wakeup0();
        }
    }
    
    private void wakeup0() {
        Native.keventTriggerUserEvent(this.kqueueFd.intValue(), 0);
    }
    
    private int kqueueWait(final IoHandlerContext context, final boolean oldWakeup) throws IOException {
        if (oldWakeup && !context.canBlock()) {
            return this.kqueueWaitNow();
        }
        final long totalDelay = context.delayNanos(System.nanoTime());
        final int delaySeconds = (int)Math.min(totalDelay / 1000000000L, 86399L);
        final int delayNanos = (int)(totalDelay % 1000000000L);
        return this.kqueueWait(delaySeconds, delayNanos);
    }
    
    private int kqueueWaitNow() throws IOException {
        return this.kqueueWait(0, 0);
    }
    
    private int kqueueWait(final int timeoutSec, final int timeoutNs) throws IOException {
        final int numEvents = Native.keventWait(this.kqueueFd.intValue(), this.changeList, this.eventList, timeoutSec, timeoutNs);
        this.changeList.clear();
        return numEvents;
    }
    
    private void processReady(final int ready) {
        for (int i = 0; i < ready; ++i) {
            final short filter = this.eventList.filter(i);
            final short flags = this.eventList.flags(i);
            final int ident = this.eventList.ident(i);
            if (filter == Native.EVFILT_USER || (flags & Native.EV_ERROR) != 0x0) {
                assert filter == Native.EVFILT_USER && ident == 0;
            }
            else {
                final long id = this.eventList.udata(i);
                final DefaultKqueueIoRegistration registration = this.registrations.get(id);
                if (registration == null) {
                    KQueueIoHandler.logger.warn("events[{}]=[{}, {}, {}] had no registration!", i, ident, id, filter);
                }
                else {
                    registration.handle(ident, filter, flags, this.eventList.fflags(i), this.eventList.data(i), id);
                }
            }
        }
    }
    
    @Override
    public int run(final IoHandlerContext context) {
        int handled = 0;
        try {
            int strategy = this.selectStrategy.calculateStrategy(this.selectNowSupplier, !context.canBlock());
            switch (strategy) {
                case -2: {
                    if (context.shouldReportActiveIoTime()) {
                        context.reportActiveIoTime(0L);
                    }
                    return 0;
                }
                case -3:
                case -1: {
                    strategy = this.kqueueWait(context, KQueueIoHandler.WAKEN_UP_UPDATER.getAndSet(this, 0) == 1);
                    if (this.wakenUp == 1) {
                        this.wakeup0();
                        break;
                    }
                    break;
                }
            }
            if (strategy > 0) {
                handled = strategy;
                if (context.shouldReportActiveIoTime()) {
                    final long activeIoStartTimeNanos = System.nanoTime();
                    this.processReady(strategy);
                    final long activeIoEndTimeNanos = System.nanoTime();
                    context.reportActiveIoTime(activeIoEndTimeNanos - activeIoStartTimeNanos);
                }
                else {
                    this.processReady(strategy);
                }
            }
            else if (context.shouldReportActiveIoTime()) {
                context.reportActiveIoTime(0L);
            }
            if (this.allowGrowing && strategy == this.eventList.capacity()) {
                this.eventList.realloc(false);
            }
        }
        catch (final Error e) {
            throw e;
        }
        catch (final Throwable t) {
            handleLoopException(t);
        }
        finally {
            this.processCancelledRegistrations();
        }
        return handled;
    }
    
    private void processCancelledRegistrations() {
        while (true) {
            final DefaultKqueueIoRegistration cancelledRegistration = this.cancelledRegistrations.poll();
            if (cancelledRegistration == null) {
                return;
            }
            final DefaultKqueueIoRegistration removed = this.registrations.remove(cancelledRegistration.id);
            assert removed == cancelledRegistration;
            if (removed.isHandleForChannel()) {
                --this.numChannels;
            }
            removed.handle.unregistered();
        }
    }
    
    int numRegisteredChannels() {
        return this.numChannels;
    }
    
    List<Channel> registeredChannelsList() {
        final LongObjectMap<DefaultKqueueIoRegistration> ch = this.registrations;
        if (ch.isEmpty()) {
            return Collections.emptyList();
        }
        final List<Channel> channels = new ArrayList<Channel>(ch.size());
        for (final DefaultKqueueIoRegistration registration : ch.values()) {
            if (registration.handle instanceof AbstractKQueueChannel.AbstractKQueueUnsafe) {
                channels.add(((AbstractKQueueChannel.AbstractKQueueUnsafe)registration.handle).channel());
            }
        }
        return Collections.unmodifiableList((List<? extends Channel>)channels);
    }
    
    private static void handleLoopException(final Throwable t) {
        KQueueIoHandler.logger.warn("Unexpected exception in the selector loop.", t);
        try {
            Thread.sleep(1000L);
        }
        catch (final InterruptedException ex) {}
    }
    
    @Override
    public void prepareToDestroy() {
        try {
            this.kqueueWaitNow();
        }
        catch (final IOException ex) {}
        final DefaultKqueueIoRegistration[] array;
        final DefaultKqueueIoRegistration[] copy = array = this.registrations.values().toArray(new DefaultKqueueIoRegistration[0]);
        for (final DefaultKqueueIoRegistration reg : array) {
            reg.close();
        }
        this.processCancelledRegistrations();
    }
    
    @Override
    public void destroy() {
        try {
            this.kqueueFd.close();
        }
        catch (final IOException e) {
            KQueueIoHandler.logger.warn("Failed to close the kqueue fd.", e);
        }
        finally {
            this.nativeArrays.free();
            this.changeList.free();
            this.eventList.free();
        }
    }
    
    @Override
    public IoRegistration register(final IoHandle handle) {
        final KQueueIoHandle kqueueHandle = cast(handle);
        if (kqueueHandle.ident() == 0) {
            throw new IllegalArgumentException("ident 0 is reserved for internal usage");
        }
        final DefaultKqueueIoRegistration registration = new DefaultKqueueIoRegistration(this.executor, kqueueHandle);
        final DefaultKqueueIoRegistration old = this.registrations.put(registration.id, registration);
        if (old != null) {
            this.registrations.put(old.id, old);
            throw new IllegalStateException();
        }
        if (registration.isHandleForChannel()) {
            ++this.numChannels;
        }
        handle.registered();
        return registration;
    }
    
    private static KQueueIoHandle cast(final IoHandle handle) {
        if (handle instanceof KQueueIoHandle) {
            return (KQueueIoHandle)handle;
        }
        throw new IllegalArgumentException("IoHandle of type " + StringUtil.simpleClassName(handle) + " not supported");
    }
    
    private static KQueueIoOps cast(final IoOps ops) {
        if (ops instanceof KQueueIoOps) {
            return (KQueueIoOps)ops;
        }
        throw new IllegalArgumentException("IoOps of type " + StringUtil.simpleClassName(ops) + " not supported");
    }
    
    @Override
    public boolean isCompatible(final Class<? extends IoHandle> handleType) {
        return KQueueIoHandle.class.isAssignableFrom(handleType);
    }
    
    static {
        logger = InternalLoggerFactory.getInstance(KQueueIoHandler.class);
        WAKEN_UP_UPDATER = AtomicIntegerFieldUpdater.newUpdater(KQueueIoHandler.class, "wakenUp");
    }
    
    private final class DefaultKqueueIoRegistration implements IoRegistration
    {
        private boolean cancellationPending;
        private final AtomicBoolean canceled;
        private final KQueueIoEvent event;
        final KQueueIoHandle handle;
        final long id;
        private final ThreadAwareExecutor executor;
        
        DefaultKqueueIoRegistration(final ThreadAwareExecutor executor, final KQueueIoHandle handle) {
            this.canceled = new AtomicBoolean();
            this.event = new KQueueIoEvent();
            this.executor = executor;
            this.handle = handle;
            this.id = KQueueIoHandler.this.generateNextId();
        }
        
        boolean isHandleForChannel() {
            return this.handle instanceof AbstractKQueueChannel.AbstractKQueueUnsafe;
        }
        
        @Override
        public <T> T attachment() {
            return (T)KQueueIoHandler.this.nativeArrays;
        }
        
        @Override
        public long submit(final IoOps ops) {
            final KQueueIoOps kQueueIoOps = cast(ops);
            if (!this.isValid()) {
                return -1L;
            }
            final short filter = kQueueIoOps.filter();
            final short flags = kQueueIoOps.flags();
            final int fflags = kQueueIoOps.fflags();
            if (this.executor.isExecutorThread(Thread.currentThread())) {
                this.evSet(filter, flags, fflags);
            }
            else {
                this.executor.execute(() -> this.evSet(filter, flags, fflags));
            }
            return 0L;
        }
        
        void handle(final int ident, final short filter, final short flags, final int fflags, final long data, final long udata) {
            if (this.cancellationPending) {
                return;
            }
            this.event.update(ident, filter, flags, fflags, data, udata);
            this.handle.handle(this, this.event);
        }
        
        private void evSet(final short filter, final short flags, final int fflags) {
            if (this.cancellationPending) {
                return;
            }
            KQueueIoHandler.this.changeList.evSet(this.handle.ident(), filter, flags, fflags, 0L, this.id);
        }
        
        @Override
        public boolean isValid() {
            return !this.canceled.get();
        }
        
        @Override
        public boolean cancel() {
            if (!this.canceled.compareAndSet(false, true)) {
                return false;
            }
            if (this.executor.isExecutorThread(Thread.currentThread())) {
                this.cancel0();
            }
            else {
                this.executor.execute(this::cancel0);
            }
            return true;
        }
        
        private void cancel0() {
            this.cancellationPending = true;
            KQueueIoHandler.this.cancelledRegistrations.offer(this);
        }
        
        void close() {
            this.cancel();
            try {
                this.handle.close();
            }
            catch (final Exception e) {
                KQueueIoHandler.logger.debug("Exception during closing " + this.handle, e);
            }
        }
    }
}
