content: package net.minecraft.network;
  
  import com.google.common.collect.Queues;
  import com.google.common.util.concurrent.ThreadFactoryBuilder;
  import com.viaversion.viarewind.protocol.protocol1_8to1_9.Protocol1_8To1_9;
  import com.viaversion.viarewind.utils.PacketUtil;
  import com.viaversion.viaversion.api.Via;
  import com.viaversion.viaversion.api.connection.UserConnection;
  import com.viaversion.viaversion.api.minecraft.Position;
  import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
  import com.viaversion.viaversion.api.type.Type;
  import com.viaversion.viaversion.connection.UserConnectionImpl;
  import com.viaversion.viaversion.protocol.ProtocolPipelineImpl;
  import com.viaversion.viaversion.protocols.protocol1_9to1_8.ServerboundPackets1_9;
  import de.florianmichael.vialoadingbase.ViaLoadingBase;
  import de.florianmichael.vialoadingbase.netty.event.CompressionReorderEvent;
  import de.florianmichael.viamcp.MCPVLBPipeline;
  import de.florianmichael.viamcp.ViaMCP;
  import io.netty.bootstrap.Bootstrap;
  import io.netty.channel.*;
  import io.netty.channel.epoll.Epoll;
  import io.netty.channel.epoll.EpollEventLoopGroup;
  import io.netty.channel.epoll.EpollSocketChannel;
  import io.netty.channel.local.LocalChannel;
  import io.netty.channel.local.LocalEventLoopGroup;
  import io.netty.channel.local.LocalServerChannel;
  import io.netty.channel.nio.NioEventLoopGroup;
  import io.netty.channel.socket.SocketChannel;
  import io.netty.channel.socket.nio.NioSocketChannel;
  import io.netty.handler.timeout.ReadTimeoutHandler;
  import io.netty.handler.timeout.TimeoutException;
  import io.netty.util.AttributeKey;
  import io.netty.util.concurrent.Future;
  import io.netty.util.concurrent.GenericFutureListener;
  import moe.ichinomiya.naven.Naven;
  import moe.ichinomiya.naven.events.api.types.EventType;
  import moe.ichinomiya.naven.events.impl.EventDispatchPacket;
  import moe.ichinomiya.naven.events.impl.EventGlobalPacket;
  import moe.ichinomiya.naven.events.impl.EventPacket;
  import net.minecraft.network.play.client.*;
  import net.minecraft.util.*;
  import org.apache.commons.lang3.ArrayUtils;
  import org.apache.commons.lang3.Validate;
  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.Marker;
  import org.apache.logging.log4j.MarkerManager;
  
  import javax.crypto.SecretKey;
  import java.net.InetAddress;
  import java.net.SocketAddress;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.Queue;
  import java.util.concurrent.locks.ReentrantReadWriteLock;
  
  public class NetworkManager extends SimpleChannelInboundHandler<Packet> {
      public static final Marker logMarkerNetwork = MarkerManager.getMarker("NETWORK");
      public static final Marker logMarkerPackets = MarkerManager.getMarker("NETWORK_PACKETS", logMarkerNetwork);
      public static final AttributeKey<EnumConnectionState> attrKeyConnectionState = AttributeKey.valueOf("protocol");
      public static final LazyLoadBase<NioEventLoopGroup> CLIENT_NIO_EVENTLOOP = new LazyLoadBase<NioEventLoopGroup>() {
          protected NioEventLoopGroup load() {
              return new NioEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Client IO #%d").setDaemon(true).build());
          }
      };
      public static final LazyLoadBase<EpollEventLoopGroup> CLIENT_EPOLL_EVENTLOOP = new LazyLoadBase<EpollEventLoopGroup>() {
          protected EpollEventLoopGroup load() {
              return new EpollEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Epoll Client IO #%d").setDaemon(true).build());
          }
      };
      public static final LazyLoadBase<LocalEventLoopGroup> CLIENT_LOCAL_EVENTLOOP = new LazyLoadBase<LocalEventLoopGroup>() {
          protected LocalEventLoopGroup load() {
              return new LocalEventLoopGroup(0, (new ThreadFactoryBuilder()).setNameFormat("Netty Local Client IO #%d").setDaemon(true).build());
          }
      };
      private static final Logger logger = LogManager.getLogger();
      private final EnumPacketDirection direction;
      private final Queue<NetworkManager.InboundHandlerTuplePacketListener> outboundPacketsQueue = Queues.newConcurrentLinkedQueue();
      private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
      private Channel channel;
      private SocketAddress socketAddress;
      private INetHandler packetListener;
      private IChatComponent terminationReason;
      private boolean isEncrypted;
      private boolean disconnected;
  
      public NetworkManager(EnumPacketDirection packetDirection) {
          this.direction = packetDirection;
      }
  
      public static NetworkManager createNetworkManagerAndConnect(InetAddress address, int serverPort, boolean useNativeTransport) {
          final NetworkManager networkmanager = new NetworkManager(EnumPacketDirection.CLIENTBOUND);
          Class<? extends SocketChannel> oclass;
          LazyLoadBase<? extends EventLoopGroup> lazyloadbase;
  
          if (Epoll.isAvailable() && useNativeTransport) {
              oclass = EpollSocketChannel.class;
              lazyloadbase = CLIENT_EPOLL_EVENTLOOP;
          } else {
              oclass = NioSocketChannel.class;
              lazyloadbase = CLIENT_NIO_EVENTLOOP;
          }
  
          (new Bootstrap()).group(lazyloadbase.getValue()).handler(new ChannelInitializer<Channel>() {
              protected void initChannel(Channel p_initChannel_1_) throws Exception {
                  try {
                      p_initChannel_1_.config().setOption(ChannelOption.TCP_NODELAY, true);
                  } catch (ChannelException var3) {
                  }
  
                  p_initChannel_1_.pipeline().addLast("timeout", new ReadTimeoutHandler(30)).addLast("splitter", new MessageDeserializer2()).addLast("decoder", new MessageDeserializer(EnumPacketDirection.CLIENTBOUND)).addLast("prepender", new MessageSerializer2()).addLast("encoder", new MessageSerializer(EnumPacketDirection.SERVERBOUND)).addLast("packet_handler", networkmanager);
  
                  if (p_initChannel_1_ instanceof SocketChannel && ViaLoadingBase.getInstance().getTargetVersion().getVersion() != ViaMCP.NATIVE_VERSION) {
                      final UserConnection user = new UserConnectionImpl(p_initChannel_1_, true);
                      new ProtocolPipelineImpl(user);
  
                      p_initChannel_1_.pipeline().addLast(new MCPVLBPipeline(user));
                  }
              }
          }).channel(oclass).connect(address, serverPort).syncUninterruptibly();
          return networkmanager;
      }
  
      public static NetworkManager provideLocalClient(SocketAddress address) {
          final NetworkManager networkmanager = new NetworkManager(EnumPacketDirection.CLIENTBOUND);
          (new Bootstrap()).group(CLIENT_LOCAL_EVENTLOOP.getValue()).handler(new ChannelInitializer<Channel>() {
              protected void initChannel(Channel p_initChannel_1_) throws Exception {
                  p_initChannel_1_.pipeline().addLast("packet_handler", networkmanager);
              }
          }).channel(LocalChannel.class).connect(address).syncUninterruptibly();
          return networkmanager;
      }
  
      public void channelActive(ChannelHandlerContext p_channelActive_1_) throws Exception {
          super.channelActive(p_channelActive_1_);
          this.channel = p_channelActive_1_.channel();
          this.socketAddress = this.channel.remoteAddress();
  
          try {
              this.setConnectionState(EnumConnectionState.HANDSHAKING);
          } catch (Throwable throwable) {
              logger.fatal(throwable);
          }
      }
  
      public void setConnectionState(EnumConnectionState newState) {
          this.channel.read();
          this.channel.attr(attrKeyConnectionState).set(newState);
          this.channel.config().setAutoRead(true);
          logger.debug("Enabled auto read");
      }
  
      public void channelInactive(ChannelHandlerContext p_channelInactive_1_) throws Exception {
          this.closeChannel(new ChatComponentTranslation("disconnect.endOfStream"));
      }
  
      public void exceptionCaught(ChannelHandlerContext p_exceptionCaught_1_, Throwable p_exceptionCaught_2_) throws Exception {
          ChatComponentTranslation chatcomponenttranslation;
  
          if (p_exceptionCaught_2_ instanceof TimeoutException) {
              chatcomponenttranslation = new ChatComponentTranslation("disconnect.timeout");
          } else {
              chatcomponenttranslation = new ChatComponentTranslation("disconnect.genericReason", "Internal Exception: " + p_exceptionCaught_2_);
          }
  
          this.closeChannel(chatcomponenttranslation);
      }
  
      protected void channelRead0(ChannelHandlerContext p_channelRead0_1_, Packet p_channelRead0_2_) throws Exception {
          EventGlobalPacket globalEvent = new EventGlobalPacket(EventType.RECEIVE, p_channelRead0_2_);
          Naven.getInstance().getEventManager().call(globalEvent);
  
          p_channelRead0_2_ = globalEvent.getPacket();
  
          if (globalEvent.isCancelled()) {
              return;
          }
  
          // Client
          EventPacket event = new EventPacket(EventType.RECEIVE, p_channelRead0_2_);
          Naven.getInstance().getEventManager().call(event);
  
          p_channelRead0_2_ = event.getPacket();
  
          if (event.isCancelled()) {
              return;
          }
  
          if (this.channel.isOpen()) {
              try {
                  p_channelRead0_2_.processPacket(this.packetListener);
              } catch (ThreadQuickExitException var4) {
              }
          }
      }
  
      public void sendPacketNoEvent(Packet packetIn) {
          if (this.isChannelOpen()) {
              this.flushOutboundQueue();
          } else {
              this.readWriteLock.writeLock().lock();
  
              try {
                  this.outboundPacketsQueue.add(new NetworkManager.InboundHandlerTuplePacketListener(packetIn));
              } finally {
                  this.readWriteLock.writeLock().unlock();
              }
          }
      }
  
      private boolean handleNewPackets(Packet<?> packet) {
          boolean isHighVersion = ViaLoadingBase.getInstance().getTargetVersion().getVersion() > ViaMCP.NATIVE_VERSION;
  
          if (packet instanceof ViaPacket) {
              if (isHighVersion) {
                  Iterator<UserConnection> iterator = Via.getManager().getConnectionManager().getConnections().iterator();
  
                  if (iterator.hasNext()) {
                      UserConnection connection = iterator.next();
  
                      if (packet instanceof CPacketPlayerTryUseItem) {
                          CPacketPlayerTryUseItem useItemPacket = (CPacketPlayerTryUseItem) packet;
  
                          PacketWrapper useItem = PacketWrapper.create(ServerboundPackets1_9.USE_ITEM, null, connection);
                          useItem.write(Type.VAR_INT, useItemPacket.getHand());
                          PacketUtil.sendToServer(useItem, Protocol1_8To1_9.class, false, false);
                      } else if (packet instanceof CPacketConfirmTeleport) {
                          CPacketConfirmTeleport confirmTeleportPacket = (CPacketConfirmTeleport) packet;
  
                          PacketWrapper confirmTeleport = PacketWrapper.create(ServerboundPackets1_9.TELEPORT_CONFIRM, null, connection);
                          confirmTeleport.write(Type.VAR_INT, confirmTeleportPacket.getTeleportId());
                          PacketUtil.sendToServer(confirmTeleport, Protocol1_8To1_9.class, true, true);
                      } else if (packet instanceof CPacketSwapItemWithOffHand) {
                          PacketWrapper swapItem = PacketWrapper.create(ServerboundPackets1_9.PLAYER_DIGGING, null, connection);
                          PacketUtil.sendToServer(swapItem, Protocol1_8To1_9.class, true, true);
                      }
                  }
              }
  
              return true;
          }
  
          return false;
      }
  
      public void sendPacket(Packet<?> packetIn) {
          EventGlobalPacket globalPacket = new EventGlobalPacket(EventType.SEND, packetIn);
          Naven.getInstance().getEventManager().call(globalPacket);
          packetIn = globalPacket.getPacket();
  
          if (globalPacket.isCancelled()) {
              return;
          }
  
          // Client
          EventPacket event = new EventPacket(EventType.SEND, packetIn);
          Naven.getInstance().getEventManager().call(event);
  
          packetIn = event.getPacket();
  
          if (event.isCancelled()) {
              return;
          }
  
          if (handleNewPackets(packetIn)) {
              return;
          }
  
          if (this.isChannelOpen()) {
              this.flushOutboundQueue();
              this.dispatchPacket(packetIn, null);
          } else {
              this.readWriteLock.writeLock().lock();
  
              try {
                  this.outboundPacketsQueue.add(new NetworkManager.InboundHandlerTuplePacketListener(packetIn, (GenericFutureListener[]) null));
              } finally {
                  this.readWriteLock.writeLock().unlock();
              }
          }
      }
  
      public void sendPacket(Packet packetIn, GenericFutureListener<? extends Future<? super Void>> listener, GenericFutureListener<? extends Future<? super Void>>... listeners) {
          if (this.isChannelOpen()) {
              this.flushOutboundQueue();
              this.dispatchPacket(packetIn, ArrayUtils.add(listeners, 0, listener));
          } else {
              this.readWriteLock.writeLock().lock();
  
              try {
                  this.outboundPacketsQueue.add(new NetworkManager.InboundHandlerTuplePacketListener(packetIn, ArrayUtils.add(listeners, 0, listener)));
              } finally {
                  this.readWriteLock.writeLock().unlock();
              }
          }
      }
  
      private void dispatchPacket(final Packet packet, final GenericFutureListener<? extends Future<? super Void>>[] futureListeners) {
          LinkedList<Packet<?>> additionalPackets = new LinkedList<>();
          EventDispatchPacket event = new EventDispatchPacket(packet, additionalPackets);
          Naven.getInstance().getEventManager().call(event);
  
          if (event.isCancelled()) {
              return;
          }
  
          final Packet<?> inPacket = event.getPacket();
  
          for (Packet<?> additionalPacket : event.getAdditionalPackets()) {
              dispatchPacket2(additionalPacket, futureListeners);
          }
  
          dispatchPacket2(inPacket, futureListeners);
      }
  
      private void dispatchPacket2(final Packet packet, final GenericFutureListener<? extends Future<? super Void>>[] futureListeners) {
          final EnumConnectionState enumconnectionstate = EnumConnectionState.getFromPacket(packet);
          final EnumConnectionState enumconnectionstate1 = this.channel.attr(attrKeyConnectionState).get();
  
          if (enumconnectionstate1 != enumconnectionstate) {
              logger.debug("Disabled auto read");
              this.channel.config().setAutoRead(false);
          }
  
          if (this.channel.eventLoop().inEventLoop()) {
              if (enumconnectionstate != enumconnectionstate1) {
                  this.setConnectionState(enumconnectionstate);
              }
  
              ChannelFuture channelfuture = this.channel.writeAndFlush(packet);
  
              if (futureListeners != null) {
                  channelfuture.addListeners(futureListeners);
              }
  
              channelfuture.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
          } else {
              this.channel.eventLoop().execute(new Runnable() {
                  public void run() {
                      if (enumconnectionstate != enumconnectionstate1) {
                          NetworkManager.this.setConnectionState(enumconnectionstate);
                      }
  
                      ChannelFuture channelfuture1 = NetworkManager.this.channel.writeAndFlush(packet);
  
                      if (futureListeners != null) {
                          channelfuture1.addListeners(futureListeners);
                      }
  
                      channelfuture1.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
                  }
              });
          }
      }
  
      private void flushOutboundQueue() {
          if (this.channel != null && this.channel.isOpen()) {
              this.readWriteLock.readLock().lock();
  
              try {
                  while (!this.outboundPacketsQueue.isEmpty()) {
                      NetworkManager.InboundHandlerTuplePacketListener networkmanager$inboundhandlertuplepacketlistener = this.outboundPacketsQueue.poll();
                      this.dispatchPacket(networkmanager$inboundhandlertuplepacketlistener.packet, networkmanager$inboundhandlertuplepacketlistener.futureListeners);
                  }
              } finally {
                  this.readWriteLock.readLock().unlock();
              }
          }
      }
  
      public void processReceivedPackets() {
          this.flushOutboundQueue();
  
          if (this.packetListener instanceof ITickable) {
              ((ITickable) this.packetListener).update();
          }
  
          this.channel.flush();
      }
  
      public SocketAddress getRemoteAddress() {
          return this.socketAddress;
      }
  
      public void closeChannel(IChatComponent message) {
          if (this.channel.isOpen()) {
              this.channel.close().awaitUninterruptibly();
              this.terminationReason = message;
          }
      }
  
      public boolean isLocalChannel() {
          return this.channel instanceof LocalChannel || this.channel instanceof LocalServerChannel;
      }
  
      public void enableEncryption(SecretKey key) {
          this.isEncrypted = true;
          this.channel.pipeline().addBefore("splitter", "decrypt", new NettyEncryptingDecoder(CryptManager.createNetCipherInstance(2, key)));
          this.channel.pipeline().addBefore("prepender", "encrypt", new NettyEncryptingEncoder(CryptManager.createNetCipherInstance(1, key)));
      }
  
      public boolean getIsencrypted() {
          return this.isEncrypted;
      }
  
      public boolean isChannelOpen() {
          return this.channel != null && this.channel.isOpen();
      }
  
      public boolean hasNoChannel() {
          return this.channel == null;
      }
  
      public INetHandler getNetHandler() {
          return this.packetListener;
      }
  
      public void setNetHandler(INetHandler handler) {
          Validate.notNull(handler, "packetListener");
          logger.debug("Set listener of {} to {}", this, handler);
          this.packetListener = handler;
      }
  
      public IChatComponent getExitMessage() {
          return this.terminationReason;
      }
  
      public void disableAutoRead() {
          this.channel.config().setAutoRead(false);
      }
  
      public void setCompressionTreshold(int treshold) {
          if (treshold >= 0) {
              if (this.channel.pipeline().get("decompress") instanceof NettyCompressionDecoder) {
                  ((NettyCompressionDecoder) this.channel.pipeline().get("decompress")).setCompressionTreshold(treshold);
              } else {
                  this.channel.pipeline().addBefore("decoder", "decompress", new NettyCompressionDecoder(treshold));
              }
  
              if (this.channel.pipeline().get("compress") instanceof NettyCompressionEncoder) {
                  ((NettyCompressionEncoder) this.channel.pipeline().get("decompress")).setCompressionTreshold(treshold);
              } else {
                  this.channel.pipeline().addBefore("encoder", "compress", new NettyCompressionEncoder(treshold));
              }
          } else {
              if (this.channel.pipeline().get("decompress") instanceof NettyCompressionDecoder) {
                  this.channel.pipeline().remove("decompress");
              }
  
              if (this.channel.pipeline().get("compress") instanceof NettyCompressionEncoder) {
                  this.channel.pipeline().remove("compress");
              }
          }
  
          this.channel.pipeline().fireUserEventTriggered(new CompressionReorderEvent());
      }
  
      public void checkDisconnected() {
          if (this.channel != null && !this.channel.isOpen()) {
              if (!this.disconnected) {
                  this.disconnected = true;
  
                  if (this.getExitMessage() != null) {
                      this.getNetHandler().onDisconnect(this.getExitMessage());
                  } else if (this.getNetHandler() != null) {
                      this.getNetHandler().onDisconnect(new ChatComponentText("Disconnected"));
                  }
              } else {
                  logger.warn("handleDisconnection() called twice");
              }
          }
      }
  
      static class InboundHandlerTuplePacketListener {
          private final Packet packet;
          private final GenericFutureListener<? extends Future<? super Void>>[] futureListeners;
  
          public InboundHandlerTuplePacketListener(Packet inPacket, GenericFutureListener<? extends Future<? super Void>>... inFutureListeners) {
              this.packet = inPacket;
              this.futureListeners = inFutureListeners;
          }
      }
  }
  