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

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

import com.hypixel.hytale.server.npc.sensorinfo.IPositionProvider;
import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.ParameterProvider;
import com.hypixel.hytale.server.npc.util.NPCPhysicsMath;
import com.hypixel.hytale.math.random.RandomExtra;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.math.util.MathUtil;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.npc.movement.controllers.MotionController;
import com.hypixel.hytale.server.npc.movement.controllers.MotionControllerWalk;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.npc.movement.Steering;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderBodyMotionBase;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionMaintainDistance;
import com.hypixel.hytale.server.npc.sensorinfo.parameterproviders.DoubleParameterProvider;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForcePursue;
import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForceEvade;
import javax.annotation.Nonnull;
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.server.npc.corecomponents.BodyMotionBase;

public class BodyMotionMaintainDistance extends BodyMotionBase
{
    protected static final ComponentType<EntityStore, TransformComponent> TRANSFORM_COMPONENT_TYPE;
    protected static final float POSITIONING_ANGLE_THRESHOLD = 0.08726646f;
    protected final double[] initialDesiredDistanceRange;
    protected final double moveThreshold;
    @Nonnull
    protected final double[] thresholdDistanceRangeSquared;
    protected final double targetDistanceFactor;
    protected final double relativeForwardsSpeed;
    protected final double relativeBackwardsSpeed;
    protected final double moveTowardsSlowdownThreshold;
    protected final double[] strafingDurationRange;
    protected final double[] strafingFrequencyRange;
    protected final int minRangeProviderSlot;
    protected final int maxRangeProviderSlot;
    protected final int positioningAngleProviderSlot;
    protected final double[] desiredDistanceRange;
    protected double targetDistanceSquared;
    protected boolean approaching;
    protected boolean movingAway;
    protected int strafingDirection;
    protected double strafingDelay;
    protected boolean pauseStrafing;
    protected final SteeringForceEvade flee;
    protected final SteeringForcePursue seek;
    protected final Vector3d targetPosition;
    protected final Vector3d toTarget;
    protected DoubleParameterProvider cachedMinRangeProvider;
    protected DoubleParameterProvider cachedMaxRangeProvider;
    protected DoubleParameterProvider cachedPositioningAngleProvider;
    protected boolean initialised;
    
    public BodyMotionMaintainDistance(@Nonnull final BuilderBodyMotionMaintainDistance builder, @Nonnull final BuilderSupport support) {
        super(builder);
        this.desiredDistanceRange = new double[2];
        this.strafingDirection = 1;
        this.flee = new SteeringForceEvade();
        this.seek = new SteeringForcePursue();
        this.targetPosition = new Vector3d();
        this.toTarget = new Vector3d();
        this.initialDesiredDistanceRange = builder.getDesiredDistanceRange(support);
        this.desiredDistanceRange[0] = this.initialDesiredDistanceRange[0];
        this.desiredDistanceRange[1] = this.initialDesiredDistanceRange[1];
        this.targetDistanceFactor = builder.getTargetDistanceFactor(support);
        this.moveThreshold = builder.getMoveThreshold(support);
        final double min = Math.max(0.0, this.initialDesiredDistanceRange[0] - this.moveThreshold);
        final double max = this.initialDesiredDistanceRange[1] + this.moveThreshold;
        (this.thresholdDistanceRangeSquared = new double[2])[0] = min * min;
        this.thresholdDistanceRangeSquared[1] = max * max;
        this.relativeForwardsSpeed = builder.getRelativeForwardsSpeed(support);
        this.relativeBackwardsSpeed = builder.getRelativeBackwardsSpeed(support);
        this.moveTowardsSlowdownThreshold = builder.getMoveTowardsSlowdownThreshold(support);
        this.strafingDurationRange = builder.getStrafingDurationRange(support);
        this.strafingFrequencyRange = builder.getStrafingFrequencyRange(support);
        this.minRangeProviderSlot = support.getParameterSlot("MinRange");
        this.maxRangeProviderSlot = support.getParameterSlot("MaxRange");
        this.positioningAngleProviderSlot = support.getParameterSlot("PositioningAngle");
    }
    
