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

package com.hypixel.hytale.server.npc.commands;

import com.hypixel.hytale.server.core.command.system.arguments.system.Argument;
import com.hypixel.hytale.server.core.entity.entities.player.pages.CustomUIPage;
import com.hypixel.hytale.server.npc.pages.EntitySpawnPage;
import com.hypixel.hytale.server.core.entity.entities.Player;
import com.hypixel.hytale.server.flock.config.FlockAsset;
import javax.annotation.Nullable;
import com.hypixel.hytale.component.Holder;
import it.unimi.dsi.fastutil.Pair;
import com.hypixel.hytale.protocol.PlayerSkin;
import com.hypixel.hytale.function.consumer.TriConsumer;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.asset.builder.Builder;
import java.util.EnumSet;
import com.hypixel.hytale.math.shape.Box;
import java.util.logging.Level;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.RemoveReason;
import java.util.Collection;
import com.hypixel.hytale.server.core.modules.splitvelocity.VelocityConfig;
import com.hypixel.hytale.server.core.entity.Frozen;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.server.flock.FlockPlugin;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.spawning.SpawnTestResult;
import com.hypixel.hytale.server.core.asset.type.model.config.Model;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.server.core.modules.entity.player.ApplyRandomSkinPersistedComponent;
import com.hypixel.hytale.server.core.modules.entity.player.PlayerSkinComponent;
import com.hypixel.hytale.common.util.RandomUtil;
import com.hypixel.hytale.server.core.cosmetics.CosmeticsModule;
import com.hypixel.hytale.server.spawning.SpawningContext;
import com.hypixel.hytale.server.spawning.ISpawnableWithModel;
import com.hypixel.hytale.server.core.command.system.exceptions.GeneralCommandException;
import com.hypixel.hytale.server.core.Message;
import java.util.concurrent.ThreadLocalRandom;
import java.util.Random;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.npc.role.RoleDebugFlags;
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.command.system.CommandContext;
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
import com.hypixel.hytale.codec.validation.Validator;
import com.hypixel.hytale.codec.validation.Validators;
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgTypes;
import com.hypixel.hytale.server.core.command.system.arguments.types.ArgumentType;
import com.hypixel.hytale.server.core.command.system.arguments.system.FlagArg;
import com.hypixel.hytale.server.core.command.system.arguments.system.OptionalArg;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo;
import com.hypixel.hytale.server.core.command.system.arguments.system.RequiredArg;
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractPlayerCommand;

public class NPCSpawnCommand extends AbstractPlayerCommand
{
    private static final double PLAYER_FOOT_POINT_EPSILON = 0.01;
    @Nonnull
    private final RequiredArg<BuilderInfo> roleArg;
    @Nonnull
    private final OptionalArg<Integer> countArg;
    @Nonnull
    private final OptionalArg<Double> radiusArg;
    @Nonnull
    private final OptionalArg<String> flagsArg;
    @Nonnull
    private final OptionalArg<Double> speedArg;
    @Nonnull
    private final FlagArg nonRandomArg;
    @Nonnull
    private final OptionalArg<String> positionSetArg;
    @Nonnull
    private final OptionalArg<String> posOffsetArg;
    @Nonnull
    private final OptionalArg<String> headRotationArg;
    @Nonnull
    private final OptionalArg<String> bodyRotationArg;
    @Nonnull
    private final FlagArg randomRotationArg;
    @Nonnull
    private final FlagArg facingRotationArg;
    @Nonnull
    private final OptionalArg<String> flockArg;
    @Nonnull
    private final FlagArg testArg;
    @Nonnull
    private final FlagArg spawnOnGroundArg;
    @Nonnull
    private final FlagArg frozenArg;
    @Nonnull
    private final FlagArg randomModelArg;
    @Nonnull
    private final OptionalArg<Float> scaleArg;
    @Nonnull
    private final FlagArg bypassScaleLimitsArg;
    
