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

package com.hypixel.hytale.server.core;

import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.HashMap;
import java.nio.file.InvalidPathException;
import joptsimple.ValueConversionException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import com.hypixel.hytale.common.util.PathUtil;
import javax.annotation.Nullable;
import java.nio.file.Paths;
import joptsimple.ValueConverter;
import java.io.IOException;
import java.util.List;
import com.hypixel.hytale.logger.backend.HytaleLoggerBackend;
import com.hypixel.hytale.common.util.java.ManifestUtil;
import java.io.OutputStream;
import javax.annotation.Nonnull;
import joptsimple.OptionSet;
import java.util.UUID;
import com.hypixel.hytale.server.core.universe.world.ValidationOption;
import java.nio.file.Path;
import com.hypixel.hytale.server.core.io.transport.TransportType;
import java.net.InetSocketAddress;
import java.util.logging.Level;
import java.util.Map;
import joptsimple.OptionSpec;
import joptsimple.OptionParser;

public class Options
{
    public static final OptionParser PARSER;
    public static final OptionSpec<Void> HELP;
    public static final OptionSpec<Void> VERSION;
    public static final OptionSpec<Void> BARE;
    public static final OptionSpec<Map.Entry<String, Level>> LOG_LEVELS;
    public static final OptionSpec<InetSocketAddress> BIND;
    public static final OptionSpec<TransportType> TRANSPORT;
    public static final OptionSpec<Void> DISABLE_CPB_BUILD;
    public static final OptionSpec<Path> PREFAB_CACHE_DIRECTORY;
    public static final OptionSpec<Path> ASSET_DIRECTORY;
    public static final OptionSpec<Path> MODS_DIRECTORIES;
    public static final OptionSpec<Void> ACCEPT_EARLY_PLUGINS;
    public static final OptionSpec<Path> EARLY_PLUGIN_DIRECTORIES;
    public static final OptionSpec<Void> VALIDATE_ASSETS;
    public static final OptionSpec<ValidationOption> VALIDATE_PREFABS;
    public static final OptionSpec<Void> VALIDATE_WORLD_GEN;
    public static final OptionSpec<Void> SHUTDOWN_AFTER_VALIDATE;
    public static final OptionSpec<Void> GENERATE_SCHEMA;
    public static final OptionSpec<Path> WORLD_GEN_DIRECTORY;
    public static final OptionSpec<Void> DISABLE_FILE_WATCHER;
    public static final OptionSpec<Void> DISABLE_SENTRY;
    public static final OptionSpec<Void> DISABLE_ASSET_COMPARE;
    public static final OptionSpec<Void> BACKUP;
    public static final OptionSpec<Integer> BACKUP_FREQUENCY_MINUTES;
    public static final OptionSpec<Path> BACKUP_DIRECTORY;
    public static final OptionSpec<Integer> BACKUP_MAX_COUNT;
    public static final OptionSpec<Void> SINGLEPLAYER;
    public static final OptionSpec<String> OWNER_NAME;
    public static final OptionSpec<UUID> OWNER_UUID;
    public static final OptionSpec<Integer> CLIENT_PID;
    public static final OptionSpec<Path> UNIVERSE;
    public static final OptionSpec<Void> EVENT_DEBUG;
    public static final OptionSpec<Boolean> FORCE_NETWORK_FLUSH;
    public static final OptionSpec<Map<String, Path>> MIGRATIONS;
    public static final OptionSpec<String> MIGRATE_WORLDS;
    public static final OptionSpec<String> BOOT_COMMAND;
    public static final String ALLOW_SELF_OP_COMMAND_STRING = "allow-op";
    public static final OptionSpec<Void> ALLOW_SELF_OP_COMMAND;
    public static final OptionSpec<AuthMode> AUTH_MODE;
    public static final OptionSpec<String> SESSION_TOKEN;
    public static final OptionSpec<String> IDENTITY_TOKEN;
    private static OptionSet optionSet;
    
    public static OptionSet getOptionSet() {
        return Options.optionSet;
    }
    
    public static <T> T getOrDefault(final OptionSpec<T> optionSpec, @Nonnull final OptionSet optionSet, final T def) {
        if (!optionSet.has(optionSpec)) {
            return def;
        }
        return optionSet.valueOf(optionSpec);
    }
    
