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

package io.netty.channel;

import io.netty.util.concurrent.EventExecutorGroup;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Callable;
import java.util.Collection;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import io.netty.util.concurrent.Future;
import io.netty.util.internal.ObjectUtil;
import io.netty.util.concurrent.AbstractEventExecutor;
import io.netty.util.internal.ThreadExecutorMap;
import java.util.Objects;
import io.netty.util.concurrent.ThreadAwareExecutor;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.util.concurrent.Ticker;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Queue;
import io.netty.util.concurrent.Promise;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.util.concurrent.AbstractScheduledEventExecutor;

public class ManualIoEventLoop extends AbstractScheduledEventExecutor implements IoEventLoop
{
    private static final Runnable WAKEUP_TASK;
    private static final int ST_STARTED = 0;
    private static final int ST_SHUTTING_DOWN = 1;
    private static final int ST_SHUTDOWN = 2;
    private static final int ST_TERMINATED = 3;
    private final AtomicInteger state;
    private final Promise<?> terminationFuture;
    private final Queue<Runnable> taskQueue;
    private final IoHandlerContext nonBlockingContext;
    private final BlockingIoHandlerContext blockingContext;
    private final IoEventLoopGroup parent;
    private final AtomicReference<Thread> owningThread;
    private final IoHandler handler;
    private final Ticker ticker;
    private volatile long gracefulShutdownQuietPeriod;
    private volatile long gracefulShutdownTimeout;
    private long gracefulShutdownStartTime;
    private long lastExecutionTime;
    private boolean initialized;
    
    protected boolean canBlock() {
        return true;
    }
    
    public ManualIoEventLoop(final Thread owningThread, final IoHandlerFactory factory) {
        this(null, owningThread, factory);
    }
    
    public ManualIoEventLoop(final IoEventLoopGroup parent, final Thread owningThread, final IoHandlerFactory factory) {
        this(parent, owningThread, factory, Ticker.systemTicker());
    }
    
    public ManualIoEventLoop(final IoEventLoopGroup parent, final Thread owningThread, final IoHandlerFactory factory, final Ticker ticker) {
        this.terminationFuture = new DefaultPromise<Object>(GlobalEventExecutor.INSTANCE);
        this.taskQueue = PlatformDependent.newMpscQueue();
        this.nonBlockingContext = new IoHandlerContext() {
            @Override
            public boolean canBlock() {
                assert ManualIoEventLoop.this.inEventLoop();
                return false;
            }
            
            @Override
            public long delayNanos(final long currentTimeNanos) {
                assert ManualIoEventLoop.this.inEventLoop();
                return 0L;
            }
            
            @Override
            public long deadlineNanos() {
                assert ManualIoEventLoop.this.inEventLoop();
                return -1L;
            }
        };
        this.blockingContext = new BlockingIoHandlerContext();
        this.parent = parent;
        this.owningThread = new AtomicReference<Thread>(owningThread);
        this.handler = factory.newHandler(this);
        this.ticker = Objects.requireNonNull(ticker, "ticker");
        this.state = new AtomicInteger(0);
    }
    
    @Override
    public final Ticker ticker() {
        return this.ticker;
    }
    
    public final int runNonBlockingTasks(final long timeoutNanos) {
        return this.runAllTasks(timeoutNanos, true);
    }
    
    private int runAllTasks(final long timeoutNanos, final boolean setCurrentExecutor) {
        assert this.inEventLoop();
        final Queue<Runnable> taskQueue = this.taskQueue;
        final boolean alwaysTrue = this.fetchFromScheduledTaskQueue(taskQueue);
        assert alwaysTrue;
        Runnable task = taskQueue.poll();
        if (task == null) {
            return 0;
        }
        final EventExecutor old = setCurrentExecutor ? ThreadExecutorMap.setCurrentExecutor(this) : null;
        try {
            final long deadline = (timeoutNanos > 0L) ? (this.getCurrentTimeNanos() + timeoutNanos) : 0L;
            int runTasks = 0;
            final Ticker ticker = this.ticker;
            while (true) {
                do {
                    AbstractEventExecutor.safeExecute(task);
                    ++runTasks;
                    if (timeoutNanos > 0L) {
                        final long lastExecutionTime = ticker.nanoTime();
                        if (lastExecutionTime - deadline >= 0L) {
                            this.lastExecutionTime = lastExecutionTime;
                            return runTasks;
                        }
                    }
                    task = taskQueue.poll();
                } while (task != null);
                final long lastExecutionTime = ticker.nanoTime();
                continue;
            }
        }
        finally {
            if (setCurrentExecutor) {
                ThreadExecutorMap.setCurrentExecutor(old);
            }
        }
    }
    
