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

package com.hypixel.hytale.server.core.command.commands.debug.stresstest;

import com.hypixel.hytale.server.core.command.system.arguments.system.Argument;
import java.util.Collections;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.metrics.metric.HistoricMetric;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.ShutdownReason;
import com.hypixel.hytale.common.util.FormatUtil;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import com.hypixel.hytale.logger.HytaleLogger;
import java.net.SocketException;
import java.util.concurrent.atomic.AtomicInteger;
import java.nio.file.StandardOpenOption;
import java.nio.file.OpenOption;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import com.hypixel.hytale.metrics.MetricsRegistry;
import java.nio.file.Paths;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.interaction.InteractionModule;
import com.hypixel.hytale.server.core.entity.InteractionManager;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.server.core.HytaleServer;
import com.hypixel.hytale.math.vector.Transform;
import com.hypixel.hytale.server.core.universe.world.spawn.ISpawnProvider;
import java.io.IOException;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector2d;
import com.hypixel.hytale.common.util.StringUtil;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.command.system.CommandContext;
import com.hypixel.hytale.codec.validation.Validator;
import com.hypixel.hytale.codec.validation.Validators;
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType;
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg;
import com.hypixel.hytale.server.core.command.system.arguments.system.DefaultArg;
import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg;
import java.util.concurrent.ScheduledFuture;
import com.hypixel.hytale.server.core.event.events.player.AddPlayerToWorldEvent;
import com.hypixel.hytale.event.EventRegistration;
import java.nio.file.Path;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.Message;
import java.util.List;
import javax.annotation.Nonnull;
import java.util.concurrent.atomic.AtomicReference;
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncWorldCommand;

public class StressTestStartCommand extends AbstractAsyncWorldCommand
{
    @Nonnull
    protected static final AtomicReference<StressTestState> STATE;
    @Nonnull
    private static final String NAME_PREFIX = "bot-";
    @Nonnull
    public static final List<Bot> BOTS;
    @Nonnull
    private static final Message MESSAGE_COMMANDS_STRESS_TEST_ALREADY_STARTED;
    @Nonnull
    private static final Message MESSAGE_COMMANDS_STRESS_TEST_STARTED;
    @Nullable
    static DumpType DUMP_TYPE;
    @Nullable
    static Path DATE_PATH;
    @Nullable
    static EventRegistration<String, AddPlayerToWorldEvent> EVENT_REGISTRATION;
    @Nullable
    static ScheduledFuture<?> STRESS_TEST_BOT_TASK;
    @Nullable
    static ScheduledFuture<?> STRESS_TEST_DUMP_TASK;
    @Nonnull
    private final OptionalArg<String> nameArg;
    @Nonnull
    private final DefaultArg<Integer> initCountArg;
    @Nonnull
    private final DefaultArg<Double> intervalArg;
    @Nonnull
    private final DefaultArg<DumpType> dumptypeArg;
    @Nonnull
    private final DefaultArg<Double> dumpintervalArg;
    @Nonnull
    private final OptionalArg<Double> thresholdArg;
    @Nonnull
    private final DefaultArg<Double> percentileArg;
    @Nonnull
    private final DefaultArg<Integer> viewRadiusArg;
    @Nonnull
    private final DefaultArg<Double> radiusArg;
    @Nonnull
    private final DefaultArg<Double> yheightArg;
    @Nonnull
    private final OptionalArg<Double> yheightMaxArg;
    @Nonnull
    private final DefaultArg<Double> flySpeedArg;
    @Nonnull
    private final FlagArg shutdownFlag;
    static final /* synthetic */ boolean $assertionsDisabled;
    