    public static boolean parse(final String[] args) throws IOException {
        Options.optionSet = Options.PARSER.parse(args);
        if (Options.optionSet.has(Options.HELP)) {
            Options.PARSER.printHelpOn(System.out);
            return true;
        }
        if (Options.optionSet.has(Options.VERSION)) {
            final String version = ManifestUtil.getImplementationVersion();
            final String patchline = ManifestUtil.getPatchline();
            final String environment = "release";
            if ("release".equals(patchline)) {
                System.out.println("HytaleServer v" + version + " (" + patchline);
            }
            else {
                System.out.println("HytaleServer v" + version + " (" + patchline + ", " + environment);
            }
            return true;
        }
        final List<?> nonOptionArguments = Options.optionSet.nonOptionArguments();
        if (!nonOptionArguments.isEmpty()) {
            System.err.println("Unknown arguments: " + String.valueOf(nonOptionArguments));
            System.exit(1);
            return true;
        }
        if (Options.optionSet.has(Options.LOG_LEVELS)) {
            HytaleLoggerBackend.loadLevels(Options.optionSet.valuesOf(Options.LOG_LEVELS));
        }
        else if (Options.optionSet.has(Options.SHUTDOWN_AFTER_VALIDATE)) {
            HytaleLoggerBackend.loadLevels(List.of(Map.entry("", Level.WARNING)));
        }
        return false;
    }
    