    private int run(final IoHandlerContext context, final long runAllTasksTimeoutNanos) {
        if (!this.initialized) {
            if (this.owningThread.get() == null) {
                throw new IllegalStateException("Owning thread not set");
            }
            this.initialized = true;
            this.handler.initialize();
        }
        final EventExecutor old = ThreadExecutorMap.setCurrentExecutor(this);
        try {
            if (this.isShuttingDown()) {
                if (this.terminationFuture.isDone()) {
                    return 0;
                }
                return this.runAllTasksBeforeDestroy();
            }
            else {
                final int ioTasks = this.handler.run(context);
                if (runAllTasksTimeoutNanos < 0L) {
                    return ioTasks;
                }
                assert runAllTasksTimeoutNanos >= 0L;
                return ioTasks + this.runAllTasks(runAllTasksTimeoutNanos, false);
            }
        }
        finally {
            ThreadExecutorMap.setCurrentExecutor(old);
        }
    }
    
    private int runAllTasksBeforeDestroy() {
        int run = this.runAllTasks(-1L, false);
        this.handler.prepareToDestroy();
        if (this.confirmShutdown()) {
            try {
                this.handler.destroy();
                int r;
                do {
                    r = this.runAllTasks(-1L, false);
                    run += r;
                } while (r != 0);
            }
            finally {
                this.state.set(3);
                this.terminationFuture.setSuccess(null);
            }
        }
        return run;
    }
    
    public final int runNow(final long runAllTasksTimeoutNanos) {
        this.checkCurrentThread();
        return this.run(this.nonBlockingContext, runAllTasksTimeoutNanos);
    }
    
    public final int runNow() {
        this.checkCurrentThread();
        return this.run(this.nonBlockingContext, 0L);
    }
    
    public final int run(final long waitNanos, final long runAllTasksTimeoutNanos) {
        this.checkCurrentThread();
        IoHandlerContext context;
        if (waitNanos < 0L) {
            context = this.nonBlockingContext;
        }
        else {
            context = this.blockingContext;
            this.blockingContext.maxBlockingNanos = ((waitNanos == 0L) ? Long.MAX_VALUE : waitNanos);
        }
        return this.run(context, runAllTasksTimeoutNanos);
    }
    
    public final int run(final long waitNanos) {
        return this.run(waitNanos, 0L);
    }
    
    private void checkCurrentThread() {
        if (!this.inEventLoop(Thread.currentThread())) {
            throw new IllegalStateException();
        }
    }
    
    public final void wakeup() {
        if (this.isShuttingDown()) {
            return;
        }
        this.handler.wakeup();
    }
    
    @Override
    public final ManualIoEventLoop next() {
        return this;
    }
    
    @Override
    public final IoEventLoopGroup parent() {
        return this.parent;
    }
    
    @Deprecated
    @Override
    public final ChannelFuture register(final Channel channel) {
        return this.register(new DefaultChannelPromise(channel, this));
    }
    
    @Deprecated
    @Override
    public final ChannelFuture register(final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        promise.channel().unsafe().register(this, promise);
        return promise;
    }
    
    @Override
    public final Future<IoRegistration> register(final IoHandle handle) {
        final Promise<IoRegistration> promise = this.newPromise();
        if (this.inEventLoop()) {
            this.registerForIo0(handle, promise);
        }
        else {
            this.execute(() -> this.registerForIo0(handle, promise));
        }
        return promise;
    }
    