    public StressTestStartCommand() {
        super("start", "server.commands.stresstest.start.desc");
        this.nameArg = this.withOptionalArg("name", "server.commands.stresstest.start.name.desc", ArgTypes.STRING);
        this.initCountArg = this.withDefaultArg("initcount", "server.commands.stresstest.start.initcount.desc", ArgTypes.INTEGER, 0, "server.commands.stresstest.start.initcount.default").addValidator((Validator<Integer>)Validators.greaterThanOrEqual((DataType)0));
        this.intervalArg = this.withDefaultArg("interval", "server.commands.stresstest.start.interval.desc", ArgTypes.DOUBLE, 30.0, "server.commands.stresstest.start.interval.default").addValidator((Validator<Double>)Validators.greaterThan((DataType)0.0));
        this.dumptypeArg = this.withDefaultArg("dumptype", "server.commands.stresstest.start.dumptype.desc", ArgTypes.forEnum("server.commands.parsing.argtype.enum.name", DumpType.class), DumpType.INTERVAL, "server.commands.stresstest.start.dumptype.default");
        this.dumpintervalArg = this.withDefaultArg("dumpinterval", "server.commands.stresstest.start.dumpinterval.desc", ArgTypes.DOUBLE, 300.0, "server.commands.stresstest.start.dumpinterval.default").addValidator((Validator<Double>)Validators.greaterThan((DataType)0.0));
        this.thresholdArg = this.withOptionalArg("threshold", "server.commands.stresstest.start.threshold.desc", ArgTypes.DOUBLE).addValidator((Validator<Double>)Validators.greaterThan((DataType)0.0));
        this.percentileArg = this.withDefaultArg("percentile", "server.commands.stresstest.start.percentile.desc", ArgTypes.DOUBLE, 0.95, "server.commands.stresstest.start.percentile.default").addValidator((Validator<Double>)Validators.range(0.0, (DataType)1.0));
        this.viewRadiusArg = this.withDefaultArg("viewradius", "server.commands.stresstest.start.viewradius.desc", ArgTypes.INTEGER, 192, "server.commands.stresstest.start.viewradius.default").addValidator((Validator<Integer>)Validators.greaterThanOrEqual((DataType)32));
        this.radiusArg = this.withDefaultArg("radius", "server.commands.stresstest.start.radius.desc", ArgTypes.DOUBLE, 384.0, "server.commands.stresstest.start.radius.default").addValidator((Validator<Double>)Validators.greaterThan((DataType)0.0));
        this.yheightArg = this.withDefaultArg("yheight", "server.commands.stresstest.start.yheight.desc", ArgTypes.DOUBLE, 125.0, "server.commands.stresstest.start.yheight.default");
        this.yheightMaxArg = this.withOptionalArg("yheightmax", "server.commands.stresstest.start.yheightmax.desc", ArgTypes.DOUBLE);
        this.flySpeedArg = this.withDefaultArg("flySpeed", "server.commands.stresstest.start.flySpeed.desc", ArgTypes.DOUBLE, 8.0, "server.commands.stresstest.start.flySpeed.default").addValidator((Validator<Double>)Validators.greaterThan((DataType)0.0));
        this.shutdownFlag = this.withFlagArg("shutdown", "server.commands.stresstest.start.shutdown.desc");
    }
    
    @Nonnull
    @Override
    protected CompletableFuture<Void> executeAsync(@Nonnull final CommandContext context, @Nonnull final World world) {
        final ISpawnProvider spawnProvider = world.getWorldConfig().getSpawnProvider();
        final Transform spawn = spawnProvider.getSpawnPoints()[0];
        final String name = this.nameArg.provided(context) ? this.nameArg.get(context) : null;
        if (name != null && !StringUtil.isAlphaNumericHyphenUnderscoreString(name)) {
            context.sendMessage(Message.translation("server.commands.stresstest.invalidName").param("name", name));
            return CompletableFuture.completedFuture((Void)null);
        }
        final int viewRadius = this.viewRadiusArg.get(context);
        final double radius = this.radiusArg.get(context);
        final double yheight = this.yheightArg.get(context);
        final double yheightMax = this.yheightMaxArg.provided(context) ? this.yheightMaxArg.get(context) : (yheight + 10.0);
        final double flySpeed = this.flySpeedArg.get(context);
        final BotConfig config = new BotConfig(radius, new Vector2d(yheight, yheightMax), flySpeed, spawn, viewRadius);
        final int initCount = this.initCountArg.get(context);
        final double interval = this.intervalArg.get(context);
        final DumpType dumpType = this.dumptypeArg.get(context);
        final double dumpInterval = this.dumpintervalArg.get(context);
        final int thresholdNanos = this.thresholdArg.provided(context) ? MathUtil.ceil(this.thresholdArg.get(context) * 1000000.0) : world.getTickStepNanos();
        final double percentile = MathUtil.round(this.percentileArg.get(context), 4);
        final boolean shutdown = ((Argument<Arg, Boolean>)this.shutdownFlag).get(context);
        if (!StressTestStartCommand.STATE.compareAndSet(StressTestState.NOT_RUNNING, StressTestState.RUNNING)) {
            context.sendMessage(StressTestStartCommand.MESSAGE_COMMANDS_STRESS_TEST_ALREADY_STARTED);
            return CompletableFuture.completedFuture((Void)null);
        }
        try {
            start(name, world, config, initCount, interval, dumpType, dumpInterval, thresholdNanos, percentile, shutdown);
        }
        catch (final IOException e) {
            throw SneakyThrow.sneakyThrow(e);
        }
        context.sendMessage(StressTestStartCommand.MESSAGE_COMMANDS_STRESS_TEST_STARTED);
        return CompletableFuture.completedFuture((Void)null);
    }
    