    static {
        PARSER = new OptionParser();
        HELP = Options.PARSER.accepts("help", "Print's this message.").forHelp();
        VERSION = Options.PARSER.accepts("version", "Prints version information.");
        BARE = Options.PARSER.accepts("bare", "Runs the server bare. For example without loading worlds, binding to ports or creating directories. (Note: Plugins will still be loaded which may not respect this flag)");
        LOG_LEVELS = Options.PARSER.accepts("log", "Sets the logger level.").withRequiredArg().withValuesSeparatedBy(',').withValuesConvertedBy((ValueConverter<Object>)new LevelValueConverter());
        BIND = Options.PARSER.acceptsAll(List.of("b", "bind"), "Port to listen on").withRequiredArg().withValuesSeparatedBy(',').withValuesConvertedBy((ValueConverter<InetSocketAddress>)new SocketAddressValueConverter()).defaultsTo(new InetSocketAddress(5520), new InetSocketAddress[0]);
        TRANSPORT = Options.PARSER.acceptsAll(List.of("t", "transport"), "Transport type").withRequiredArg().ofType(TransportType.class).defaultsTo(TransportType.QUIC, new TransportType[0]);
        DISABLE_CPB_BUILD = Options.PARSER.accepts("disable-cpb-build", "Disables building of compact prefab buffers");
        PREFAB_CACHE_DIRECTORY = Options.PARSER.accepts("prefab-cache", "Prefab cache directory for immutable assets").withRequiredArg().withValuesConvertedBy((ValueConverter<Object>)new PathConverter(PathConverter.PathType.ANY));
        ASSET_DIRECTORY = Options.PARSER.acceptsAll(List.of("assets"), "Asset directory").withRequiredArg().withValuesConvertedBy((ValueConverter<Path>)new PathConverter(PathConverter.PathType.DIR_OR_ZIP)).defaultsTo(Paths.get("../HytaleAssets", new String[0]), new Path[0]);
        MODS_DIRECTORIES = Options.PARSER.acceptsAll(List.of("mods"), "Additional mods directories").withRequiredArg().withValuesSeparatedBy(',').withValuesConvertedBy((ValueConverter<Object>)new PathConverter(PathConverter.PathType.DIR));
        ACCEPT_EARLY_PLUGINS = Options.PARSER.accepts("accept-early-plugins", "You acknowledge that loading early plugins is unsupported and may cause stability issues.");
        EARLY_PLUGIN_DIRECTORIES = Options.PARSER.accepts("early-plugins", "Additional early plugin directories to load from").withRequiredArg().withValuesSeparatedBy(',').withValuesConvertedBy((ValueConverter<Object>)new PathConverter(PathConverter.PathType.DIR));
        VALIDATE_ASSETS = Options.PARSER.accepts("validate-assets", "Causes the server to exit with an error code if any assets are invalid.");
        VALIDATE_PREFABS = Options.PARSER.accepts("validate-prefabs", "Causes the server to exit with an error code if any prefabs are invalid.").withOptionalArg().withValuesSeparatedBy(',').ofType(ValidationOption.class);
        VALIDATE_WORLD_GEN = Options.PARSER.accepts("validate-world-gen", "Causes the server to exit with an error code if default world gen is invalid.");
        SHUTDOWN_AFTER_VALIDATE = Options.PARSER.accepts("shutdown-after-validate", "Automatically shutdown the server after asset and/or prefab validation.");
        GENERATE_SCHEMA = Options.PARSER.accepts("generate-schema", "Causes the server generate schema, save it into the assets directory and then exit");
        WORLD_GEN_DIRECTORY = Options.PARSER.accepts("world-gen", "World gen directory").withRequiredArg().withValuesConvertedBy((ValueConverter<Object>)new PathConverter(PathConverter.PathType.DIR));
        DISABLE_FILE_WATCHER = Options.PARSER.accepts("disable-file-watcher");
        DISABLE_SENTRY = Options.PARSER.accepts("disable-sentry");
        DISABLE_ASSET_COMPARE = Options.PARSER.accepts("disable-asset-compare");
        BACKUP = Options.PARSER.accepts("backup");
        BACKUP_FREQUENCY_MINUTES = Options.PARSER.accepts("backup-frequency").withRequiredArg().ofType(Integer.class).defaultsTo(30, new Integer[0]);
        BACKUP_DIRECTORY = Options.PARSER.accepts("backup-dir").requiredIf(Options.BACKUP, (OptionSpec<?>[])new OptionSpec[0]).withRequiredArg().withValuesConvertedBy((ValueConverter<Object>)new PathConverter(PathConverter.PathType.DIR));
        BACKUP_MAX_COUNT = Options.PARSER.accepts("backup-max-count").withRequiredArg().ofType(Integer.class).defaultsTo(5, new Integer[0]);
        SINGLEPLAYER = Options.PARSER.accepts("singleplayer");
        OWNER_NAME = Options.PARSER.accepts("owner-name").withRequiredArg();
        OWNER_UUID = Options.PARSER.accepts("owner-uuid").withRequiredArg().withValuesConvertedBy((ValueConverter<Object>)new UUIDConverter());
        CLIENT_PID = Options.PARSER.accepts("client-pid").withRequiredArg().ofType(Integer.class);
        UNIVERSE = Options.PARSER.accepts("universe").withRequiredArg().withValuesConvertedBy((ValueConverter<Object>)new PathConverter(PathConverter.PathType.DIR));
        EVENT_DEBUG = Options.PARSER.accepts("event-debug");
        FORCE_NETWORK_FLUSH = Options.PARSER.accepts("force-network-flush").withRequiredArg().ofType(Boolean.class).defaultsTo(true, new Boolean[0]);
        MIGRATIONS = Options.PARSER.accepts("migrations", "The migrations to run").withRequiredArg().withValuesConvertedBy((ValueConverter<Object>)new StringToPathMapConverter());
        MIGRATE_WORLDS = Options.PARSER.accepts("migrate-worlds", "Worlds to migrate").availableIf(Options.MIGRATIONS, (OptionSpec<?>[])new OptionSpec[0]).withRequiredArg().withValuesSeparatedBy(',');
        BOOT_COMMAND = Options.PARSER.accepts("boot-command", "Runs command on boot. If multiple commands are provided they are executed synchronously in order.").withRequiredArg().withValuesSeparatedBy(',');
        ALLOW_SELF_OP_COMMAND = Options.PARSER.accepts("allow-op");
        AUTH_MODE = Options.PARSER.accepts("auth-mode", "Authentication mode").withRequiredArg().withValuesConvertedBy((ValueConverter<AuthMode>)new AuthModeConverter()).defaultsTo(AuthMode.AUTHENTICATED, new AuthMode[0]);
        SESSION_TOKEN = Options.PARSER.accepts("session-token", "Session token for Session Service API").withRequiredArg().ofType(String.class);
        IDENTITY_TOKEN = Options.PARSER.accepts("identity-token", "Identity token (JWT)").withRequiredArg().ofType(String.class);
    }
    
    public enum AuthMode
    {
        AUTHENTICATED, 
        OFFLINE, 
        INSECURE;
    }
    
    private static class AuthModeConverter implements ValueConverter<AuthMode>
    {
        @Override
        public AuthMode convert(final String value) {
            return AuthMode.valueOf(value.toUpperCase());
        }
        
        @Override
        public Class<? extends AuthMode> valueType() {
            return AuthMode.class;
        }
        
        @Override
        public String valuePattern() {
            return "authenticated|offline|insecure";
        }
    }
    
    public static class UUIDConverter implements ValueConverter<UUID>
    {
        @Nonnull
        @Override
        public UUID convert(@Nonnull final String s) {
            return UUID.fromString(s);
        }
        
        @Nonnull
        @Override
        public Class<? extends UUID> valueType() {
            return UUID.class;
        }
        