    private void registerForIo0(final IoHandle handle, final Promise<IoRegistration> promise) {
        assert this.inEventLoop();
        IoRegistration registration;
        try {
            registration = this.handler.register(handle);
        }
        catch (final Exception e) {
            promise.setFailure(e);
            return;
        }
        promise.setSuccess(registration);
    }
    
    @Deprecated
    @Override
    public final ChannelFuture register(final Channel channel, final ChannelPromise promise) {
        ObjectUtil.checkNotNull(promise, "promise");
        ObjectUtil.checkNotNull(channel, "channel");
        channel.unsafe().register(this, promise);
        return promise;
    }
    
    @Override
    public final boolean isCompatible(final Class<? extends IoHandle> handleType) {
        return this.handler.isCompatible(handleType);
    }
    
    @Override
    public final boolean isIoType(final Class<? extends IoHandler> handlerType) {
        return this.handler.getClass().equals(handlerType);
    }
    
    @Override
    public final boolean inEventLoop(final Thread thread) {
        return this.owningThread.get() == thread;
    }
    
    public final void setOwningThread(final Thread owningThread) {
        Objects.requireNonNull(owningThread, "owningThread");
        if (!this.owningThread.compareAndSet(null, owningThread)) {
            throw new IllegalStateException("Owning thread already set");
        }
    }
    
    private void shutdown0(final long quietPeriod, final long timeout, final int shutdownState) {
        final boolean inEventLoop = this.inEventLoop();
        while (!this.isShuttingDown()) {
            boolean wakeup = true;
            final int oldState = this.state.get();
            int newState;
            if (inEventLoop) {
                newState = shutdownState;
            }
            else if (oldState == 0) {
                newState = shutdownState;
            }
            else {
                newState = oldState;
                wakeup = false;
            }
            if (this.state.compareAndSet(oldState, newState)) {
                if (quietPeriod != -1L) {
                    this.gracefulShutdownQuietPeriod = quietPeriod;
                }
                if (timeout != -1L) {
                    this.gracefulShutdownTimeout = timeout;
                }
                if (wakeup) {
                    this.taskQueue.offer(ManualIoEventLoop.WAKEUP_TASK);
                    this.handler.wakeup();
                }
            }
        }
    }
    
    @Override
    public final Future<?> shutdownGracefully(final long quietPeriod, final long timeout, final TimeUnit unit) {
        ObjectUtil.checkPositiveOrZero(quietPeriod, "quietPeriod");
        if (timeout < quietPeriod) {
            throw new IllegalArgumentException("timeout: " + timeout + " (expected >= quietPeriod (" + quietPeriod + "))");
        }
        ObjectUtil.checkNotNull(unit, "unit");
        this.shutdown0(unit.toNanos(quietPeriod), unit.toNanos(timeout), 1);
        return this.terminationFuture();
    }
    
    @Deprecated
    @Override
    public final void shutdown() {
        this.shutdown0(-1L, -1L, 2);
    }
    
    @Override
    public final Future<?> terminationFuture() {
        return this.terminationFuture;
    }
    
    @Override
    public final boolean isShuttingDown() {
        return this.state.get() >= 1;
    }
    
    @Override
    public final boolean isShutdown() {
        return this.state.get() >= 2;
    }
    
    @Override
    public final boolean isTerminated() {
        return this.state.get() == 3;
    }
    
    @Override
    public final boolean awaitTermination(final long timeout, final TimeUnit unit) throws InterruptedException {
        return this.terminationFuture.await(timeout, unit);
    }
    
    @Override
    public final void execute(final Runnable command) {
        Objects.requireNonNull(command, "command");
        final boolean inEventLoop = this.inEventLoop();
        if (inEventLoop && this.isShutdown()) {
            throw new RejectedExecutionException("event executor terminated");
        }
        this.taskQueue.add(command);
        if (!inEventLoop) {
            if (this.isShutdown()) {
                boolean reject = false;
                try {
                    if (this.taskQueue.remove(command)) {
                        reject = true;
                    }
                }
                catch (final UnsupportedOperationException ex) {}
                if (reject) {
                    throw new RejectedExecutionException("event executor terminated");
                }
            }
            this.handler.wakeup();
        }
    }
    
