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

package com.hypixel.hytale.server.core;

import com.hypixel.hytale.metrics.JVMMetrics;
import com.hypixel.hytale.codec.Codec;
import java.util.concurrent.Executors;
import io.sentry.Hint;
import io.sentry.SentryEvent;
import io.sentry.IScope;
import com.sun.management.GarbageCollectionNotificationInfo;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.plugin.PluginState;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.logger.backend.HytaleLogManager;
import com.hypixel.hytale.server.core.event.events.ShutdownEvent;
import java.util.Objects;
import java.util.List;
import com.hypixel.hytale.server.core.update.UpdateModule;
import java.util.Deque;
import com.hypixel.hytale.server.core.command.system.CommandSender;
import java.util.Collection;
import java.util.ArrayDeque;
import com.hypixel.hytale.server.core.console.ConsoleSender;
import com.hypixel.hytale.server.core.event.events.BootEvent;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.concurrent.TimeUnit;
import com.hypixel.hytale.server.core.asset.LoadAssetEvent;
import com.hypixel.hytale.common.util.FormatUtil;
import java.io.IOException;
import com.hypixel.hytale.server.core.auth.SessionServiceClient;
import java.util.UUID;
import java.util.Iterator;
import io.sentry.protocol.Contexts;
import com.hypixel.hytale.common.plugin.PluginManifest;
import joptsimple.OptionSet;
import com.hypixel.hytale.common.util.GCUtil;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.asset.AssetRegistryLoader;
import com.hypixel.hytale.math.util.TrigMathUtil;
import com.hypixel.hytale.server.core.io.netty.NettyUtil;
import com.hypixel.hytale.server.core.modules.singleplayer.SingleplayerModule;
import io.sentry.protocol.OperatingSystem;
import io.sentry.Sentry;
import com.hypixel.hytale.common.util.HardwareUtil;
import java.util.Map;
import io.sentry.protocol.User;
import com.hypixel.hytale.assetstore.AssetPack;
import com.hypixel.hytale.server.core.asset.AssetModule;
import com.hypixel.hytale.server.core.plugin.PluginBase;
import com.hypixel.hytale.server.core.universe.Universe;
import java.util.function.Function;
import com.hypixel.hytale.server.core.io.ServerManager;
import java.util.HashMap;
import com.hypixel.hytale.server.core.plugin.PluginClassLoader;
import com.hypixel.hytale.common.util.NetworkUtil;
import com.hypixel.hytale.common.util.java.ManifestUtil;
import io.sentry.SentryOptions;
import com.hypixel.hytale.plugin.early.EarlyPluginLoader;
import com.hypixel.hytale.server.core.auth.ServerAuthManager;
import com.hypixel.hytale.common.thread.HytaleForkJoinThreadFactory;
import com.hypixel.hytale.server.core.universe.datastore.DiskDataStoreProvider;
import com.hypixel.hytale.server.core.universe.datastore.DataStoreProvider;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.util.concurrent.ThreadUtil;
import com.hypixel.hytale.logger.backend.HytaleLoggerBackend;
import io.netty.handler.codec.quic.Quic;
import joptsimple.OptionSpec;
import java.time.Instant;
import com.hypixel.hytale.server.core.command.system.CommandManager;
import com.hypixel.hytale.server.core.plugin.PluginManager;
import com.hypixel.hytale.event.EventBus;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.Semaphore;
import com.hypixel.hytale.logger.HytaleLogger;
import javax.annotation.Nonnull;
import com.hypixel.hytale.metrics.MetricsRegistry;
import java.util.concurrent.ScheduledExecutorService;

public class HytaleServer
{
    public static final int DEFAULT_PORT = 5520;
    public static final ScheduledExecutorService SCHEDULED_EXECUTOR;
    @Nonnull
    public static final MetricsRegistry<HytaleServer> METRICS_REGISTRY;
    private static final HytaleLogger LOGGER;
    private static HytaleServer instance;
    private final Semaphore aliveLock;
    private final AtomicBoolean booting;
    private final AtomicBoolean booted;
    private final AtomicReference<ShutdownReason> shutdown;
    private final EventBus eventBus;
    private final PluginManager pluginManager;
    private final CommandManager commandManager;
    @Nonnull
    private final HytaleServerConfig hytaleServerConfig;
    private final Instant boot;
    private final long bootStart;
    private int pluginsProgress;
    