    public NPCSpawnCommand() {
        super("spawn", "server.commands.npc.spawn.desc");
        this.roleArg = this.withRequiredArg("role", "server.commands.npc.spawn.role.desc", NPCCommand.NPC_ROLE);
        this.countArg = this.withOptionalArg("count", "server.commands.npc.spawn.count.desc", ArgTypes.INTEGER).addValidator((Validator<Integer>)Validators.greaterThan((DataType)0));
        this.radiusArg = this.withOptionalArg("radius", "server.commands.npc.spawn.radius.desc", ArgTypes.DOUBLE).addValidator((Validator<Double>)Validators.greaterThan((DataType)0.0));
        this.flagsArg = this.withOptionalArg("flags", "server.commands.npc.spawn.flags.desc", ArgTypes.STRING);
        this.speedArg = this.withOptionalArg("speed", "server.commands.npc.spawn.speed.desc", ArgTypes.DOUBLE).addValidator((Validator<Double>)Validators.greaterThan((DataType)0.0));
        this.nonRandomArg = this.withFlagArg("nonrandom", "server.commands.npc.spawn.random.desc");
        this.positionSetArg = this.withOptionalArg("position", "server.commands.npc.spawn.position.desc", ArgTypes.STRING);
        this.posOffsetArg = this.withOptionalArg("posOffset", "server.commands.npc.spawn.posOffset.desc", ArgTypes.STRING);
        this.headRotationArg = this.withOptionalArg("headRotation", "server.commands.npc.spawn.headRotation.desc", ArgTypes.STRING);
        this.bodyRotationArg = this.withOptionalArg("bodyRotation", "server.commands.npc.spawn.bodyRotation.desc", ArgTypes.STRING);
        this.randomRotationArg = this.withFlagArg("randomRotation", "server.commands.npc.spawn.randomRotation.desc");
        this.facingRotationArg = this.withFlagArg("facingRotation", "server.commands.npc.spawn.facingRotation.desc");
        this.flockArg = this.withOptionalArg("flock", "server.commands.npc.spawn.flock.desc", ArgTypes.STRING);
        this.testArg = this.withFlagArg("test", "server.commands.npc.spawn.test.desc");
        this.spawnOnGroundArg = this.withFlagArg("spawnOnGround", "server.commands.npc.spawn.spawnOnGround.desc");
        this.frozenArg = this.withFlagArg("frozen", "server.commands.npc.spawn.frozen.desc");
        this.randomModelArg = this.withFlagArg("randomModel", "server.commands.npc.spawn.randomModel.desc");
        this.scaleArg = this.withOptionalArg("scale", "server.commands.npc.spawn.scale.desc", ArgTypes.FLOAT);
        this.bypassScaleLimitsArg = this.withFlagArg("bypassScaleLimits", "server.commands.npc.spawn.bypassScaleLimits.desc");
        this.addUsageVariant(new SpawnPageCommand());
    }
    