    private boolean hasTasks() {
        return !this.taskQueue.isEmpty();
    }
    
    private boolean confirmShutdown() {
        if (!this.isShuttingDown()) {
            return false;
        }
        if (!this.inEventLoop()) {
            throw new IllegalStateException("must be invoked from an event loop");
        }
        this.cancelScheduledTasks();
        if (this.gracefulShutdownStartTime == 0L) {
            this.gracefulShutdownStartTime = this.ticker.nanoTime();
        }
        if (this.runAllTasks(-1L, false) > 0) {
            return this.isShutdown() || this.gracefulShutdownQuietPeriod == 0L;
        }
        final long nanoTime = this.ticker.nanoTime();
        if (this.isShutdown() || nanoTime - this.gracefulShutdownStartTime > this.gracefulShutdownTimeout) {
            return true;
        }
        if (nanoTime - this.lastExecutionTime <= this.gracefulShutdownQuietPeriod) {
            try {
                Thread.sleep(100L);
            }
            catch (final InterruptedException ex) {}
            return false;
        }
        return true;
    }
    
    @Override
    public final <T> T invokeAny(final Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
        this.throwIfInEventLoop("invokeAny");
        return super.invokeAny(tasks);
    }
    
    @Override
    public final <T> T invokeAny(final Collection<? extends Callable<T>> tasks, final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
        this.throwIfInEventLoop("invokeAny");
        return super.invokeAny(tasks, timeout, unit);
    }
    
    @Override
    public final <T> List<java.util.concurrent.Future<T>> invokeAll(final Collection<? extends Callable<T>> tasks) throws InterruptedException {
        this.throwIfInEventLoop("invokeAll");
        return super.invokeAll(tasks);
    }
    
    @Override
    public final <T> List<java.util.concurrent.Future<T>> invokeAll(final Collection<? extends Callable<T>> tasks, final long timeout, final TimeUnit unit) throws InterruptedException {
        this.throwIfInEventLoop("invokeAll");
        return super.invokeAll(tasks, timeout, unit);
    }
    
    private void throwIfInEventLoop(final String method) {
        if (this.inEventLoop()) {
            throw new RejectedExecutionException("Calling " + method + " from within the EventLoop is not allowed as it would deadlock");
        }
    }
    
    static {
        WAKEUP_TASK = (() -> {});
    }
    
    private class BlockingIoHandlerContext implements IoHandlerContext
    {
        long maxBlockingNanos;
        
        private BlockingIoHandlerContext() {
            this.maxBlockingNanos = Long.MAX_VALUE;
        }
        
        @Override
        public boolean canBlock() {
            assert ManualIoEventLoop.this.inEventLoop();
            return !ManualIoEventLoop.this.hasTasks() && !AbstractScheduledEventExecutor.this.hasScheduledTasks() && ManualIoEventLoop.this.canBlock();
        }
        
        @Override
        public long delayNanos(final long currentTimeNanos) {
            assert ManualIoEventLoop.this.inEventLoop();
            return Math.min(this.maxBlockingNanos, AbstractScheduledEventExecutor.this.delayNanos(currentTimeNanos, this.maxBlockingNanos));
        }
        
        @Override
        public long deadlineNanos() {
            assert ManualIoEventLoop.this.inEventLoop();
            final long next = AbstractScheduledEventExecutor.this.nextScheduledTaskDeadlineNanos();
            if (this.maxBlockingNanos == Long.MAX_VALUE) {
                return next;
            }
            final long now = ManualIoEventLoop.this.ticker.nanoTime();
            if (next == -1L || next - now > this.maxBlockingNanos) {
                return now + this.maxBlockingNanos;
            }
            return next;
        }
    }
}
