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

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

import java.util.concurrent.TimeUnit;
import io.netty.channel.ChannelFuture;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.io.handlers.game.GamePacketHandler;
import java.net.SocketException;
import java.net.SocketAddress;
import java.util.Collections;
import com.hypixel.hytale.server.core.universe.Universe;
import java.util.Iterator;
import java.net.UnknownHostException;
import java.net.InetSocketAddress;
import java.net.InetAddress;
import com.hypixel.hytale.server.core.Constants;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
import com.hypixel.hytale.server.core.io.commands.BindingsCommand;
import com.hypixel.hytale.server.core.io.handlers.game.InventoryPacketHandler;
import com.hypixel.hytale.server.core.event.events.ShutdownEvent;
import com.hypixel.hytale.common.util.CompletableFutureUtil;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import com.hypixel.hytale.common.util.FormatUtil;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.io.transport.QUICTransport;
import com.hypixel.hytale.server.core.io.transport.TCPTransport;
import com.hypixel.hytale.server.core.io.transport.TransportType;
import joptsimple.OptionSpec;
import com.hypixel.hytale.server.core.Options;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.concurrent.CopyOnWriteArrayList;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.io.transport.Transport;
import com.hypixel.hytale.server.core.io.handlers.SubPacketHandler;
import com.hypixel.hytale.server.core.io.handlers.IPacketHandler;
import java.util.function.Function;
import io.netty.channel.Channel;
import java.util.List;
import javax.annotation.Nonnull;
import com.hypixel.hytale.common.util.NetworkUtil;
import com.hypixel.hytale.common.plugin.PluginManifest;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class ServerManager extends JavaPlugin
{
    public static final PluginManifest MANIFEST;
    @Nonnull
    private static final NetworkUtil.AddressType[] NON_PUBLIC_ADDRESS_TYPES;
    private static ServerManager instance;
    @Nonnull
    private final List<Channel> listeners;
    @Nonnull
    private final List<Function<IPacketHandler, SubPacketHandler>> subPacketHandlers;
    @Nullable
    private Transport transport;
    @Nullable
    private CompletableFuture<Void> registerFuture;
    @Nullable
    private CompletableFuture<Void> bootFuture;
    
    public static ServerManager get() {
        return ServerManager.instance;
    }
    
    public ServerManager(@Nonnull final JavaPluginInit init) {
        super(init);
        this.listeners = new CopyOnWriteArrayList<Channel>();
        this.subPacketHandlers = new ObjectArrayList<Function<IPacketHandler, SubPacketHandler>>();
        ServerManager.instance = this;
        if (Options.getOptionSet().has(Options.BARE)) {
            return;
        }
        this.init();
    }
    
    public void init() {
        this.registerFuture = CompletableFutureUtil._catch(CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> {
            final long start = System.nanoTime();
            switch (Options.getOptionSet().valuesOf(Options.TRANSPORT).getFirst()) {
                default: {
                    throw new MatchException(null, null);
                }
                case TCP: {
                    new(com.hypixel.hytale.server.core.io.transport.TCPTransport.class)();
                    new TCPTransport();
                    break;
                }
                case QUIC: {
                    new(com.hypixel.hytale.server.core.io.transport.QUICTransport.class)();
                    new QUICTransport();
                    break;
                }
            }
            final Transport transport;
            this.transport = transport;
            this.getLogger().at(Level.INFO).log("Took %s to setup transport!", FormatUtil.nanosToString(System.nanoTime() - start));
            this.registerFuture = null;
        })));
    }
    
    @Override
    protected void setup() {
        this.getEventRegistry().register((short)(-40), ShutdownEvent.class, event -> this.unbindAllListeners());
        get().registerSubPacketHandlers((Function<IPacketHandler, SubPacketHandler>)InventoryPacketHandler::new);
        this.getCommandRegistry().registerCommand(new BindingsCommand());
    }
    
    @Override
    protected void start() {
        this.bootFuture = CompletableFuture.runAsync(() -> {
            final CompletableFuture<Void> registerFuture = this.registerFuture;
            if (registerFuture != null) {
                registerFuture.getNow(null);
            }
            if (!HytaleServer.get().isShuttingDown()) {
                Label_0194_1: {
                    if (!Options.getOptionSet().has(Options.MIGRATIONS) && !Options.getOptionSet().has(Options.BARE)) {
                        if (Constants.SINGLEPLAYER) {
                            try {
                                final InetAddress[] arr$;
                                final InetAddress[] localhosts = arr$ = InetAddress.getAllByName("localhost");
                                for (final InetAddress localhost : arr$) {
                                    this.bind(new InetSocketAddress(localhost, Options.getOptionSet().valueOf(Options.BIND).getPort()));
                                }
                                break Label_0194_1;
                            }
                            catch (final UnknownHostException e) {
                                throw SneakyThrow.sneakyThrow(e);
                            }
                        }
                        for (final InetSocketAddress address : Options.getOptionSet().valuesOf(Options.BIND)) {
                            this.bind(address);
                        }
                        if (this.listeners.isEmpty()) {
                            throw new IllegalArgumentException("Listeners is empty after starting ServerManager!!");
                        }
                    }
                }
                this.bootFuture = null;
            }
        });
    }
    
    @Override
    protected void shutdown() {
        Universe.get().disconnectAllPLayers();
        this.unbindAllListeners();
        this.transport.shutdown();
        this.transport = null;
        this.getLogger().at(Level.INFO).log("Finished shutting down ServerManager...");
    }
    
    public void unbindAllListeners() {
        for (final Channel channel : this.listeners) {
            this.unbind0(channel);
        }
        this.listeners.clear();
    }
    
    @Nonnull
    public List<Channel> getListeners() {
        return Collections.unmodifiableList((List<? extends Channel>)this.listeners);
    }
    
    public boolean bind(@Nonnull final InetSocketAddress address) {
        if (address.getAddress().isAnyLocalAddress() && this.transport.getType() == TransportType.QUIC) {
            final Channel channelIpv6 = this.bind0(new InetSocketAddress(NetworkUtil.ANY_IPV6_ADDRESS, address.getPort()));
            if (channelIpv6 != null) {
                this.listeners.add(channelIpv6);
            }
            final Channel channelIpv7 = this.bind0(new InetSocketAddress(NetworkUtil.ANY_IPV4_ADDRESS, address.getPort()));
            if (channelIpv7 != null) {
                this.listeners.add(channelIpv7);
            }
            final Channel channelIpv6Localhost = this.bind0(new InetSocketAddress(NetworkUtil.LOOPBACK_IPV6_ADDRESS, address.getPort()));
            if (channelIpv6Localhost != null) {
                this.listeners.add(channelIpv6Localhost);
            }
            return channelIpv7 != null || channelIpv6 != null;
        }
        final Channel channel = this.bind0(address);
        if (channel != null) {
            this.listeners.add(channel);
        }
        return channel != null;
    }
    
    public boolean unbind(@Nonnull final Channel channel) {
        final boolean success = this.unbind0(channel);
        if (success) {
            this.listeners.remove(channel);
        }
        return success;
    }
    
    @Nullable
    public InetSocketAddress getLocalOrPublicAddress() throws SocketException {
        for (final Channel channel : this.listeners) {
            final SocketAddress socketAddress = channel.localAddress();
            if (socketAddress instanceof final InetSocketAddress inetSocketAddress) {
                final InetAddress address = inetSocketAddress.getAddress();
                if (address.isLoopbackAddress()) {
                    return inetSocketAddress;
                }
                if (!address.isAnyLocalAddress()) {
                    return inetSocketAddress;
                }
                final InetAddress anyNonLoopbackAddress = NetworkUtil.getFirstNonLoopbackAddress();
                if (anyNonLoopbackAddress == null) {
                    return null;
                }
                return new InetSocketAddress(anyNonLoopbackAddress, inetSocketAddress.getPort());
            }
        }
        return null;
    }
    
    @Nullable
    public InetSocketAddress getNonLoopbackAddress() throws SocketException {
        for (final Channel channel : this.listeners) {
            final SocketAddress socketAddress = channel.localAddress();
            if (socketAddress instanceof final InetSocketAddress inetSocketAddress) {
                final InetAddress address = inetSocketAddress.getAddress();
                if (address.isLoopbackAddress()) {
                    continue;
                }
                if (!address.isAnyLocalAddress()) {
                    return inetSocketAddress;
                }
                final InetAddress anyNonLoopbackAddress = NetworkUtil.getFirstNonLoopbackAddress();
                if (anyNonLoopbackAddress == null) {
                    return null;
                }
                return new InetSocketAddress(anyNonLoopbackAddress, inetSocketAddress.getPort());
            }
        }
        return null;
    }
    
    @Nullable
    public InetSocketAddress getPublicAddress() throws SocketException {
        for (final Channel channel : this.listeners) {
            final SocketAddress socketAddress = channel.localAddress();
            if (socketAddress instanceof final InetSocketAddress inetSocketAddress) {
                final InetAddress address = inetSocketAddress.getAddress();
                if (address.isLoopbackAddress()) {
                    continue;
                }
                if (address.isSiteLocalAddress()) {
                    continue;
                }
                if (!address.isAnyLocalAddress()) {
                    return inetSocketAddress;
                }
                final InetAddress anyPublicAddress = NetworkUtil.getFirstAddressWithout(ServerManager.NON_PUBLIC_ADDRESS_TYPES);
                if (anyPublicAddress == null) {
                    return null;
                }
                return new InetSocketAddress(anyPublicAddress, inetSocketAddress.getPort());
            }
        }
        return null;
    }
    
    public void waitForBindComplete() {
        final CompletableFuture<Void> future = this.bootFuture;
        if (future != null) {
            future.getNow(null);
        }
    }
    
    public void registerSubPacketHandlers(@Nonnull final Function<IPacketHandler, SubPacketHandler> supplier) {
        this.subPacketHandlers.add(supplier);
    }
    
    public void populateSubPacketHandlers(@Nonnull final GamePacketHandler packetHandler) {
        for (final Function<IPacketHandler, SubPacketHandler> subPacketHandler : this.subPacketHandlers) {
            packetHandler.registerSubPacketHandler(subPacketHandler.apply(packetHandler));
        }
    }
    
    @Nullable
    private Channel bind0(@Nonnull final InetSocketAddress address) {
        final long start = System.nanoTime();
        this.getLogger().at(Level.FINE).log("Binding to %s (%s)", address, this.transport.getType());
        try {
            final ChannelFuture f = this.transport.bind(address).sync();
            if (f.isSuccess()) {
                final Channel channel = f.channel();
                this.getLogger().at(Level.INFO).log("Listening on %s and took %s", channel.localAddress(), FormatUtil.nanosToString(System.nanoTime() - start));
                return channel;
            }
            this.getLogger().at(Level.SEVERE).withCause(new SkipSentryException(f.cause())).log("Could not bind to host %s", address);
        }
        catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Interrupted when attempting to bind to host " + String.valueOf(address), (Throwable)e);
        }
        catch (final Throwable t) {
            this.getLogger().at(Level.SEVERE).withCause(new SkipSentryException(t)).log("Failed to bind to %s", address);
        }
        return null;
    }
    
    private boolean unbind0(@Nonnull final Channel channel) {
        final long start = System.nanoTime();
        this.getLogger().at(Level.FINE).log("Closing listener %s", channel);
        try {
            channel.close().await(1L, TimeUnit.SECONDS);
            this.getLogger().at(Level.INFO).log("Closed listener %s and took %s", channel, FormatUtil.nanosToString(System.nanoTime() - start));
            return true;
        }
        catch (final InterruptedException e) {
            this.getLogger().at(Level.SEVERE).withCause(e).log("Failed to await for listener to close!");
            Thread.currentThread().interrupt();
            return false;
        }
    }
    
    static {
        MANIFEST = PluginManifest.corePlugin(ServerManager.class).build();
        NON_PUBLIC_ADDRESS_TYPES = new NetworkUtil.AddressType[] { NetworkUtil.AddressType.ANY_LOCAL, NetworkUtil.AddressType.LOOPBACK, NetworkUtil.AddressType.SITE_LOCAL, NetworkUtil.AddressType.LINK_LOCAL, NetworkUtil.AddressType.MULTICAST };
    }
}