    @Override
    protected void execute(@Nonnull final CommandContext context, @Nonnull final Store<EntityStore> store, @Nonnull final Ref<EntityStore> ref, @Nonnull final PlayerRef playerRef, @Nonnull final World world) {
        final NPCPlugin npcPlugin = NPCPlugin.get();
        final BuilderInfo roleInfo = this.roleArg.get(context);
        final int roleIndex = roleInfo.getIndex();
        final HeadRotation headRotationComponent = store.getComponent(ref, HeadRotation.getComponentType());
        assert headRotationComponent != null;
        final Vector3f playerHeadRotation = headRotationComponent.getRotation();
        final TransformComponent transformComponent = store.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        final Vector3d playerPosition = transformComponent.getPosition();
        final BoundingBox boundingBoxComponent = store.getComponent(ref, BoundingBox.getComponentType());
        assert boundingBoxComponent != null;
        final Box playerBoundingBox = boundingBoxComponent.getBoundingBox();
        final int count = this.countArg.provided(context) ? this.countArg.get(context) : 1;
        final double radius = this.radiusArg.provided(context) ? this.radiusArg.get(context) : 8.0;
        final String flagsString = this.flagsArg.provided(context) ? this.flagsArg.get(context) : null;
        final EnumSet<RoleDebugFlags> flags = (flagsString != null) ? RoleDebugFlags.getFlags(flagsString.split(",")) : RoleDebugFlags.getPreset("none");
        final Vector3d velocity = new Vector3d(Vector3d.ZERO);
        if (this.speedArg.provided(context)) {
            PhysicsMath.vectorFromAngles(playerHeadRotation.getYaw(), playerHeadRotation.getPitch(), velocity);
            velocity.setLength(this.speedArg.get(context));
        }
        final Random random = ((Argument<Arg, Boolean>)this.nonRandomArg).get(context) ? new Random(0L) : ThreadLocalRandom.current();
        final Vector3d posOffset = this.posOffsetArg.provided(context) ? this.parseVector3d(context, this.posOffsetArg.get(context)) : null;
        final Vector3f headRotation = this.headRotationArg.provided(context) ? this.parseVector3f(context, this.headRotationArg.get(context)) : null;
        boolean randomRotation = false;
        Vector3f rotation = playerHeadRotation;
        if (this.bodyRotationArg.provided(context)) {
            rotation = this.parseVector3f(context, this.bodyRotationArg.get(context));
        }
        else if (((Argument<Arg, Boolean>)this.randomRotationArg).get(context)) {
            randomRotation = true;
        }
        else if (((Argument<Arg, Boolean>)this.facingRotationArg).get(context)) {
            rotation.setY(rotation.getY() - 3.1415927f);
        }
        final String flockSizeString = this.flockArg.provided(context) ? this.flockArg.get(context) : "1";
        final Integer flockSize = this.parseFlockSize(context, flockSizeString);
        if (flockSize == null) {
            return;
        }
        final Boolean frozen = ((Argument<Arg, Boolean>)this.frozenArg).get(context);
        npcPlugin.forceValidation(roleIndex);
        if (!npcPlugin.testAndValidateRole(roleInfo)) {
            throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.validation_failed"));
        }
        try {
            for (int i = 0; i < count; ++i) {
                final Builder<Role> roleBuilder = npcPlugin.tryGetCachedValidRole(roleIndex);
                if (roleBuilder == null) {
                    throw new IllegalArgumentException("Can't find a matching role builder");
                }
                if (!(roleBuilder instanceof ISpawnableWithModel)) {
                    throw new IllegalArgumentException("Role builder must support ISpawnableWithModel interface");
                }
                final ISpawnableWithModel spawnable = (ISpawnableWithModel)roleBuilder;
                if (!roleBuilder.isSpawnable()) {
                    throw new IllegalArgumentException("Abstract role templates cannot be spawned directly - a variant needs to be created!");
                }
                final SpawningContext spawningContext = new SpawningContext();
                if (!spawningContext.setSpawnable(spawnable)) {
                    throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSetRolebuilder"));
                }
                TriConsumer<NPCEntity, Ref<EntityStore>, Store<EntityStore>> skinApplyingFunction = null;
                Model model;
                if (((Argument<Arg, Boolean>)this.randomModelArg).get(context)) {
                    final PlayerSkin playerSkin = CosmeticsModule.get().generateRandomSkin(RandomUtil.getSecureRandom());
                    model = CosmeticsModule.get().createModel(playerSkin);
                    skinApplyingFunction = ((npcEntity, entityStoreRef, entityStore) -> {
                        entityStore.putComponent(entityStoreRef, PlayerSkinComponent.getComponentType(), new PlayerSkinComponent(playerSkin));
                        entityStore.putComponent(entityStoreRef, ApplyRandomSkinPersistedComponent.getComponentType(), ApplyRandomSkinPersistedComponent.INSTANCE);
                        return;
                    });
                }
                else {
                    model = spawningContext.getModel();
                }
                if (randomRotation) {
                    rotation = new Vector3f(0.0f, (float)(2.0 * random.nextDouble() * 3.141592653589793), 0.0f);
                }
                if (this.scaleArg.provided(context)) {
                    final ModelAsset modelAsset = ModelAsset.getAssetMap().getAsset(model.getModelAssetId());
                    assert modelAsset != null;
                    Float scale = this.scaleArg.get(context);
                    if (!((Argument<Arg, Boolean>)this.bypassScaleLimitsArg).get(context)) {
                        scale = MathUtil.clamp(scale, modelAsset.getMinScale(), modelAsset.getMaxScale());
                    }
                    model = Model.createScaledModel(modelAsset, scale);
                }
                Ref<EntityStore> npcRef;
                NPCEntity npc;
                if (count == 1 && ((Argument<Arg, Boolean>)this.testArg).get(context)) {
                    if (!spawningContext.set(world, playerPosition.x, playerPosition.y, playerPosition.z)) {
                        throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSpawnNotEnoughSpace"));
                    }
                    if (spawnable.canSpawn(spawningContext) != SpawnTestResult.TEST_OK) {
                        throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.cantSpawnNotSuitable"));
                    }
                    final Vector3d spawnPosition = spawningContext.newPosition();
                    if (posOffset != null) {
                        spawnPosition.add(posOffset);
                    }
                    final Pair<Ref<EntityStore>, NPCEntity> npcPair = npcPlugin.spawnEntity(store, roleIndex, spawnPosition, rotation, model, skinApplyingFunction);
                    npcRef = npcPair.first();
                    npc = npcPair.second();
                    if (flockSize > 1) {
                        FlockPlugin.trySpawnFlock(npcRef, npc, store, roleIndex, spawnPosition, rotation, flockSize, skinApplyingFunction);
                    }
                }
                else {
                    Vector3d position;
                    if (this.positionSetArg.provided(context)) {
                        position = this.parseVector3d(context, this.positionSetArg.get(context));
                        if (position == null) {
                            return;
                        }
                        position.y -= model.getBoundingBox().min.y;
                    }
                    else {
                        position = new Vector3d(playerPosition);
                        position.y = Math.floor(position.y + playerBoundingBox.min.y + 0.01) - model.getBoundingBox().min.y;
                    }
                    if (posOffset != null) {
                        position.add(posOffset);
                    }
                    final Pair<Ref<EntityStore>, NPCEntity> npcPair = npcPlugin.spawnEntity(store, roleIndex, position, rotation, model, skinApplyingFunction);
                    npcRef = npcPair.first();
                    npc = npcPair.second();
                    if (flockSize > 1) {
                        FlockPlugin.trySpawnFlock(npcRef, npc, store, roleIndex, position, rotation, flockSize, skinApplyingFunction);
                    }
                }
                final TransformComponent npcTransformComponent = store.getComponent(npcRef, TransformComponent.getComponentType());
                assert npcTransformComponent != null;
                final HeadRotation npcHeadRotationComponent = store.getComponent(npcRef, HeadRotation.getComponentType());
                assert npcHeadRotationComponent != null;
                final UUIDComponent npcUuidComponent = store.getComponent(npcRef, UUIDComponent.getComponentType());
                assert npcUuidComponent != null;
                if (headRotation != null) {
                    npcHeadRotationComponent.getRotation().assign(headRotation);
                    store.ensureComponent(npcRef, Frozen.getComponentType());
                }
                final Vector3d npcPosition = npcTransformComponent.getPosition();
                double x = npcPosition.getX();
                double y = npcPosition.getY();
                double z = npcPosition.getZ();
                if (count > 1) {
                    x += random.nextDouble() * 2.0 * radius - radius;
                    z += random.nextDouble() * 2.0 * radius - radius;
                    y += (((Argument<Arg, Boolean>)this.spawnOnGroundArg).get(context) ? 0.1 : (random.nextDouble() * 2.0 + 5.0));
                }
                else {
                    y += 0.1;
                }
                npcPosition.assign(x, y, z);
                npc.saveLeashInformation(npcPosition, npcTransformComponent.getRotation());
                if (!velocity.equals(Vector3d.ZERO)) {
                    npc.getRole().forceVelocity(velocity, null, false);
                }
                if (frozen) {
                    store.ensureComponent(npcRef, Frozen.getComponentType());
                }
                final EnumSet<RoleDebugFlags> debugFlags = npc.getRoleDebugFlags().clone();
                debugFlags.addAll((Collection<?>)flags);
                if (!debugFlags.isEmpty()) {
                    final Holder<EntityStore> holder = store.removeEntity(npcRef, RemoveReason.UNLOAD);
                    npc.setRoleDebugFlags(debugFlags);
                    store.addEntity(holder, AddReason.LOAD);
                }
                NPCPlugin.get().getLogger().at(Level.INFO).log("%s created with id %s at position %s", npc.getRoleName(), npcUuidComponent.getUuid(), Vector3d.formatShortString(npcPosition));
            }
        }
        catch (final IllegalArgumentException | IllegalStateException | NullPointerException e) {
            NPCPlugin.get().getLogger().at(Level.WARNING).log("Spawn failed: " + e.getMessage());
            throw new GeneralCommandException(Message.translation("server.commands.npc.spawn.failed").param("reason", e.getMessage()));
        }
    }
    