    public HytaleServer() throws IOException {
        this.aliveLock = new Semaphore(0);
        this.booting = new AtomicBoolean(false);
        this.booted = new AtomicBoolean(false);
        this.shutdown = new AtomicReference<ShutdownReason>();
        this.eventBus = new EventBus(Options.getOptionSet().has(Options.EVENT_DEBUG));
        this.pluginManager = new PluginManager();
        this.commandManager = new CommandManager();
        HytaleServer.instance = this;
        Quic.ensureAvailability();
        HytaleLoggerBackend.setIndent(25);
        ThreadUtil.forceTimeHighResolution();
        ThreadUtil.createKeepAliveThread(this.aliveLock);
        this.boot = Instant.now();
        this.bootStart = System.nanoTime();
        HytaleServer.LOGGER.at(Level.INFO).log("Starting HytaleServer");
        Constants.init();
        DataStoreProvider.CODEC.register("Disk", (Class<?>)DiskDataStoreProvider.class, (C)DiskDataStoreProvider.CODEC);
        HytaleServer.LOGGER.at(Level.INFO).log("Loading config...");
        this.hytaleServerConfig = HytaleServerConfig.load();
        HytaleLoggerBackend.reloadLogLevels();
        System.setProperty("java.util.concurrent.ForkJoinPool.common.threadFactory", HytaleForkJoinThreadFactory.class.getName());
        final OptionSet optionSet = Options.getOptionSet();
        HytaleServer.LOGGER.at(Level.INFO).log("Authentication mode: %s", optionSet.valueOf(Options.AUTH_MODE));
        ServerAuthManager.getInstance().initialize();
        if (EarlyPluginLoader.hasTransformers()) {
            HytaleLogger.getLogger().at(Level.INFO).log("Early plugins loaded!! Disabling Sentry!!");
        }
        else if (!optionSet.has(Options.DISABLE_SENTRY)) {
            HytaleServer.LOGGER.at(Level.INFO).log("Enabling Sentry");
            final SentryOptions options = new SentryOptions();
            options.setDsn("https://6043a13c7b5c45b5c834b6d896fb378e@sentry.hytale.com/4");
            options.setRelease(ManifestUtil.getImplementationVersion());
            options.setDist(ManifestUtil.getImplementationRevisionId());
            options.setEnvironment("release");
            options.setTag("patchline", ManifestUtil.getPatchline());
            options.setServerName(NetworkUtil.getHostName());
            options.setBeforeSend((event, hint) -> {
                final Throwable throwable = event.getThrowable();
                if (PluginClassLoader.isFromThirdPartyPlugin(throwable)) {
                    return null;
                }
                else {
                    final Contexts contexts = event.getContexts();
                    final HashMap<String, String> serverContext = new HashMap<String, String>();
                    serverContext.put("name", this.getServerName());
                    serverContext.put("max-players", (String)this.getConfig().getMaxPlayers());
                    final ServerManager serverManager = ServerManager.get();
                    if (serverManager != null) {
                        serverContext.put("listeners", (String)serverManager.getListeners().stream().map((Function<? super Object, ?>)Object::toString).toList());
                    }
                    contexts.put("server", serverContext);
                    final Universe universe = Universe.get();
                    if (universe != null) {
                        final HashMap<String, String> universeContext = new HashMap<String, String>();
                        universeContext.put("path", universe.getPath().toString());
                        universeContext.put("player-count", (String)universe.getPlayerCount());
                        universeContext.put("worlds", (String)universe.getWorlds().keySet().stream().toList());
                        contexts.put("universe", universeContext);
                    }
                    final HashMap<String, HashMap<String, Object>> pluginsContext = new HashMap<String, HashMap<String, Object>>();
                    for (final PluginBase plugin : this.pluginManager.getPlugins()) {
                        final PluginManifest manifest2 = plugin.getManifest();
                        final HashMap<String, Object> pluginInfo = new HashMap<String, Object>();
                        pluginInfo.put("version", manifest2.getVersion().toString());
                        pluginInfo.put("state", plugin.getState().name());
                        pluginsContext.put(plugin.getIdentifier().toString(), pluginInfo);
                    }
                    contexts.put("plugins", pluginsContext);
                    final AssetModule assetModule = AssetModule.get();
                    if (assetModule != null) {
                        final HashMap<String, HashMap<String, Object>> packsContext = new HashMap<String, HashMap<String, Object>>();
                        for (final AssetPack pack : assetModule.getAssetPacks()) {
                            final HashMap<String, Boolean> packInfo = new HashMap<String, Boolean>();
                            final PluginManifest manifest3 = pack.getManifest();
                            if (manifest3 != null && manifest3.getVersion() != null) {
                                packInfo.put("version", (Boolean)manifest3.getVersion().toString());
                            }
                            packInfo.put("immutable", pack.isImmutable());
                            packsContext.put(pack.getName(), (HashMap<String, Object>)packInfo);
                        }
                        contexts.put("packs", packsContext);
                    }
                    final User user = new User();
                    final HashMap<String, Object> unknown = new HashMap<String, Object>();
                    user.setUnknown(unknown);
                    final UUID hardwareUUID = HardwareUtil.getUUID();
                    if (hardwareUUID != null) {
                        unknown.put("hardware-uuid", hardwareUUID.toString());
                    }
                    final ServerAuthManager authManager = ServerAuthManager.getInstance();
                    unknown.put("auth-mode", authManager.getAuthMode().toString());
                    final SessionServiceClient.GameProfile profile = authManager.getSelectedProfile();
                    if (profile != null) {
                        user.setUsername(profile.username);
                        user.setId(profile.uuid.toString());
                    }
                    user.setIpAddress("{{auto}}");
                    event.setUser(user);
                    return event;
                }
            });
            Sentry.init(options);
            Sentry.configureScope(scope -> {
                final UUID hardwareUUID2 = HardwareUtil.getUUID();
                if (hardwareUUID2 != null) {
                    scope.setContexts("hardware", Map.of("uuid", hardwareUUID2.toString()));
                }
                final OperatingSystem os = new OperatingSystem();
                os.setName(System.getProperty("os.name"));
                os.setVersion(System.getProperty("os.version"));
                scope.getContexts().setOperatingSystem(os);
                scope.setContexts("build", Map.of("version", String.valueOf(ManifestUtil.getImplementationVersion()), "revision-id", String.valueOf(ManifestUtil.getImplementationRevisionId()), "patchline", String.valueOf(ManifestUtil.getPatchline()), "environment", "release"));
                if (Constants.SINGLEPLAYER) {
                    scope.setContexts("singleplayer", Map.of("owner-uuid", String.valueOf(SingleplayerModule.getUuid()), "owner-name", SingleplayerModule.getUsername()));
                }
                return;
            });
            HytaleLogger.getLogger().setSentryClient(Sentry.getCurrentScopes());
        }
        ServerAuthManager.getInstance().checkPendingFatalError();
        NettyUtil.init();
        final float sin = TrigMathUtil.sin(0.0f);
        final float atan2 = TrigMathUtil.atan2(0.0f, 0.0f);
        final Thread shutdownHook = new Thread(() -> {
            if (this.shutdown.getAndSet(ShutdownReason.SIGINT) != null) {
                return;
            }
            else {
                this.shutdown0(ShutdownReason.SIGINT);
                return;
            }
        }, "ShutdownHook");
        shutdownHook.setDaemon(false);
        Runtime.getRuntime().addShutdownHook(shutdownHook);
        AssetRegistryLoader.init();
        for (final PluginManifest manifest : Constants.CORE_PLUGINS) {
            this.pluginManager.registerCorePlugin(manifest);
        }
        GCUtil.register(info -> {
            final Universe universe2 = Universe.get();
            if (universe2 == null) {
                return;
            }
            else {
                for (final World world : universe2.getWorlds().values()) {
                    world.markGCHasRun();
                }
                return;
            }
        });
        this.boot();
    }
    