    private static void start(@Nullable final String name, @Nonnull final World world, @Nonnull final BotConfig config, final int initCount, final double interval, final DumpType dumpType, final double dumpInterval, final long thresholdNanos, final double percentile, final boolean shutdown) throws IOException {
        StressTestStartCommand.EVENT_REGISTRATION = HytaleServer.get().getEventBus().register(AddPlayerToWorldEvent.class, world.getName(), event -> {
            final Holder<EntityStore> holder = event.getHolder();
            final PlayerRef playerRefComponent = holder.getComponent(PlayerRef.getComponentType());
            if (!StressTestStartCommand.$assertionsDisabled && playerRefComponent == null) {
                throw new AssertionError();
            }
            else {
                if (playerRefComponent.getUsername().startsWith("bot-")) {
                    final InteractionManager manager = holder.getComponent(InteractionModule.get().getInteractionManagerComponent());
                    holder.putComponent(TransformComponent.getComponentType(), new TransformComponent(config.spawn.getPosition(), config.spawn.getRotation()));
                    manager.setHasRemoteClient(false);
                }
                return;
            }
        });
        StressTestStartCommand.DUMP_TYPE = dumpType;
        StressTestStartCommand.DATE_PATH = MetricsRegistry.createDatePath(Paths.get("stress-test-dumps", new String[0]), (String)null, (name != null) ? ("_" + name) : null);
        if (!Files.exists(StressTestStartCommand.DATE_PATH, new LinkOption[0])) {
            Files.createDirectories(StressTestStartCommand.DATE_PATH, (FileAttribute<?>[])new FileAttribute[0]);
        }
        final String percentileDisplay = StringUtil.trimEnd(Double.toString(MathUtil.round(percentile * 100.0, 2)), ".0");
        final Path resultsPath = StressTestStartCommand.DATE_PATH.resolve("results.csv");
        Files.writeString(resultsPath, "avg,min,p25,p50,p75,max,p" + percentileDisplay + ",bots\n", StandardOpenOption.CREATE_NEW);
        final int tickStepNanos = world.getTickStepNanos();
        final AtomicInteger counter = new AtomicInteger();
        for (int i = 0; i < initCount; ++i) {
            try {
                StressTestStartCommand.BOTS.add(new Bot("bot-" + counter.getAndIncrement(), config, tickStepNanos));
            }
            catch (final InterruptedException | SocketException e) {
                Thread.currentThread().interrupt();
                throw SneakyThrow.sneakyThrow(e);
            }
        }
        if (StressTestStartCommand.DUMP_TYPE == DumpType.INTERVAL) {
            final int dumpIntervalMillis = MathUtil.ceil(dumpInterval * 1000.0);
            StressTestStartCommand.STRESS_TEST_DUMP_TASK = HytaleServer.SCHEDULED_EXECUTOR.scheduleAtFixedRate(() -> {
                try {
                    final Path path = MetricsRegistry.createDumpPath(StressTestStartCommand.DATE_PATH, ".dump.json");
                    HytaleServer.METRICS_REGISTRY.dumpToJson(path, HytaleServer.get());
                }
                catch (final IOException e2) {
                    HytaleLogger.getLogger().at(Level.SEVERE).withCause(e2).log("Failed to save dump!");
                }
                return;
            }, dumpIntervalMillis, dumpIntervalMillis, TimeUnit.MILLISECONDS);
        }
        final int intervalMillis = MathUtil.ceil(interval * 1000.0);
        StressTestStartCommand.STRESS_TEST_BOT_TASK = HytaleServer.SCHEDULED_EXECUTOR.scheduleWithFixedDelay(() -> CompletableFuture.runAsync(SneakyThrow.sneakyRunnable(() -> {
            if (StressTestStartCommand.DUMP_TYPE == DumpType.NEW_BOT) {
                final Path path2 = MetricsRegistry.createDumpPath(StressTestStartCommand.DATE_PATH, ".dump.json");
                HytaleServer.METRICS_REGISTRY.dumpToJson(path2, HytaleServer.get());
            }
            final HistoricMetric historicMetric = world.getBufferedTickLengthMetricSet();
            final int periodIndex = 1;
            final double avg = historicMetric.getAverage(periodIndex);
            final long min = historicMetric.calculateMin(periodIndex);
            final long max = historicMetric.calculateMax(periodIndex);
            final long[] values = historicMetric.getValues(1);
            Arrays.sort(values);
            final double p25 = MathUtil.percentile(values, 0.25);
            final double p26 = MathUtil.percentile(values, 0.5);
            final double p27 = MathUtil.percentile(values, 0.75);
            final int bots = StressTestStartCommand.BOTS.size();
            final double p28 = MathUtil.percentile(values, percentile);
            Files.writeString(resultsPath, avg + "," + min + "," + p25 + "," + p26 + "," + p27 + "," + max + "," + p28 + "," + bots, StandardOpenOption.APPEND);
            HytaleLogger.getLogger().at(Level.INFO).log("avg: %s, min: %s, p25: %s, p50: %s, p75: %s, max: %s, p%s: %s, bots: %s", FormatUtil.timeUnitToString(MathUtil.ceil(avg), TimeUnit.NANOSECONDS), FormatUtil.timeUnitToString(min, TimeUnit.NANOSECONDS), FormatUtil.timeUnitToString(MathUtil.ceil(p25), TimeUnit.NANOSECONDS), FormatUtil.timeUnitToString(MathUtil.ceil(p26), TimeUnit.NANOSECONDS), FormatUtil.timeUnitToString(MathUtil.ceil(p27), TimeUnit.NANOSECONDS), FormatUtil.timeUnitToString(max, TimeUnit.NANOSECONDS), percentileDisplay, FormatUtil.timeUnitToString(MathUtil.ceil(p28), TimeUnit.NANOSECONDS), bots);
            if (p28 > thresholdNanos) {
                HytaleLogger.getLogger().at(Level.INFO).log("Stopped stress test due to p%s > threshold: %s > %s", percentileDisplay, FormatUtil.timeUnitToString(MathUtil.ceil(p28), TimeUnit.NANOSECONDS), FormatUtil.timeUnitToString(thresholdNanos, TimeUnit.NANOSECONDS));
                if (StressTestStartCommand.STATE.compareAndSet(StressTestState.RUNNING, StressTestState.STOPPING)) {
                    stop();
                }
                if (shutdown) {
                    HytaleServer.get().shutdownServer(ShutdownReason.SHUTDOWN.withMessage("Stress test finished!"));
                }
            }
            else {
                StressTestStartCommand.BOTS.add(new Bot("bot-" + counter.getAndIncrement(), config, tickStepNanos));
            }
        })), intervalMillis, intervalMillis, TimeUnit.MILLISECONDS);
        HytaleLogger.getLogger().at(Level.INFO).log("Started stress test! Bot add interval %s with threshold %s for p%s" + (shutdown ? " and will shutdown when finished" : ""), (Object)FormatUtil.timeUnitToString(intervalMillis, TimeUnit.MILLISECONDS), (Object)FormatUtil.timeUnitToString(thresholdNanos, TimeUnit.NANOSECONDS), (Object)percentileDisplay);
    }
    
