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

package com.hypixel.hytale.server.npc.corecomponents.entity;

import com.hypixel.hytale.function.predicate.TriPredicate;
import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings;
import com.hypixel.hytale.protocol.GameMode;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.npc.movement.controllers.MotionController;
import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderSensorBase;
import com.hypixel.hytale.server.npc.asset.builder.ComponentContext;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorEntityBase;
import com.hypixel.hytale.server.npc.sensorinfo.EntityPositionProvider;
import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityCollector;
import com.hypixel.hytale.server.npc.util.IEntityByPriorityFilter;
import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityPrioritiser;
import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent;
import com.hypixel.hytale.server.core.entity.entities.Player;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.npc.corecomponents.SensorWithEntityFilters;

public abstract class SensorEntityBase extends SensorWithEntityFilters
{
    private static final HytaleLogger LOGGER;
    protected static final ComponentType<EntityStore, TransformComponent> TRANSFORM_COMPONENT_TYPE;
    @Nullable
    protected static final ComponentType<EntityStore, NPCEntity> NPC_COMPONENT_TYPE;
    protected static final ComponentType<EntityStore, Player> PLAYER_COMPONENT_TYPE;
    protected static final ComponentType<EntityStore, DeathComponent> DEATH_COMPONENT_TYPE;
    protected final double range;
    protected final double minRange;
    protected final boolean useProjectedDistance;
    protected final boolean lockOnTarget;
    protected final boolean autoUnlockTarget;
    protected final boolean onlyLockedTarget;
    protected final int lockedTargetSlot;
    protected final int ignoredTargetSlot;
    protected final ISensorEntityPrioritiser prioritiser;
    protected IEntityByPriorityFilter npcPrioritiser;
    protected IEntityByPriorityFilter playerPrioritiser;
    @Nullable
    protected final ISensorEntityCollector collector;
    protected int ownRole;
    protected final EntityPositionProvider positionProvider;
    
    public SensorEntityBase(@Nonnull final BuilderSensorEntityBase builder, final ISensorEntityPrioritiser prioritiser, @Nonnull final BuilderSupport builderSupport) {
        super(builder, builder.getFilters(builderSupport, prioritiser, ComponentContext.SensorEntity));
        this.positionProvider = new EntityPositionProvider();
        this.range = builder.getRange(builderSupport);
        this.minRange = builder.getMinRange(builderSupport);
        this.lockOnTarget = builder.isLockOnTarget(builderSupport);
        this.autoUnlockTarget = builder.isAutoUnlockTarget(builderSupport);
        this.onlyLockedTarget = builder.isOnlyLockedTarget(builderSupport);
        this.useProjectedDistance = builder.isUseProjectedDistance(builderSupport);
        this.lockedTargetSlot = builder.getLockedTargetSlot(builderSupport);
        this.ignoredTargetSlot = builder.getIgnoredTargetSlot(builderSupport);
        this.prioritiser = prioritiser;
        this.collector = builder.getCollector(builderSupport);
    }
    
    @Override
    public boolean matches(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, final double dt, @Nonnull final Store<EntityStore> store) {
        if (!super.matches(ref, role, dt, store)) {
            this.positionProvider.clear();
            return false;
        }
        final TransformComponent transformComponent = store.getComponent(ref, SensorEntityBase.TRANSFORM_COMPONENT_TYPE);
        assert transformComponent != null;
        final Vector3d position = transformComponent.getPosition();
        this.ownRole = role.getRoleIndex();
        if (this.ignoredTargetSlot == Integer.MIN_VALUE || this.ignoredTargetSlot != this.lockedTargetSlot) {
            final Ref<EntityStore> targetRef = this.filterLockedEntity(ref, position, role, store);
            if (targetRef != null) {
                this.collector.init(ref, role, store);
                if (!this.collector.terminateOnFirstMatch()) {
                    this.findPlayerOrEntity(ref, position, role, store);
                }
                this.collector.cleanup();
                return this.positionProvider.setTarget(targetRef, store) != null;
            }
        }
        if (this.onlyLockedTarget) {
            this.positionProvider.clear();
            return false;
        }
        this.collector.init(ref, role, store);
        final Ref<EntityStore> targetRef = this.findPlayerOrEntity(ref, position, role, store);
        this.collector.cleanup();
        if (targetRef == null) {
            this.positionProvider.clear();
            return false;
        }
        this.positionProvider.setTarget(targetRef, store);
        if (this.lockOnTarget) {
            role.getMarkedEntitySupport().setMarkedEntity(this.lockedTargetSlot, targetRef);
        }
        return true;
    }
    