        @Nullable
        @Override
        public String valuePattern() {
            return null;
        }
    }
    
    public static class LevelValueConverter implements ValueConverter<Map.Entry<String, Level>>
    {
        private static final Map.Entry<String, Level> ENTRY;
        
        @Nonnull
        @Override
        public Map.Entry<String, Level> convert(@Nonnull final String value) {
            if (!value.contains(":")) {
                return Map.entry("", Level.parse(value.toUpperCase()));
            }
            final String[] split = value.split(":");
            return Map.entry(split[0], Level.parse(split[1].toUpperCase()));
        }
        
        @Nonnull
        @Override
        public Class<Map.Entry<String, Level>> valueType() {
            return (Class<Map.Entry<String, Level>>)LevelValueConverter.ENTRY.getClass();
        }
        
        @Nullable
        @Override
        public String valuePattern() {
            return null;
        }
        
        static {
            ENTRY = Map.entry("", Level.ALL);
        }
    }
    
    public static class PathConverter implements ValueConverter<Path>
    {
        private final PathType pathType;
        
        public PathConverter(final PathType pathType) {
            this.pathType = pathType;
        }
        
        @Nonnull
        @Override
        public Path convert(@Nonnull final String s) {
            try {
                final Path path = PathUtil.get(s);
                if (Files.exists(path, new LinkOption[0])) {
                    switch (this.pathType.ordinal()) {
                        case 0: {
                            if (!Files.isRegularFile(path, new LinkOption[0])) {
                                throw new ValueConversionException("Path must be a file!");
                            }
                            break;
                        }
                        case 1: {
                            if (!Files.isDirectory(path, new LinkOption[0])) {
                                throw new ValueConversionException("Path must be a directory!");
                            }
                            break;
                        }
                        case 2: {
                            if (!Files.isDirectory(path, new LinkOption[0]) && (!Files.exists(path, new LinkOption[0]) || !path.getFileName().toString().endsWith(".zip"))) {
                                throw new ValueConversionException("Path must be a directory or zip!");
                            }
                            break;
                        }
                    }
                }
                return path;
            }
            catch (final InvalidPathException e) {
                throw new ValueConversionException("Failed to parse '" + s + "' to path!", (Throwable)e);
            }
        }
        
        @Nonnull
        @Override
        public Class<? extends Path> valueType() {
            return Path.class;
        }
        
        @Nullable
        @Override
        public String valuePattern() {
            return null;
        }
        
        public enum PathType
        {
            FILE, 
            DIR, 
            DIR_OR_ZIP, 
            ANY;
        }
    }
    
    public static class SocketAddressValueConverter implements ValueConverter<InetSocketAddress>
    {
        @Nonnull
        @Override
        public InetSocketAddress convert(@Nonnull final String value) {
            if (value.contains(":")) {
                final String[] split = value.split(":");
                return new InetSocketAddress(split[0], Integer.parseInt(split[1]));
            }
            try {
                return new InetSocketAddress(Integer.parseInt(value));
            }
            catch (final NumberFormatException e) {
                return new InetSocketAddress(value, 5520);
            }
        }
        
        @Nonnull
        @Override
        public Class<? extends InetSocketAddress> valueType() {
            return InetSocketAddress.class;
        }
        
        @Nullable
        @Override
        public String valuePattern() {
            return null;
        }
    }
    
    public static class StringToPathMapConverter implements ValueConverter<Map<String, Path>>
    {
        private static final Map<String, Level> MAP;
        
        @Nonnull
        @Override
        public Map<String, Path> convert(@Nonnull final String value) {
            final HashMap<String, Path> map = new HashMap<String, Path>();
            final String[] split2;
            final String[] strings = split2 = value.split(",");
            for (final String string : split2) {
                final String[] split = string.split("=");
                if (split.length == 2) {
                    if (map.containsKey(split[0])) {
                        throw new ValueConversionException("String '" + split[0] + "' has already been specified!");
                    }
                    final Path path = PathUtil.get(split[1]);
                    if (!Files.exists(path, new LinkOption[0])) {
                        throw new ValueConversionException("No file found for '" + split[1] + "'!");
                    }
                    map.put(split[0], path);
                }
            }
            return map;
        }
        
        @Nonnull
        @Override
        public Class<Map<String, Path>> valueType() {
            return (Class<Map<String, Path>>)StringToPathMapConverter.MAP.getClass();
        }
        
        @Nullable
        @Override
        public String valuePattern() {
            return null;
        }
        
        static {
            MAP = new Object2ObjectOpenHashMap<String, Level>();
        }
    }
}
