package net.minecraft.client.multiplayer;

import org.apache.logging.log4j.LogManager;
import java.util.Iterator;
import io.netty.channel.socket.nio.NioSocketChannel;
import net.minecraft.util.Mth;
import com.google.common.collect.Iterables;
import java.nio.charset.StandardCharsets;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.channel.ChannelFutureListener;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.buffer.ByteBuf;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelException;
import io.netty.channel.ChannelOption;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.bootstrap.Bootstrap;
import java.net.UnknownHostException;
import net.minecraft.network.protocol.status.ServerboundStatusRequestPacket;
import net.minecraft.network.protocol.handshake.ClientIntentionPacket;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.PacketListener;
import net.minecraft.network.protocol.status.ClientboundPongResponsePacket;
import com.mojang.authlib.GameProfile;
import net.minecraft.network.protocol.status.ServerStatus;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.status.ServerboundPingRequestPacket;
import net.minecraft.Util;
import java.util.Objects;
import net.minecraft.ChatFormatting;
import org.apache.commons.lang3.ArrayUtils;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.status.ClientboundStatusResponsePacket;
import net.minecraft.network.protocol.status.ClientStatusPacketListener;
import net.minecraft.network.chat.TranslatableComponent;
import java.net.InetAddress;
import java.util.Collections;
import com.google.common.collect.Lists;
import net.minecraft.network.Connection;
import java.util.List;
import org.apache.logging.log4j.Logger;
import com.google.common.base.Splitter;

public class ServerStatusPinger {
    private static final Splitter SPLITTER;
    private static final Logger LOGGER;
    private final List<Connection> connections;
    
    public ServerStatusPinger() {
        this.connections = Collections.<Connection>synchronizedList(Lists.newArrayList());
    }
    
    public void pingServer(final ServerData dys, final Runnable runnable) throws UnknownHostException {
        final ServerAddress dyr4 = ServerAddress.parseString(dys.ip);
        final Connection me5 = Connection.connectToServer(InetAddress.getByName(dyr4.getHost()), dyr4.getPort(), false);
        this.connections.add(me5);
        dys.motd = new TranslatableComponent("multiplayer.status.pinging");
        dys.ping = -1L;
        dys.playerList = null;
        me5.setListener(new ClientStatusPacketListener() {
            private boolean success;
            private boolean receivedPing;
            private long pingStart;
            
            @Override
            public void handleStatusResponse(final ClientboundStatusResponsePacket tk) {
                if (this.receivedPing) {
                    me5.disconnect(new TranslatableComponent("multiplayer.status.unrequested"));
                    return;
                }
                this.receivedPing = true;
                final ServerStatus tl3 = tk.getStatus();
                if (tl3.getDescription() != null) {
                    dys.motd = tl3.getDescription();
                }
                else {
                    dys.motd = TextComponent.EMPTY;
                }
                if (tl3.getVersion() != null) {
                    dys.version = new TextComponent(tl3.getVersion().getName());
                    dys.protocol = tl3.getVersion().getProtocol();
                }
                else {
                    dys.version = new TranslatableComponent("multiplayer.status.old");
                    dys.protocol = 0;
                }
                if (tl3.getPlayers() != null) {
                    dys.status = formatPlayerCount(tl3.getPlayers().getNumPlayers(), tl3.getPlayers().getMaxPlayers());
                    final List<Component> list4 = Lists.newArrayList();
                    if (ArrayUtils.isNotEmpty((Object[])tl3.getPlayers().getSample())) {
                        for (final GameProfile gameProfile8 : tl3.getPlayers().getSample()) {
                            list4.add(new TextComponent(gameProfile8.getName()));
                        }
                        if (tl3.getPlayers().getSample().length < tl3.getPlayers().getNumPlayers()) {
                            list4.add(new TranslatableComponent("multiplayer.status.and_more", new Object[] { tl3.getPlayers().getNumPlayers() - tl3.getPlayers().getSample().length }));
                        }
                        dys.playerList = list4;
                    }
                }
                else {
                    dys.status = new TranslatableComponent("multiplayer.status.unknown").withStyle(ChatFormatting.DARK_GRAY);
                }
                String string4 = null;
                if (tl3.getFavicon() != null) {
                    final String string5 = tl3.getFavicon();
                    if (string5.startsWith("data:image/png;base64,")) {
                        string4 = string5.substring("data:image/png;base64,".length());
                    }
                    else {
                        ServerStatusPinger.LOGGER.error("Invalid server icon (unknown format)");
                    }
                }
                if (!Objects.equals(string4, dys.getIconB64())) {
                    dys.setIconB64(string4);
                    runnable.run();
                }
                this.pingStart = Util.getMillis();
                me5.send(new ServerboundPingRequestPacket(this.pingStart));
                this.success = true;
            }
            
            @Override
            public void handlePongResponse(final ClientboundPongResponsePacket tj) {
                final long long3 = this.pingStart;
                final long long4 = Util.getMillis();
                dys.ping = long4 - long3;
                me5.disconnect(new TranslatableComponent("multiplayer.status.finished"));
            }
            
            @Override
            public void onDisconnect(final Component mr) {
                if (!this.success) {
                    ServerStatusPinger.LOGGER.error("Can't ping {}: {}", dys.ip, mr.getString());
                    dys.motd = new TranslatableComponent("multiplayer.status.cannot_connect").withStyle(ChatFormatting.DARK_RED);
                    dys.status = TextComponent.EMPTY;
                    ServerStatusPinger.this.pingLegacyServer(dys);
                }
            }
            
            @Override
            public Connection getConnection() {
                return me5;
            }
        });
        try {
            me5.send(new ClientIntentionPacket(dyr4.getHost(), dyr4.getPort(), ConnectionProtocol.STATUS));
            me5.send(new ServerboundStatusRequestPacket());
        }
        catch (Throwable throwable6) {
            ServerStatusPinger.LOGGER.error(throwable6);
        }
    }
    