    @Override
    public void done() {
        this.positionProvider.clear();
    }
    
    @Override
    public InfoProvider getSensorInfo() {
        return this.positionProvider;
    }
    
    @Override
    public void registerWithSupport(@Nonnull final Role role) {
        super.registerWithSupport(role);
        if (this.isGetPlayers()) {
            role.getPositionCache().requirePlayerDistanceSorted(this.range);
        }
        if (this.isGetNPCs()) {
            role.getPositionCache().requireEntityDistanceSorted(this.range);
        }
        this.prioritiser.registerWithSupport(role);
        this.collector.registerWithSupport(role);
    }
    
    @Override
    public void motionControllerChanged(@Nullable final Ref<EntityStore> ref, @Nonnull final NPCEntity npcComponent, final MotionController motionController, @Nullable final ComponentAccessor<EntityStore> componentAccessor) {
        super.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
        this.prioritiser.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
        this.collector.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
    }
    
    @Override
    public void loaded(final Role role) {
        super.loaded(role);
        this.prioritiser.loaded(role);
        this.collector.loaded(role);
    }
    
    @Override
    public void spawned(final Role role) {
        super.spawned(role);
        this.prioritiser.spawned(role);
        this.collector.spawned(role);
    }
    
    @Override
    public void unloaded(final Role role) {
        super.unloaded(role);
        this.prioritiser.unloaded(role);
        this.collector.unloaded(role);
    }
    
    @Override
    public void removed(final Role role) {
        super.removed(role);
        this.prioritiser.removed(role);
        this.collector.removed(role);
    }
    
    @Override
    public void teleported(final Role role, final World from, final World to) {
        super.teleported(role, from, to);
        this.prioritiser.teleported(role, from, to);
        this.collector.teleported(role, from, to);
    }
    
    protected void initialisePrioritiser() {
        this.npcPrioritiser = (this.isGetNPCs() ? this.prioritiser.getNPCPrioritiser() : null);
        this.playerPrioritiser = (this.isGetPlayers() ? this.prioritiser.getPlayerPrioritiser() : null);
    }
    
    protected abstract boolean isGetPlayers();
    
    protected abstract boolean isGetNPCs();
    
    protected boolean isExcludingOwnType() {
        return false;
    }
    
    @Nullable
    protected Ref<EntityStore> filterLockedEntity(@Nonnull final Ref<EntityStore> ref, @Nonnull final Vector3d position, @Nonnull final Role role, @Nonnull final Store<EntityStore> store) {
        final Ref<EntityStore> target = (this.lockedTargetSlot >= 0) ? role.getMarkedEntitySupport().getMarkedEntityRef(this.lockedTargetSlot) : null;
        if (target == null) {
            return null;
        }
        if (this.filterEntityWithRange(ref, target, position, role, store)) {
            return target;
        }
        if (this.autoUnlockTarget) {
            role.getMarkedEntitySupport().clearMarkedEntity(this.lockedTargetSlot);
        }
        return null;
    }
    
    protected boolean filterEntityWithRange(@Nonnull final Ref<EntityStore> ref, @Nonnull final Ref<EntityStore> targetRef, @Nonnull final Vector3d position, @Nonnull final Role role, @Nonnull final Store<EntityStore> store) {
        final Player playerComponent = store.getComponent(targetRef, Player.getComponentType());
        if (playerComponent != null) {
            if (!this.isGetPlayers()) {
                return false;
            }
            final GameMode gameMode = playerComponent.getGameMode();
            if (gameMode == GameMode.Creative) {
                final PlayerSettings playerSettingsComponent = store.getComponent(targetRef, PlayerSettings.getComponentType());
                final boolean allowDetection = playerSettingsComponent != null && playerSettingsComponent.creativeSettings().allowNPCDetection();
                if (!allowDetection) {
                    return false;
                }
            }
        }
        else {
            if (!store.getArchetype(targetRef).contains(SensorEntityBase.NPC_COMPONENT_TYPE)) {
                return false;
            }
            if (!this.isGetNPCs()) {
                return false;
            }
        }
        final TransformComponent targetTransformComponent = store.getComponent(targetRef, SensorEntityBase.TRANSFORM_COMPONENT_TYPE);
        assert targetTransformComponent != null;
        final Vector3d pos = targetTransformComponent.getPosition();
        final double squaredDistance = role.getActiveMotionController().getSquaredDistance(position, pos, this.useProjectedDistance);
        return squaredDistance >= this.minRange * this.minRange && squaredDistance <= this.range * this.range && this.filterEntity(ref, targetRef, role, store);
    }
    
