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

package com.hypixel.hytale.server.flock;

import com.hypixel.hytale.component.system.EcsEvent;
import java.util.HashMap;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.server.core.prefab.event.PrefabPasteEvent;
import com.hypixel.hytale.component.system.WorldEventSystem;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.server.core.entity.group.EntityGroup;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.component.ComponentAccessor;
import it.unimi.dsi.fastutil.Pair;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation;
import com.hypixel.hytale.server.core.asset.type.model.config.Model;
import com.hypixel.hytale.math.random.RandomExtra;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.function.consumer.TriConsumer;
import javax.annotation.Nullable;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.ComponentRegistryProxy;
import com.hypixel.hytale.server.npc.systems.PositionCacheSystems;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.server.flock.decisionmaker.conditions.FlockSizeCondition;
import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.Condition;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorFlockLeader;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorInflictedDamage;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderSensorFlockCombatDamage;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderEntityFilterFlock;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockBeacon;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockSetTarget;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockState;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockLeave;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderActionFlockJoin;
import com.hypixel.hytale.server.npc.asset.builder.Builder;
import java.util.function.Supplier;
import com.hypixel.hytale.server.flock.corecomponents.builders.BuilderBodyMotionFlock;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.component.system.ISystem;
import com.hypixel.hytale.assetstore.AssetRegistry;
import com.hypixel.hytale.server.flock.config.RangeSizeFlockAsset;
import java.util.function.Function;
import com.hypixel.hytale.assetstore.codec.AssetCodec;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.server.flock.config.FlockAsset;
import com.hypixel.hytale.server.core.asset.HytaleAssetStore;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ComponentType;
import java.util.UUID;
import java.util.Map;
import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class FlockPlugin extends JavaPlugin
{
    private static FlockPlugin instance;
    private final Int2ObjectConcurrentHashMap<Map<UUID, UUID>> prefabFlockRemappings;
    private ComponentType<EntityStore, Flock> flockComponentType;
    private ComponentType<EntityStore, FlockMembership> flockMembershipComponentType;
    private ComponentType<EntityStore, PersistentFlockData> persistentFlockDataComponentType;
    
    public static FlockPlugin get() {
        return FlockPlugin.instance;
    }
    
    public FlockPlugin(@Nonnull final JavaPluginInit init) {
        super(init);
        this.prefabFlockRemappings = new Int2ObjectConcurrentHashMap<Map<UUID, UUID>>();
    }
    
    public void setup() {
        FlockPlugin.instance = this;
        final ComponentRegistryProxy<EntityStore> entityStoreRegistry = this.getEntityStoreRegistry();
        AssetRegistry.register(((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(FlockAsset.class, new IndexedLookupTableAssetMap(FlockAsset[]::new)).setPath()).setCodec(FlockAsset.CODEC)).setKeyFunction(FlockAsset::getId)).setReplaceOnRemove(RangeSizeFlockAsset::getUnknownFor)).build());
        this.getEntityStoreRegistry().registerSystem(new PrefabPasteEventSystem(this));
        NPCPlugin.get().registerCoreComponentType("Flock", (Supplier<Builder<Object>>)BuilderBodyMotionFlock::new).registerCoreComponentType("JoinFlock", (Supplier<Builder<Object>>)BuilderActionFlockJoin::new).registerCoreComponentType("LeaveFlock", (Supplier<Builder<Object>>)BuilderActionFlockLeave::new).registerCoreComponentType("FlockState", (Supplier<Builder<Object>>)BuilderActionFlockState::new).registerCoreComponentType("FlockTarget", (Supplier<Builder<Object>>)BuilderActionFlockSetTarget::new).registerCoreComponentType("FlockBeacon", (Supplier<Builder<Object>>)BuilderActionFlockBeacon::new).registerCoreComponentType("Flock", (Supplier<Builder<Object>>)BuilderEntityFilterFlock::new).registerCoreComponentType("FlockCombatDamage", (Supplier<Builder<Object>>)BuilderSensorFlockCombatDamage::new).registerCoreComponentType("InflictedDamage", (Supplier<Builder<Object>>)BuilderSensorInflictedDamage::new).registerCoreComponentType("FlockLeader", (Supplier<Builder<Object>>)BuilderSensorFlockLeader::new);
        Condition.CODEC.register("FlockSize", FlockSizeCondition.class, FlockSizeCondition.CODEC);
        this.flockComponentType = entityStoreRegistry.registerComponent(Flock.class, () -> {
            throw new UnsupportedOperationException("Not implemented");
        });
        entityStoreRegistry.registerSystem(new FlockSystems.EntityRemoved(this.flockComponentType));
        entityStoreRegistry.registerSystem(new FlockSystems.Ticking(this.flockComponentType));
        entityStoreRegistry.registerSystem(new FlockSystems.PlayerChangeGameModeEventSystem());
        this.flockMembershipComponentType = entityStoreRegistry.registerComponent(FlockMembership.class, "FlockMembership", FlockMembership.CODEC);
        this.persistentFlockDataComponentType = entityStoreRegistry.registerComponent(PersistentFlockData.class, "FlockData", PersistentFlockData.CODEC);
        entityStoreRegistry.registerSystem(new FlockMembershipSystems.EntityRef(this.flockMembershipComponentType));
        entityStoreRegistry.registerSystem((ISystem<EntityStore>)new FlockMembershipSystems.RefChange(this.flockMembershipComponentType));
        entityStoreRegistry.registerSystem((ISystem<EntityStore>)new PositionCacheSystems.OnFlockJoinSystem(NPCEntity.getComponentType(), this.flockMembershipComponentType));
        entityStoreRegistry.registerSystem((ISystem<EntityStore>)new FlockDeathSystems.EntityDeath());
        entityStoreRegistry.registerSystem((ISystem<EntityStore>)new FlockDeathSystems.PlayerDeath());
        entityStoreRegistry.registerSystem(new FlockMembershipSystems.OnDamageReceived());
        entityStoreRegistry.registerSystem(new FlockMembershipSystems.OnDamageDealt());
        entityStoreRegistry.registerSystem(new FlockMembershipSystems.NPCAddedFromWorldGen());
    }
    
    public void start() {
    }
    
    public void shutdown() {
    }
    
    public ComponentType<EntityStore, Flock> getFlockComponentType() {
        return this.flockComponentType;
    }
    
    public ComponentType<EntityStore, FlockMembership> getFlockMembershipComponentType() {
        return this.flockMembershipComponentType;
    }
    
    public ComponentType<EntityStore, PersistentFlockData> getPersistentFlockDataComponentType() {
        return this.persistentFlockDataComponentType;
    }
    
    @Nonnull
    public UUID getPrefabRemappedFlockReference(final int prefabId, final UUID oldId) {
        return this.prefabFlockRemappings.get(prefabId).computeIfAbsent(oldId, s -> UUID.randomUUID());
    }
    
    @Nullable
    public static Ref<EntityStore> trySpawnFlock(@Nonnull final Ref<EntityStore> npcRef, @Nonnull final NPCEntity npc, @Nonnull final Store<EntityStore> store, final int roleIndex, @Nonnull final Vector3d position, final Vector3f rotation, @Nullable final FlockAsset flockDefinition, final TriConsumer<NPCEntity, Ref<EntityStore>, Store<EntityStore>> postSpawn) {
        final int flockSize = (flockDefinition != null) ? flockDefinition.pickFlockSize() : 1;
        return trySpawnFlock(npcRef, npc, roleIndex, position, rotation, flockSize, flockDefinition, null, postSpawn, store);
    }
    
    @Nullable
    public static Ref<EntityStore> trySpawnFlock(@Nonnull final Ref<EntityStore> npcRef, @Nonnull final NPCEntity npc, @Nonnull final Store<EntityStore> store, final int roleIndex, @Nonnull final Vector3d position, final Vector3f rotation, final int flockSize, final TriConsumer<NPCEntity, Ref<EntityStore>, Store<EntityStore>> postSpawn) {
        return trySpawnFlock(npcRef, npc, roleIndex, position, rotation, flockSize, null, null, postSpawn, store);
    }
    
    @Nullable
    public static Ref<EntityStore> trySpawnFlock(@Nonnull final Ref<EntityStore> npcRef, @Nonnull final NPCEntity npc, final int roleIndex, @Nonnull final Vector3d position, final Vector3f rotation, final int flockSize, final FlockAsset flockDefinition, final TriConsumer<NPCEntity, Holder<EntityStore>, Store<EntityStore>> preAddToWorld, final TriConsumer<NPCEntity, Ref<EntityStore>, Store<EntityStore>> postSpawn, @Nonnull final Store<EntityStore> store) {
        if (flockSize <= 1 || !npcRef.isValid()) {
            return null;
        }
        final Role role = npc.getRole();
        assert role != null;
        final FlockMembership membershipComponent = store.getComponent(npcRef, FlockMembership.getComponentType());
        final Ref<EntityStore> flockReference = (membershipComponent != null) ? membershipComponent.getFlockRef() : createFlock(store, flockDefinition, role.getFlockAllowedRoles());
        if (membershipComponent == null) {
            FlockMembershipSystems.join(npcRef, flockReference, store);
        }
        final BoundingBox boundingBoxComponent = store.getComponent(npcRef, BoundingBox.getComponentType());
        assert boundingBoxComponent != null;
        final TransformComponent transformComponent = store.getComponent(npcRef, TransformComponent.getComponentType());
        assert transformComponent != null;
        final Box boundingBox = boundingBoxComponent.getBoundingBox();
        final Vector3f bodyRotation = transformComponent.getRotation();
        final double x = position.getX();
        final int y = MathUtil.floor(position.getY() + boundingBox.min.y + 1.0E-6);
        final double z = position.getZ();
        final double yaw = bodyRotation.getYaw();
        final boolean randomSpawn = role.isFlockSpawnTypesRandom();
        final int[] roles = role.getFlockSpawnTypes();
        final int rolesSize = (roles == null) ? 0 : roles.length;
        int index = 0;
        int memberRoleIndex = roleIndex;
        for (int i = 1; i < flockSize; ++i) {
            if (rolesSize > 0) {
                if (randomSpawn) {
                    memberRoleIndex = roles[RandomExtra.randomRange(rolesSize)];
                }
                else {
                    memberRoleIndex = roles[index];
                    index = (index + 1) % rolesSize;
                }
            }
            final Pair<Ref<EntityStore>, NPCEntity> memberPair = NPCPlugin.get().spawnEntity(store, memberRoleIndex, position, rotation, null, preAddToWorld, postSpawn);
            final Ref<EntityStore> memberRef = memberPair.first();
            if (memberRef != null) {
                if (memberRef.isValid()) {
                    final BoundingBox memberBoundingBoxComponent = store.getComponent(memberRef, BoundingBox.getComponentType());
                    assert memberBoundingBoxComponent != null;
                    final TransformComponent memberTransformComponent = store.getComponent(memberRef, TransformComponent.getComponentType());
                    assert memberTransformComponent != null;
                    final HeadRotation memberHeadRotationComponent = store.getComponent(memberRef, HeadRotation.getComponentType());
                    assert memberHeadRotationComponent != null;
                    final double offsetY = y - memberBoundingBoxComponent.getBoundingBox().min.y;
                    memberTransformComponent.getRotation().setYaw((float)(yaw + RandomExtra.randomRange(0.7853982f, 0.7853982f)));
                    memberHeadRotationComponent.getRotation().setPitch(0.0f);
                    memberTransformComponent.getPosition().assign(x + RandomExtra.randomRange(-0.5, 0.5), offsetY, z + RandomExtra.randomRange(-0.5, 0.5));
                    FlockMembershipSystems.join(memberRef, flockReference, store);
                }
            }
        }
        return flockReference;
    }
    
    @Nullable
    @Deprecated
    public static Flock getFlock(@Nonnull final ComponentAccessor<EntityStore> componentAccessor, @Nonnull final Ref<EntityStore> reference) {
        final FlockMembership flockMembershipComponent = componentAccessor.getComponent(reference, FlockMembership.getComponentType());
        if (flockMembershipComponent == null) {
            return null;
        }
        final Ref<EntityStore> membershipRef = flockMembershipComponent.getFlockRef();
        if (membershipRef == null || !membershipRef.isValid()) {
            return null;
        }
        return componentAccessor.getComponent(membershipRef, Flock.getComponentType());
    }
    
    @Nonnull
    public static Ref<EntityStore> createFlock(@Nonnull final Store<EntityStore> store, @Nonnull final Role role) {
        return createFlock(store, null, role.getFlockAllowedRoles());
    }
    
    @Nonnull
    public static Ref<EntityStore> createFlock(@Nonnull final Store<EntityStore> store, @Nullable final FlockAsset flockDefinition, @Nonnull final String[] allowedRoles) {
        final Holder<EntityStore> holder = EntityStore.REGISTRY.newHolder();
        holder.addComponent(UUIDComponent.getComponentType(), UUIDComponent.randomUUID());
        holder.addComponent(EntityGroup.getComponentType(), new EntityGroup());
        holder.addComponent(Flock.getComponentType(), new Flock(flockDefinition, allowedRoles));
        final Ref<EntityStore> ref = store.addEntity(holder, AddReason.SPAWN);
        if (ref == null) {
            throw new UnsupportedOperationException("Unable to handle non-spawned flock!");
        }
        return ref;
    }
    
    @Nullable
    public static Ref<EntityStore> getFlockReference(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final FlockMembership flockMembershipComponent = componentAccessor.getComponent(ref, FlockMembership.getComponentType());
        if (flockMembershipComponent == null) {
            return null;
        }
        return flockMembershipComponent.getFlockRef();
    }
    
    public static boolean isFlockMember(@Nonnull final Ref<EntityStore> ref, @Nonnull final Store<EntityStore> store) {
        final FlockMembership flockMembershipComponent = store.getComponent(ref, FlockMembership.getComponentType());
        return flockMembershipComponent != null;
    }
    
    private static final class PrefabPasteEventSystem extends WorldEventSystem<EntityStore, PrefabPasteEvent>
    {
        private final FlockPlugin plugin;
        
        PrefabPasteEventSystem(@Nonnull final FlockPlugin plugin) {
            super(PrefabPasteEvent.class);
            this.plugin = plugin;
        }
        
        @Override
        public void handle(@Nonnull final Store<EntityStore> store, @Nonnull final CommandBuffer<EntityStore> commandBuffer, @Nonnull final PrefabPasteEvent event) {
            if (event.isPasteStart()) {
                this.plugin.prefabFlockRemappings.put(event.getPrefabId(), new HashMap<UUID, UUID>());
            }
            else {
                this.plugin.prefabFlockRemappings.remove(event.getPrefabId());
            }
        }
    }
}