    @Override
    public boolean computeSteering(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role support, @Nullable final InfoProvider sensorInfo, final double dt, @Nonnull final Steering desiredSteering, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        desiredSteering.clear();
        if (!support.getActiveMotionController().matchesType(MotionControllerWalk.class)) {
            support.setBackingAway(false);
            return false;
        }
        if (!this.initialised) {
            if (sensorInfo != null) {
                ParameterProvider parameterProvider = sensorInfo.getParameterProvider(this.minRangeProviderSlot);
                if (parameterProvider instanceof final DoubleParameterProvider doubleParameterProvider) {
                    this.cachedMinRangeProvider = doubleParameterProvider;
                }
                parameterProvider = sensorInfo.getParameterProvider(this.maxRangeProviderSlot);
                if (parameterProvider instanceof final DoubleParameterProvider doubleParameterProvider2) {
                    this.cachedMaxRangeProvider = doubleParameterProvider2;
                }
                parameterProvider = sensorInfo.getParameterProvider(this.positioningAngleProviderSlot);
                if (parameterProvider instanceof final DoubleParameterProvider doubleParameterProvider3) {
                    this.cachedPositioningAngleProvider = doubleParameterProvider3;
                }
            }
            this.initialised = true;
        }
        boolean recalculateMinThreshold = false;
        boolean forceNewTargetRange = false;
        if (this.cachedMinRangeProvider != null) {
            final double value = this.cachedMinRangeProvider.getDoubleParameter();
            final double before = this.desiredDistanceRange[0];
            if (value != -1.7976931348623157E308) {
                this.desiredDistanceRange[0] = this.cachedMinRangeProvider.getDoubleParameter();
            }
            else {
                this.desiredDistanceRange[0] = this.initialDesiredDistanceRange[0];
            }
            recalculateMinThreshold = true;
            if (before != this.desiredDistanceRange[0]) {
                forceNewTargetRange = true;
            }
        }
        if (this.cachedMaxRangeProvider != null) {
            final double value = this.cachedMaxRangeProvider.getDoubleParameter();
            final double before = this.desiredDistanceRange[1];
            if (value != -1.7976931348623157E308) {
                this.desiredDistanceRange[1] = this.cachedMaxRangeProvider.getDoubleParameter();
            }
            else {
                this.desiredDistanceRange[1] = this.initialDesiredDistanceRange[1];
            }
            final double max = this.desiredDistanceRange[1] + this.moveThreshold;
            this.thresholdDistanceRangeSquared[1] = max * max;
            if (before != this.desiredDistanceRange[1]) {
                forceNewTargetRange = true;
            }
        }
        double positioningAngle = Double.MAX_VALUE;
        if (this.cachedPositioningAngleProvider != null) {
            positioningAngle = this.cachedPositioningAngleProvider.getDoubleParameter();
        }
        final NPCEntity npcComponent = componentAccessor.getComponent(ref, NPCEntity.getComponentType());
        assert npcComponent != null;
        if (this.desiredDistanceRange[0] > this.desiredDistanceRange[1]) {
            NPCPlugin.get().getLogger().at(Level.WARNING).atMostEvery(1, TimeUnit.MINUTES).log("Attempting to set min distance for %s to a value higher than its max distance [min=%d max=%s]", npcComponent.getRoleName(), this.desiredDistanceRange[0], this.desiredDistanceRange[1]);
            this.desiredDistanceRange[0] = this.desiredDistanceRange[1];
            recalculateMinThreshold = true;
        }
        if (recalculateMinThreshold) {
            final double min = Math.max(0.0, this.desiredDistanceRange[0] - this.moveThreshold);
            this.thresholdDistanceRangeSquared[0] = min * min;
        }
        if (sensorInfo == null || !sensorInfo.hasPosition()) {
            return false;
        }
        final TransformComponent transformComponent = componentAccessor.getComponent(ref, BodyMotionMaintainDistance.TRANSFORM_COMPONENT_TYPE);
        assert transformComponent != null;
        final IPositionProvider positionProvider = sensorInfo.getPositionProvider();
        positionProvider.providePosition(this.targetPosition);
        final Vector3d selfPosition = transformComponent.getPosition();
        final double distanceSquared = selfPosition.distanceSquaredTo(this.targetPosition);
        if (forceNewTargetRange) {
            double targetDistance;
            if (distanceSquared > this.thresholdDistanceRangeSquared[1]) {
                targetDistance = MathUtil.lerp(this.desiredDistanceRange[0], this.desiredDistanceRange[1], 1.0 - this.targetDistanceFactor);
            }
            else {
                targetDistance = MathUtil.lerp(this.desiredDistanceRange[0], this.desiredDistanceRange[1], this.targetDistanceFactor);
            }
            this.targetDistanceSquared = targetDistance * targetDistance;
        }
        if (distanceSquared > this.thresholdDistanceRangeSquared[1] || (this.approaching && distanceSquared > this.targetDistanceSquared)) {
            if (!this.approaching) {
                final double targetDistance = MathUtil.lerp(this.desiredDistanceRange[0], this.desiredDistanceRange[1], 1.0 - this.targetDistanceFactor);
                this.targetDistanceSquared = targetDistance * targetDistance;
                this.seek.setDistances(targetDistance + this.moveTowardsSlowdownThreshold, targetDistance);
                this.approaching = true;
                support.setBackingAway(this.movingAway = false);
            }
            this.seek.setPositions(selfPosition, this.targetPosition);
            final MotionController activeMotionController = support.getActiveMotionController();
            this.seek.setComponentSelector(activeMotionController.getComponentSelector());
            this.seek.compute(desiredSteering);
            desiredSteering.scaleTranslation(this.relativeForwardsSpeed);
        }
        else if (distanceSquared < this.thresholdDistanceRangeSquared[0] || (this.movingAway && distanceSquared < this.targetDistanceSquared)) {
            if (!this.movingAway) {
                final double targetDistance = MathUtil.lerp(this.desiredDistanceRange[0], this.desiredDistanceRange[1], this.targetDistanceFactor);
                this.targetDistanceSquared = targetDistance * targetDistance;
                this.movingAway = true;
                this.approaching = false;
                support.setBackingAway(true);
            }
            this.flee.setPositions(selfPosition, this.targetPosition);
            final MotionController activeMotionController = support.getActiveMotionController();
            this.flee.setComponentSelector(activeMotionController.getComponentSelector());
            this.flee.compute(desiredSteering);
            desiredSteering.scaleTranslation(this.relativeBackwardsSpeed);
        }
        else if (this.approaching || this.movingAway) {
            this.approaching = false;
            this.movingAway = false;
        }
        final double x = this.targetPosition.getX() - selfPosition.getX();
        final double z = this.targetPosition.getZ() - selfPosition.getZ();
        float targetYaw = PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(x, z));
        final MotionController motionController = support.getActiveMotionController();
        if (this.strafingDurationRange[1] > 0.0 || positioningAngle != Double.MAX_VALUE) {
            if (positioningAngle == Double.MAX_VALUE) {
                if (!this.tickStrafingDelay(dt)) {
                    if (this.pauseStrafing) {
                        this.strafingDelay = RandomExtra.randomRange(this.strafingDurationRange);
                        this.strafingDirection = (RandomExtra.randomBoolean() ? 1 : -1);
                        this.pauseStrafing = false;
                    }
                    else {
                        this.strafingDelay = RandomExtra.randomRange(this.strafingFrequencyRange);
                        this.pauseStrafing = true;
                    }
                }
            }
            else {
                final Ref<EntityStore> targetRef = positionProvider.getTarget();
                if (targetRef != null) {
                    final TransformComponent targetTransformComponent = componentAccessor.getComponent(targetRef, BodyMotionMaintainDistance.TRANSFORM_COMPONENT_TYPE);
                    assert targetTransformComponent != null;
                    final float selfYaw = NPCPhysicsMath.lookatHeading(selfPosition, this.targetPosition, transformComponent.getRotation().getYaw());
                    final float difference = PhysicsMath.normalizeTurnAngle(targetTransformComponent.getRotation().getYaw() - selfYaw - (float)positioningAngle);
                    if (Math.abs(difference) > 0.08726646f) {
                        this.strafingDirection = ((difference > 0.0f) ? -1 : 1);
                        this.pauseStrafing = false;
                    }
                    else {
                        this.pauseStrafing = true;
                    }
                }
                else {
                    this.pauseStrafing = true;
                }
            }
            if (!this.pauseStrafing) {
                float angle;
                if (!desiredSteering.hasTranslation()) {
                    this.toTarget.add(this.targetPosition).subtract(selfPosition).setY(0.0);
                    this.toTarget.normalize();
                    desiredSteering.setTranslation(this.toTarget);
                    final Vector3d translation = desiredSteering.getTranslation();
                    final double newX = translation.getZ() * this.strafingDirection;
                    final double newZ = translation.getX() * -this.strafingDirection;
                    translation.setX(newX);
                    translation.setZ(newZ);
                    desiredSteering.scaleTranslation(this.relativeForwardsSpeed);
                    angle = this.strafingDirection * 0.7853982f;
                }
                else {
                    angle = this.strafingDirection * (this.movingAway ? -0.7853982f : 0.7853982f);
                    desiredSteering.getTranslation().rotateY(angle);
                }
                support.setBackingAway(true);
                if (!motionController.isObstructed()) {
                    targetYaw += angle;
                }
            }
        }
        motionController.requireDepthProbing();
        desiredSteering.setYaw(targetYaw);
        return false;
    }
    
    protected boolean tickStrafingDelay(final double dt) {
        if (this.strafingDelay > 0.0) {
            this.strafingDelay -= dt;
            return true;
        }
        return false;
    }
    
    @Override
    public void deactivate(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        super.deactivate(ref, role, componentAccessor);
        role.setBackingAway(false);
    }
    
    static {
        TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType();
    }
}