    private void pingLegacyServer(final ServerData dys) {
        final ServerAddress dyr3 = ServerAddress.parseString(dys.ip);
        ((Bootstrap)((Bootstrap)((Bootstrap)new Bootstrap().group((EventLoopGroup)Connection.NETWORK_WORKER_GROUP.get())).handler((ChannelHandler)new ChannelInitializer<Channel>() {
            protected void initChannel(final Channel channel) throws Exception {
                try {
                    channel.config().setOption(ChannelOption.TCP_NODELAY, true);
                }
                catch (ChannelException ex) {}
                channel.pipeline().addLast(new ChannelHandler[] { (ChannelHandler)new SimpleChannelInboundHandler<ByteBuf>() {
                        public void channelActive(final ChannelHandlerContext channelHandlerContext) throws Exception {
                            super.channelActive(channelHandlerContext);
                            final ByteBuf byteBuf3 = Unpooled.buffer();
                            try {
                                byteBuf3.writeByte(254);
                                byteBuf3.writeByte(1);
                                byteBuf3.writeByte(250);
                                char[] arr4 = "MC|PingHost".toCharArray();
                                byteBuf3.writeShort(arr4.length);
                                for (final char character8 : arr4) {
                                    byteBuf3.writeChar((int)character8);
                                }
                                byteBuf3.writeShort(7 + 2 * dyr3.getHost().length());
                                byteBuf3.writeByte(127);
                                arr4 = dyr3.getHost().toCharArray();
                                byteBuf3.writeShort(arr4.length);
                                for (final char character8 : arr4) {
                                    byteBuf3.writeChar((int)character8);
                                }
                                byteBuf3.writeInt(dyr3.getPort());
                                channelHandlerContext.channel().writeAndFlush(byteBuf3).addListener((GenericFutureListener)ChannelFutureListener.CLOSE_ON_FAILURE);
                            }
                            finally {
                                byteBuf3.release();
                            }
                        }
                        
                        protected void channelRead0(final ChannelHandlerContext channelHandlerContext, final ByteBuf byteBuf) throws Exception {
                            final short short4 = byteBuf.readUnsignedByte();
                            if (short4 == 255) {
                                final String string5 = new String(byteBuf.readBytes(byteBuf.readShort() * 2).array(), StandardCharsets.UTF_16BE);
                                final String[] arr6 = Iterables.<String>toArray(ServerStatusPinger.SPLITTER.split(string5), String.class);
                                if ("§1".equals(arr6[0])) {
                                    final int integer7 = Mth.getInt(arr6[1], 0);
                                    final String string6 = arr6[2];
                                    final String string7 = arr6[3];
                                    final int integer8 = Mth.getInt(arr6[4], -1);
                                    final int integer9 = Mth.getInt(arr6[5], -1);
                                    dys.protocol = -1;
                                    dys.version = new TextComponent(string6);
                                    dys.motd = new TextComponent(string7);
                                    dys.status = formatPlayerCount(integer8, integer9);
                                }
                            }
                            channelHandlerContext.close();
                        }
                        
                        public void exceptionCaught(final ChannelHandlerContext channelHandlerContext, final Throwable throwable) throws Exception {
                            channelHandlerContext.close();
                        }
                    } });
            }
        })).channel((Class)NioSocketChannel.class)).connect(dyr3.getHost(), dyr3.getPort());
    }
    
    private static Component formatPlayerCount(final int integer1, final int integer2) {
        return new TextComponent(Integer.toString(integer1)).append(new TextComponent("/").withStyle(ChatFormatting.DARK_GRAY)).append(Integer.toString(integer2)).withStyle(ChatFormatting.GRAY);
    }
    
    public void tick() {
        synchronized (this.connections) {
            final Iterator<Connection> iterator3 = this.connections.iterator();
            while (iterator3.hasNext()) {
                final Connection me4 = iterator3.next();
                if (me4.isConnected()) {
                    me4.tick();
                }
                else {
                    iterator3.remove();
                    me4.handleDisconnection();
                }
            }
        }
    }
    
    public void removeAll() {
        synchronized (this.connections) {
            final Iterator<Connection> iterator3 = this.connections.iterator();
            while (iterator3.hasNext()) {
                final Connection me4 = iterator3.next();
                if (me4.isConnected()) {
                    iterator3.remove();
                    me4.disconnect(new TranslatableComponent("multiplayer.status.cancelled"));
                }
            }
        }
    }
    
    static {
        SPLITTER = Splitter.on('\0').limit(6);
        LOGGER = LogManager.getLogger();
    }
}
