package com.github.games647.lagmonitor.traffic;

import com.google.common.collect.Lists;
import com.github.games647.lagmonitor.traffic.Reflection.FieldAccessor;

import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;

import java.util.List;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;

import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;

/**
 * This is modified version of TinyProtocol from the ProtocolLib authors (dmulloy2 and aadnk). The not relevant things
 * are removed like player channel injection and the serverChannelHandler is modified so we can read the raw input of
 * the incoming and outgoing packets
 *
 * Original can be found here:
 * https://github.com/dmulloy2/ProtocolLib/blob/master/modules/TinyProtocol/src/main/java/com/comphenix/tinyprotocol/TinyProtocol.java
 */
public abstract class TinyProtocol {

    // Looking up ServerConnection
    private static final Class<Object> SERVER_CLASS = Reflection.getUntypedClass("{nms}.MinecraftServer");
    private static final Class<Object> CONNECTION_CLASS = Reflection.getUntypedClass("{nms}.ServerConnection");
    private static final FieldAccessor<Object> GET_SERVER = Reflection.getField("{obc}.CraftServer", SERVER_CLASS, 0);
    private static final FieldAccessor<Object> GET_CONNECTION = Reflection.getField(SERVER_CLASS, CONNECTION_CLASS, 0);

    // Injected channel handlers
    private final List<Channel> serverChannels = Lists.newArrayList();
    private ChannelInboundHandlerAdapter serverChannelHandler;

    private volatile boolean closed;
    protected final Plugin plugin;

    /**
     * Construct a new instance of TinyProtocol, and start intercepting packets for all connected clients and future
     * clients.
     * <p>
     * You can construct multiple instances per plugin.
     *
     * @param plugin - the plugin.
     */
    public TinyProtocol(final Plugin plugin) {
        this.plugin = plugin;

        try {
            registerChannelHandler();
        } catch (IllegalArgumentException illegalArgumentException) {
            // Damn you, late bind
            plugin.getLogger().info("[TinyProtocol] Delaying server channel injection due to late bind.");

            // Damn you, late bind
            Bukkit.getScheduler().runTask(plugin, () -> {
                registerChannelHandler();
                plugin.getLogger().info("[TinyProtocol] Late bind injection successful.");
            });
        }
    }

    private void createServerChannelHandler() {
        serverChannelHandler = new ChannelInboundHandlerAdapter() {

            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                Channel channel = (Channel) msg;

                channel.pipeline().addLast(new ChannelDuplexHandler() {
                    @Override
                    public void channelRead(ChannelHandlerContext handlerContext, Object object) throws Exception {
                        onChannelRead(handlerContext, object);

                        super.channelRead(handlerContext, object);
                    }

                    @Override
                    public void write(ChannelHandlerContext handlerContext, Object object, ChannelPromise promise)
                            throws Exception {
                        onChannelWrite(handlerContext, object, promise);

                        super.write(handlerContext, object, promise);
                    }
                });

                ctx.fireChannelRead(msg);
            }
        };
    }

    public abstract void onChannelRead(ChannelHandlerContext handlerContext, Object object) throws Exception;

    public abstract void onChannelWrite(ChannelHandlerContext handlerContext, Object object, ChannelPromise promise)
            throws Exception;

    @SuppressWarnings("unchecked")
    private void registerChannelHandler() {
        Object mcServer = GET_SERVER.get(Bukkit.getServer());
        Object serverConnection = GET_CONNECTION.get(mcServer);

        createServerChannelHandler();

        plugin.getLogger().info("CraftServer: " + mcServer);
        plugin.getLogger().info("ServerConnection: " + serverConnection);
        plugin.getLogger().info("ServerConnection: " + new ReflectionToStringBuilder(serverConnection));

        // Find the correct list, or implicitly throw an exception
        boolean looking = true;
        for (int i = 0; looking; i++) {
            plugin.getLogger().info("Index: " + i);
            plugin.getLogger().info("List field: " + Reflection.getField(serverConnection.getClass(), List.class, i));
            List<Object> list = Reflection.getField(serverConnection.getClass(), List.class, i).get(serverConnection);

            for (Object item : list) {
                if (!ChannelFuture.class.isInstance(item)) {
                    break;
                }

                // Channel future that contains the server connection
                Channel serverChannel = ((ChannelFuture) item).channel();

                serverChannels.add(serverChannel);
                serverChannel.pipeline().addFirst(serverChannelHandler);
                looking = false;
            }
        }
    }

    private void unregisterChannelHandler() {
        if (serverChannelHandler == null) {
            return;
        }

        serverChannels.forEach((serverChannel) -> {
            // Remove channel handler
            ChannelPipeline pipeline = serverChannel.pipeline();
            serverChannel.eventLoop().execute(new CleanUpTask(pipeline, serverChannelHandler));
        });
    }

    /**
     * Cease listening for packets. This is called automatically when your plugin is disabled.
     */
    public final void close() {
        if (!closed) {
            closed = true;

            unregisterChannelHandler();
        }
    }
}

@@ -482,74 +482,8 @@
 ist;
-%0Aimport org.apache.commons.lang.builder.ReflectionToStringBuilder;
 %0A%0Aim
@@ -4248,550 +4248,138 @@
     
-plugin.getLogger().info(%22CraftServer: %22 + mcServer);%0A        plugin.getLogger().info(%22ServerConnection: %22 + serverConnection);%0A        plugin.getLogger().info(%22ServerConnection: %22 + new ReflectionToStringBuilder(serverConnection));%0A%0A        // Find the correct list, or implicitly throw an exception%0A        boolean looking = true;%0A        for (int i = 0; looking; i++) %7B%0A            plugin.getLogger().info(%22Index: %22 + i);%0A            plugin.getLogger().info(%22List field: %22 + Reflection.getField(serverConnection.getClass(), List.class, i));
+// Find the correct list, or implicitly throw an exception%0A        boolean looking = true;%0A        for (int i = 0; looking; i++) %7B
 %0A   

Remove debug code