    static void stop() {
        Label_0047: {
            if (StressTestStartCommand.DUMP_TYPE != DumpType.INTERVAL) {
                if (StressTestStartCommand.DUMP_TYPE != DumpType.FINISH) {
                    break Label_0047;
                }
            }
            try {
                final Path path = MetricsRegistry.createDumpPath(StressTestStartCommand.DATE_PATH, ".dump.json");
                HytaleServer.METRICS_REGISTRY.dumpToJson(path, HytaleServer.get());
            }
            catch (final IOException e) {
                throw SneakyThrow.sneakyThrow(e);
            }
        }
        if (StressTestStartCommand.STRESS_TEST_BOT_TASK != null) {
            StressTestStartCommand.STRESS_TEST_BOT_TASK.cancel(false);
            StressTestStartCommand.STRESS_TEST_BOT_TASK = null;
        }
        if (StressTestStartCommand.STRESS_TEST_DUMP_TASK != null) {
            StressTestStartCommand.STRESS_TEST_DUMP_TASK.cancel(false);
            StressTestStartCommand.STRESS_TEST_DUMP_TASK = null;
        }
        StressTestStartCommand.BOTS.removeIf(bot -> {
            bot.shutdown();
            return true;
        });
        if (StressTestStartCommand.EVENT_REGISTRATION != null) {
            StressTestStartCommand.EVENT_REGISTRATION.unregister();
            StressTestStartCommand.EVENT_REGISTRATION = null;
        }
        StressTestStartCommand.DATE_PATH = null;
        StressTestStartCommand.DUMP_TYPE = null;
        StressTestStartCommand.STATE.compareAndSet(StressTestState.STOPPING, StressTestState.NOT_RUNNING);
        HytaleLogger.getLogger().at(Level.INFO).log("Stopped stress test!");
    }
    
    static {
        STATE = new AtomicReference<StressTestState>(StressTestState.NOT_RUNNING);
        BOTS = Collections.synchronizedList(new ObjectArrayList<Bot>());
        MESSAGE_COMMANDS_STRESS_TEST_ALREADY_STARTED = Message.translation("server.commands.stresstest.alreadyStarted");
        MESSAGE_COMMANDS_STRESS_TEST_STARTED = Message.translation("server.commands.stresstest.started");
    }
    
    enum StressTestState
    {
        NOT_RUNNING, 
        RUNNING, 
        STOPPING;
    }
    
    enum DumpType
    {
        NEW_BOT, 
        INTERVAL, 
        FINISH, 
        NEVER;
    }
}