    @Nonnull
    public EventBus getEventBus() {
        return this.eventBus;
    }
    
    @Nonnull
    public PluginManager getPluginManager() {
        return this.pluginManager;
    }
    
    @Nonnull
    public CommandManager getCommandManager() {
        return this.commandManager;
    }
    
    @Nonnull
    public HytaleServerConfig getConfig() {
        return this.hytaleServerConfig;
    }
    
    private void boot() {
        if (this.booting.getAndSet(true)) {
            return;
        }
        HytaleServer.LOGGER.at(Level.INFO).log("Booting up HytaleServer - Version: " + ManifestUtil.getImplementationVersion() + ", Revision: " + ManifestUtil.getImplementationRevisionId());
        try {
            this.pluginsProgress = 0;
            this.sendSingleplayerProgress();
            if (this.isShuttingDown()) {
                return;
            }
            HytaleServer.LOGGER.at(Level.INFO).log("Setup phase...");
            this.commandManager.registerCommands();
            this.pluginManager.setup();
            ServerAuthManager.getInstance().initializeCredentialStore();
            HytaleServer.LOGGER.at(Level.INFO).log("Setup phase completed! Boot time %s", FormatUtil.nanosToString(System.nanoTime() - this.bootStart));
            if (this.isShuttingDown()) {
                return;
            }
            final LoadAssetEvent loadAssetEvent = get().getEventBus().dispatchFor((Class<? super LoadAssetEvent>)LoadAssetEvent.class).dispatch(new LoadAssetEvent(this.bootStart));
            if (this.isShuttingDown()) {
                return;
            }
            if (loadAssetEvent.isShouldShutdown()) {
                final List<String> reasons = loadAssetEvent.getReasons();
                final String join = String.join(", ", reasons);
                HytaleServer.LOGGER.at(Level.SEVERE).log("Asset validation FAILED with %d reason(s): %s", reasons.size(), join);
                this.shutdownServer(ShutdownReason.VALIDATE_ERROR.withMessage(join));
                return;
            }
            if (Options.getOptionSet().has(Options.SHUTDOWN_AFTER_VALIDATE)) {
                HytaleServer.LOGGER.at(Level.INFO).log("Asset validation passed");
                this.shutdownServer(ShutdownReason.SHUTDOWN);
                return;
            }
            this.pluginsProgress = 0;
            this.sendSingleplayerProgress();
            if (this.isShuttingDown()) {
                return;
            }
            HytaleServer.LOGGER.at(Level.INFO).log("Starting plugin manager...");
            this.pluginManager.start();
            HytaleServer.LOGGER.at(Level.INFO).log("Plugin manager started! Startup time so far: %s", FormatUtil.nanosToString(System.nanoTime() - this.bootStart));
            if (this.isShuttingDown()) {
                return;
            }
            this.sendSingleplayerSignal("-=|Enabled|0");
        }
        catch (final Throwable throwable) {
            HytaleServer.LOGGER.at(Level.SEVERE).withCause(throwable).log("Failed to boot HytaleServer!");
            Throwable t;
            for (t = throwable; t.getCause() != null; t = t.getCause()) {}
            this.shutdownServer(ShutdownReason.CRASH.withMessage("Failed to start server! " + t.getMessage()));
        }
        if (this.hytaleServerConfig.consumeHasChanged()) {
            HytaleServerConfig.save(this.hytaleServerConfig).join();
        }
        HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> {
            try {
                if (this.hytaleServerConfig.consumeHasChanged()) {
                    HytaleServerConfig.save(this.hytaleServerConfig).join();
                }
            }
            catch (final Exception e) {
                HytaleServer.LOGGER.at(Level.SEVERE).withCause(e).log("Failed to save server config!");
            }
            return;
        }, 1L, 1L, TimeUnit.MINUTES);
        HytaleServer.LOGGER.at(Level.INFO).log("Getting Hytale Universe ready...");
        Universe.get().getUniverseReady().join();
        HytaleServer.LOGGER.at(Level.INFO).log("Universe ready!");
        final List<String> tags = new ObjectArrayList<String>();
        if (Constants.SINGLEPLAYER) {
            tags.add("Singleplayer");
        }
        else {
            tags.add("Multiplayer");
        }
        if (Constants.FRESH_UNIVERSE) {
            tags.add("Fresh Universe");
        }
        this.booted.set(true);
        ServerManager.get().waitForBindComplete();
        this.eventBus.dispatch(BootEvent.class);
        final List<String> bootCommands = Options.getOptionSet().valuesOf(Options.BOOT_COMMAND);
        if (!bootCommands.isEmpty()) {
            CommandManager.get().handleCommands(ConsoleSender.INSTANCE, new ArrayDeque<String>(bootCommands)).join();
        }
        final String border = "\u001b[0;32m===============================================================================================";
        HytaleServer.LOGGER.at(Level.INFO).log("\u001b[0;32m===============================================================================================");
        HytaleServer.LOGGER.at(Level.INFO).log("%s         Hytale Server Booted! [%s] took %s", "\u001b[0;32m", String.join(", ", tags), FormatUtil.nanosToString(System.nanoTime() - this.bootStart));
        HytaleServer.LOGGER.at(Level.INFO).log("\u001b[0;32m===============================================================================================");
        final UpdateModule updateModule = UpdateModule.get();
        if (updateModule != null) {
            updateModule.onServerReady();
        }
        final ServerAuthManager authManager = ServerAuthManager.getInstance();
        if (!authManager.isSingleplayer() && authManager.getAuthMode() == ServerAuthManager.AuthMode.NONE) {
            HytaleServer.LOGGER.at(Level.WARNING).log("%sNo server tokens configured. Use /auth login to authenticate.", "\u001b[0;31m");
        }
        this.sendSingleplayerSignal(">> Singleplayer Ready <<");
    }
    
    public void shutdownServer() {
        this.shutdownServer(ShutdownReason.SHUTDOWN);
    }
    
    public void shutdownServer(@Nonnull final ShutdownReason reason) {
        Objects.requireNonNull(reason, "Server shutdown reason can't be null!");
        if (this.shutdown.getAndSet(reason) != null) {
            return;
        }
        if (reason.getMessage() != null) {
            this.sendSingleplayerSignal("-=|Shutdown|" + reason.getMessage());
        }
        final Thread shutdownThread = new Thread(() -> this.shutdown0(reason), "ShutdownThread");
        shutdownThread.setDaemon(false);
        shutdownThread.start();
    }
    
    void shutdown0(@Nonnull final ShutdownReason reason) {
        HytaleServer.LOGGER.at(Level.INFO).log("Shutdown triggered!!!");
        try {
            HytaleServer.LOGGER.at(Level.INFO).log("Shutting down... %d  '%s'", reason.getExitCode(), reason.getMessage());
            this.eventBus.dispatch(ShutdownEvent.class);
            this.pluginManager.shutdown();
            this.commandManager.shutdown();
            this.eventBus.shutdown();
            ServerAuthManager.getInstance().shutdown();
            HytaleServer.LOGGER.at(Level.INFO).log("Saving config...");
            if (this.hytaleServerConfig.consumeHasChanged()) {
                HytaleServerConfig.save(this.hytaleServerConfig).join();
            }
            HytaleServer.LOGGER.at(Level.INFO).log("Shutdown completed!");
        }
        catch (final Throwable t) {
            HytaleServer.LOGGER.at(Level.SEVERE).withCause(t).log("Exception while shutting down:");
        }
        this.aliveLock.release();
        HytaleLogManager.resetFinally();
        HytaleServer.SCHEDULED_EXECUTOR.schedule(() -> {
            HytaleServer.LOGGER.at(Level.SEVERE).log("Forcing shutdown!");
            Runtime.getRuntime().halt(reason.getExitCode());
            return;
        }, 3L, TimeUnit.SECONDS);
        if (reason != ShutdownReason.SIGINT) {
            System.exit(reason.getExitCode());
        }
    }
    
    public void doneSetup(final PluginBase plugin) {
        ++this.pluginsProgress;
        this.sendSingleplayerProgress();
    }
    
    public void doneStart(final PluginBase plugin) {
        ++this.pluginsProgress;
        this.sendSingleplayerProgress();
    }
    
    public void doneStop(final PluginBase plugin) {
        --this.pluginsProgress;
        this.sendSingleplayerProgress();
    }
    
    public void sendSingleplayerProgress() {
        final List<PluginBase> plugins = this.pluginManager.getPlugins();
        if (this.shutdown.get() != null) {
            this.sendSingleplayerSignal("-=|Shutdown Modules|" + MathUtil.round((plugins.size() - this.pluginsProgress) / (double)plugins.size(), 2) * 100.0);
        }
        else if (this.pluginManager.getState() == PluginState.SETUP) {
            this.sendSingleplayerSignal("-=|Setup|" + MathUtil.round(this.pluginsProgress / (double)plugins.size(), 2) * 100.0);
        }
        else if (this.pluginManager.getState() == PluginState.START) {
            this.sendSingleplayerSignal("-=|Starting|" + MathUtil.round(this.pluginsProgress / (double)plugins.size(), 2) * 100.0);
        }
    }
    
    public String getServerName() {
        return this.getConfig().getServerName();
    }
    
    public boolean isBooting() {
        return this.booting.get();
    }
    
    public boolean isBooted() {
        return this.booted.get();
    }
    
    public boolean isShuttingDown() {
        return this.shutdown.get() != null;
    }
    
    @Nonnull
    public Instant getBoot() {
        return this.boot;
    }
    
    public long getBootStart() {
        return this.bootStart;
    }
    
    @Nullable
    public ShutdownReason getShutdownReason() {
        return this.shutdown.get();
    }
    
    private void sendSingleplayerSignal(final String message) {
        if (Constants.SINGLEPLAYER) {
            HytaleLoggerBackend.rawLog(message);
        }
    }
    
    public void reportSingleplayerStatus(final String message) {
        if (Constants.SINGLEPLAYER) {
            HytaleLoggerBackend.rawLog("-=|" + message + "|0");
        }
    }
    
    public void reportSaveProgress(@Nonnull final World world, final int saved, final int total) {
        if (!this.isShuttingDown()) {
            return;
        }
        final double progress = MathUtil.round(saved / (double)total, 2) * 100.0;
        if (Constants.SINGLEPLAYER) {
            this.sendSingleplayerSignal("-=|Saving world " + world.getName() + " chunks|" + progress);
        }
        else if (total < 10 || saved % (total / 10) == 0) {
            world.getLogger().at(Level.INFO).log("Saving chunks: %.0f%%", progress);
        }
    }
    
    public static HytaleServer get() {
        return HytaleServer.instance;
    }
    
    static {
        SCHEDULED_EXECUTOR = Executors.newSingleThreadScheduledExecutor(ThreadUtil.daemon("Scheduler"));
        METRICS_REGISTRY = new MetricsRegistry<HytaleServer>().register("Time", server -> Instant.now(), (Codec<Instant>)Codec.INSTANT).register("Boot", server -> server.boot, (Codec<Instant>)Codec.INSTANT).register("BootStart", server -> server.bootStart, (Codec<Long>)Codec.LONG).register("Booting", server -> server.booting.get(), (Codec<Boolean>)Codec.BOOLEAN).register("ShutdownReason", server -> {
            final ShutdownReason reason = server.shutdown.get();
            return (reason == null) ? null : reason.toString();
        }, (Codec<String>)Codec.STRING).register("PluginManager", HytaleServer::getPluginManager, PluginManager.METRICS_REGISTRY).register("Config", HytaleServer::getConfig, HytaleServerConfig.CODEC).register("JVM", JVMMetrics.METRICS_REGISTRY);
        LOGGER = HytaleLogger.forEnclosingClass();
    }
}