    @Nullable
    private Vector3d parseVector3d(@Nonnull final CommandContext context, @Nonnull final String str) {
        final String[] parts = str.split(",");
        if (parts.length != 3) {
            context.sendMessage(Message.raw("Invalid Vector3d format: must be three comma-separated doubles"));
            return null;
        }
        try {
            return new Vector3d(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]), Double.parseDouble(parts[2]));
        }
        catch (final NumberFormatException e) {
            context.sendMessage(Message.raw("Invalid Vector3d format: " + e.getMessage()));
            return null;
        }
    }
    
    @Nullable
    private Vector3f parseVector3f(@Nonnull final CommandContext context, @Nonnull final String str) {
        final String[] parts = str.split(",");
        if (parts.length != 3) {
            context.sendMessage(Message.raw("Invalid Vector3f format: must be three comma-separated floats"));
            return null;
        }
        try {
            return new Vector3f(Float.parseFloat(parts[0]), Float.parseFloat(parts[1]), Float.parseFloat(parts[2]));
        }
        catch (final NumberFormatException e) {
            context.sendMessage(Message.raw("Invalid Vector3f format: " + e.getMessage()));
            return null;
        }
    }
    
    @Nullable
    private Integer parseFlockSize(@Nonnull final CommandContext context, @Nonnull final String str) {
        try {
            final Integer size = Integer.valueOf(str);
            if (size <= 0) {
                context.sendMessage(Message.raw("Flock size must be greater than 0!"));
                return null;
            }
            return size;
        }
        catch (final NumberFormatException e) {
            final FlockAsset flockDefinition = FlockAsset.getAssetMap().getAsset(str);
            if (flockDefinition == null) {
                context.sendMessage(Message.raw("No such flock asset: " + str));
                return null;
            }
            return flockDefinition.pickFlockSize();
        }
    }
    
    public static class SpawnPageCommand extends AbstractPlayerCommand
    {
        public SpawnPageCommand() {
            super("server.commands.npc.spawn.page.desc");
        }
        
        @Override
        protected void execute(@Nonnull final CommandContext context, @Nonnull final Store<EntityStore> store, @Nonnull final Ref<EntityStore> ref, @Nonnull final PlayerRef playerRef, @Nonnull final World world) {
            final Player playerComponent = store.getComponent(ref, Player.getComponentType());
            assert playerComponent != null;
            final PlayerRef playerRefComponent = store.getComponent(ref, PlayerRef.getComponentType());
            assert playerRefComponent != null;
            playerComponent.getPageManager().openCustomPage(ref, store, new EntitySpawnPage(playerRefComponent));
        }
    }
}
