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

package com.hypixel.hytale.server.core.io.netty;

import java.util.concurrent.Delayed;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelHandler;
import io.netty.channel.Channel;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import java.util.Comparator;
import java.util.Collection;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import io.netty.channel.ChannelPromise;
import io.netty.channel.ChannelHandlerContext;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.atomic.AtomicInteger;
import io.netty.channel.ChannelDuplexHandler;

public class LatencySimulationHandler extends ChannelDuplexHandler
{
    public static final String PIPELINE_KEY = "latencySimulator";
    private static final AtomicInteger counter;
    private final DelayQueue<DelayedHandler> delayedQueue;
    @Nonnull
    private final Thread taskThread;
    private final long delayNanos;
    
    public LatencySimulationHandler(final long delay, @Nonnull final TimeUnit unit) {
        this.delayedQueue = new DelayQueue<DelayedHandler>();
        this.delayNanos = unit.toNanos(delay);
        (this.taskThread = new Thread(() -> {
            try {
                while (!Thread.interrupted()) {
                    final DelayedHandler handler = this.delayedQueue.take();
                    handler.ctx.executor().execute(handler);
                }
            }
            catch (final InterruptedException ignored) {
                Thread.currentThread().interrupt();
            }
            return;
        }, "latency-simulator-" + LatencySimulationHandler.counter.getAndIncrement())).setDaemon(true);
        this.taskThread.start();
    }
    
    @Override
    public void read(final ChannelHandlerContext ctx) throws Exception {
        this.delayedQueue.offer(new DelayedRead(ctx, System.nanoTime() + this.delayNanos));
    }
    
    @Override
    public void write(final ChannelHandlerContext ctx, final Object msg, final ChannelPromise promise) throws Exception {
        this.delayedQueue.offer(new DelayedWrite(ctx, System.nanoTime() + this.delayNanos, msg, promise));
    }
    
    @Override
    public void flush(final ChannelHandlerContext ctx) {
        this.delayedQueue.offer(new DelayedFlush(ctx, System.nanoTime() + this.delayNanos));
    }
    
    @Override
    public void handlerRemoved(final ChannelHandlerContext ctx) throws Exception {
        super.handlerRemoved(ctx);
        this.taskThread.interrupt();
        final ObjectArrayList<DelayedHandler> list = new ObjectArrayList<DelayedHandler>(this.delayedQueue);
        list.sort(Comparator.comparingLong(value -> value.executeAtNanos));
        for (final DelayedHandler handler : list) {
            handler.run();
        }
    }
    
    @Override
    public void close(final ChannelHandlerContext ctx, final ChannelPromise promise) throws Exception {
        super.close(ctx, promise);
        this.taskThread.interrupt();
    }
    
    public static void setLatency(@Nonnull final Channel channel, final long delay, @Nonnull final TimeUnit unit) {
        final ChannelPipeline pipeline = channel.pipeline();
        if (pipeline.get("latencySimulator") == null) {
            if (delay <= 0L) {
                return;
            }
            pipeline.addAfter("packetArrayEncoder", "latencySimulator", new LatencySimulationHandler(delay, unit));
        }
        else {
            if (delay <= 0L) {
                pipeline.remove("latencySimulator");
                return;
            }
            pipeline.replace("latencySimulator", "latencySimulator", new LatencySimulationHandler(delay, unit));
        }
    }
    
    static {
        counter = new AtomicInteger();
    }
    
    private abstract static class DelayedHandler implements Delayed, Runnable
    {
        protected final ChannelHandlerContext ctx;
        protected final long executeAtNanos;
        
        protected DelayedHandler(final ChannelHandlerContext ctx, final long executeAtNanos) {
            this.ctx = ctx;
            this.executeAtNanos = executeAtNanos;
        }
        
        @Override
        public long getDelay(@Nonnull final TimeUnit unit) {
            return unit.convert(this.executeAtNanos - System.nanoTime(), TimeUnit.NANOSECONDS);
        }
        
        @Override
        public int compareTo(@Nonnull final Delayed o) {
            return Long.compare(this.executeAtNanos, ((DelayedHandler)o).executeAtNanos);
        }
    }
    
    private static class DelayedRead extends DelayedHandler
    {
        private DelayedRead(final ChannelHandlerContext ctx, final long executeAtNanos) {
            super(ctx, executeAtNanos);
        }
        
        @Override
        public void run() {
            this.ctx.read();
        }
    }
    
    private static class DelayedWrite extends DelayedHandler
    {
        private final Object msg;
        private final ChannelPromise promise;
        
        public DelayedWrite(final ChannelHandlerContext ctx, final long executeAtNanos, final Object msg, final ChannelPromise promise) {
            super(ctx, executeAtNanos);
            this.msg = msg;
            this.promise = promise;
        }
        
        @Override
        public void run() {
            this.ctx.write(this.msg, this.promise);
        }
    }
    
    private static class DelayedFlush extends DelayedHandler
    {
        public DelayedFlush(final ChannelHandlerContext ctx, final long executeAtNanos) {
            super(ctx, executeAtNanos);
        }
        
        @Override
        public void run() {
            this.ctx.flush();
        }
    }
}