    protected boolean filterEntity(@Nonnull final Ref<EntityStore> ref, @Nonnull final Ref<EntityStore> targetRef, @Nonnull final Role role, @Nonnull final Store<EntityStore> store) {
        if (store.getArchetype(targetRef).contains(SensorEntityBase.DEATH_COMPONENT_TYPE)) {
            return false;
        }
        final NPCEntity npcComponent = store.getComponent(targetRef, SensorEntityBase.NPC_COMPONENT_TYPE);
        return (!this.isExcludingOwnType() || npcComponent == null || this.ownRole != npcComponent.getRoleIndex()) && this.matchesFilters(ref, targetRef, role, store);
    }
    
    protected boolean filterPrioritisedPlayer(@Nonnull final Ref<EntityStore> ref, @Nonnull final Ref<EntityStore> targetRef, @Nonnull final Role role, @Nonnull final Store<EntityStore> store) {
        return this.filterPrioritisedEntity(ref, targetRef, role, store, this.playerPrioritiser);
    }
    
    protected boolean filterPrioritisedNPC(@Nonnull final Ref<EntityStore> ref, @Nonnull final Ref<EntityStore> targetRef, @Nonnull final Role role, @Nonnull final Store<EntityStore> store) {
        return this.filterPrioritisedEntity(ref, targetRef, role, store, this.npcPrioritiser);
    }
    
    protected boolean filterPrioritisedEntity(@Nonnull final Ref<EntityStore> ref, @Nonnull final Ref<EntityStore> targetRef, @Nonnull final Role role, @Nonnull final Store<EntityStore> store, @Nonnull final IEntityByPriorityFilter playerPrioritiser) {
        if (!this.filterEntity(ref, targetRef, role, store)) {
            this.collector.collectNonMatching(targetRef, store);
            return false;
        }
        final boolean match = ((TriPredicate<Ref<EntityStore>, Ref<EntityStore>, Store<EntityStore>>)playerPrioritiser).test(ref, targetRef, store);
        if (match) {
            this.collector.collectMatching(ref, targetRef, store);
        }
        else {
            this.collector.collectNonMatching(targetRef, store);
        }
        return this.collector.terminateOnFirstMatch() && match;
    }
    
    @Nullable
    protected Ref<EntityStore> findPlayerOrEntity(@Nonnull final Ref<EntityStore> ref, @Nonnull final Vector3d position, @Nonnull final Role role, @Nonnull final Store<EntityStore> store) {
        Ref<EntityStore> player = null;
        Ref<EntityStore> npc = null;
        final Ref<EntityStore> ignoredEntity = (this.ignoredTargetSlot >= 0) ? role.getMarkedEntitySupport().getMarkedEntityRef(this.ignoredTargetSlot) : null;
        if (this.isGetPlayers()) {
            this.playerPrioritiser.init(role);
            role.getPositionCache().processPlayersInRange(ref, this.minRange, this.range, this.useProjectedDistance, ignoredEntity, role, (sensorEntityBase, targetRef, role1, ref1) -> sensorEntityBase.filterPrioritisedPlayer(ref1, targetRef, role1, ref1.getStore()), this, ref, store);
            player = this.playerPrioritiser.getHighestPriorityTarget();
            this.playerPrioritiser.cleanup();
        }
        if (this.isGetNPCs()) {
            this.npcPrioritiser.init(role);
            role.getPositionCache().processNPCsInRange(ref, this.minRange, this.range, this.useProjectedDistance, ignoredEntity, role, (sensorEntityBase, targetRef, role1, ref1) -> sensorEntityBase.filterPrioritisedNPC(ref1, targetRef, role1, ref1.getStore()), this, ref, store);
            npc = this.npcPrioritiser.getHighestPriorityTarget();
            this.npcPrioritiser.cleanup();
        }
        Ref<EntityStore> target;
        if (npc == null) {
            target = player;
        }
        else if (player == null) {
            target = npc;
        }
        else {
            target = this.prioritiser.pickTarget(ref, role, position, player, npc, this.useProjectedDistance, store);
        }
        return target;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType();
        NPC_COMPONENT_TYPE = NPCEntity.getComponentType();
        PLAYER_COMPONENT_TYPE = Player.getComponentType();
        DEATH_COMPONENT_TYPE = DeathComponent.getComponentType();
    }
}
