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

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

import java.util.Arrays;
import com.hypixel.hytale.server.npc.util.NPCPhysicsMath;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import javax.annotation.Nullable;
import javax.annotation.Nonnull;
import com.hypixel.hytale.math.vector.Vector3d;

public class ProbeMoveData
{
    @Nonnull
    public final Vector3d probePosition;
    @Nonnull
    public final Vector3d probeDirection;
    @Nonnull
    public final Vector3d initialPosition;
    @Nonnull
    public final Vector3d targetPosition;
    @Nonnull
    public final Vector3d directionComponentSelector;
    public boolean isAvoidingBlockDamage;
    public boolean isRelaxedMoveConstraints;
    public boolean onGround;
    public boolean isSavingSegments;
    public int segmentCount;
    @Nullable
    public Segment[] segments;
    
    public ProbeMoveData() {
        this.isAvoidingBlockDamage = true;
        this.isRelaxedMoveConstraints = false;
        this.isSavingSegments = false;
        this.segmentCount = 0;
        this.segments = null;
        this.probeDirection = new Vector3d();
        this.probePosition = new Vector3d();
        this.initialPosition = new Vector3d();
        this.targetPosition = new Vector3d();
        this.directionComponentSelector = new Vector3d();
    }
    
    public void setSaveSegments(final boolean saveSegments) {
        this.isSavingSegments = saveSegments;
        if (this.isSavingSegments && this.segments == null) {
            this.segments = new Segment[6];
            for (int i = 0; i < this.segments.length; ++i) {
                this.segments[i] = new Segment();
            }
        }
    }
    
    public boolean isAvoidingBlockDamage() {
        return this.isAvoidingBlockDamage;
    }
    
    public void setAvoidingBlockDamage(final boolean avoid) {
        this.isAvoidingBlockDamage = avoid;
    }
    
    public boolean isRelaxedMoveConstraints() {
        return this.isRelaxedMoveConstraints;
    }
    
    public void setRelaxedMoveConstraints(final boolean relaxedMoveConstraints) {
        this.isRelaxedMoveConstraints = relaxedMoveConstraints;
    }
    
    @Nonnull
    public ProbeMoveData setPosition(@Nonnull final Vector3d position) {
        this.probePosition.assign(position);
        this.initialPosition.assign(position);
        return this;
    }
    
    @Nonnull
    public ProbeMoveData setDirection(@Nonnull final Vector3d direction) {
        this.probeDirection.assign(direction);
        this.targetPosition.assign(this.probePosition).add(this.probeDirection);
        return this;
    }
    
    @Nonnull
    public ProbeMoveData setTargetPosition(@Nonnull final Vector3d targetPosition) {
        this.targetPosition.assign(targetPosition);
        this.probeDirection.assign(targetPosition).subtract(this.probePosition);
        return this;
    }
    
    public boolean canAdvance(@Nonnull final Ref<EntityStore> ref, @Nonnull final MotionController motionController, final double threshold, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final double requiredDistance = threshold * this.probeDirection.length();
        return this.canAdvanceAbs(ref, motionController, requiredDistance, componentAccessor);
    }
    
    public boolean canAdvanceAbs(@Nonnull final Ref<EntityStore> ref, @Nonnull final MotionController motionController, final double requiredDistance, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final double distance = motionController.probeMove(ref, this, componentAccessor);
        return distance >= requiredDistance;
    }
    
    public boolean canMoveTo(@Nonnull final Ref<EntityStore> ref, @Nonnull final MotionController motionController, final double maxDistance, final double maxDistanceY, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (!this.canMoveTo(ref, motionController, maxDistance, componentAccessor)) {
            return false;
        }
        if (!motionController.is2D()) {
            return true;
        }
        final double dy = NPCPhysicsMath.getProjectedDifference(this.targetPosition, this.probePosition, motionController.getComponentSelector());
        return Math.abs(dy) <= maxDistanceY;
    }
    
    public boolean canMoveTo(@Nonnull final Ref<EntityStore> ref, @Nonnull final MotionController motionController, final double maxDistance, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        motionController.probeMove(ref, this, componentAccessor);
        return motionController.waypointDistanceSquared(this.targetPosition, this.probePosition) <= maxDistance * maxDistance;
    }
    
    public boolean computePosition(final double distance, @Nonnull final Vector3d result) {
        if (this.segmentCount < 2) {
            return false;
        }
        if (distance <= 0.0) {
            result.assign(this.segments[0].position);
            return true;
        }
        int index = 1;
        Segment segment = this.segments[0];
        Segment prevSegment = null;
        while (index < this.segmentCount) {
            prevSegment = segment;
            segment = this.segments[index];
            if (segment.distance >= distance) {
                break;
            }
            ++index;
        }
        if (segment.distance <= distance) {
            result.assign(segment.position);
            return true;
        }
        if (segment.type.canInterpolate()) {
            final double lambda = (distance - prevSegment.distance) / (segment.distance - prevSegment.distance);
            NPCPhysicsMath.lerp(prevSegment.position, segment.position, lambda, result);
            return true;
        }
        result.assign(prevSegment.position);
        return true;
    }
    
    public boolean startProbing() {
        if (this.isSavingSegments) {
            this.segmentCount = 0;
        }
        return this.isSavingSegments;
    }
    
    public void addStartSegment(@Nonnull final Vector3d position, final boolean onGround) {
        this.newSegment().initAsStartSegment(position, onGround);
    }
    
    public void addEndSegment(@Nonnull final Vector3d position, final boolean onGround, final double distance) {
        this.newSegment().initAsEndSegment(position, onGround, distance);
    }
    
    public void addBlockedGroundSegment(@Nonnull final Vector3d position, final double distance, @Nonnull final Vector3d normal, final int blockId) {
        this.newSegment().initAsBlockedGroundSegment(position, distance, normal, blockId);
    }
    
    public void addHitGroundSegment(@Nonnull final Vector3d position, final double distance, @Nonnull final Vector3d normal, final int blockId) {
        this.newSegment().initAsHitGroundSegment(position, distance, normal, blockId);
    }
    
    public void addHitWallSegment(@Nonnull final Vector3d position, final boolean onGround, final double distance, @Nonnull final Vector3d normal, final int blockId) {
        this.newSegment().initAsHitWallSegment(position, onGround, distance, normal, blockId);
    }
    
    public void addMoveSegment(@Nonnull final Vector3d position, final boolean onGround, final double distance) {
        this.newSegment().initAsMoveSegment(position, onGround, distance);
    }
    
    public void addClimbSegment(@Nonnull final Vector3d position, final double distance, final int blockId) {
        this.newSegment().initAsClimbSegment(position, distance, blockId);
    }
    
    public void addHitEdgeSegment(@Nonnull final Vector3d position, final double distance) {
        this.newSegment().initAsHitEdgeSegment(position, distance);
    }
    
    public void addDropSegment(@Nonnull final Vector3d position, final double distance) {
        this.newSegment().initAsDropSegment(position, distance);
    }
    
    public void addBlockedDropSegment(@Nonnull final Vector3d position, final double distance) {
        this.newSegment().initAsBlockedDropSegment(position, distance);
    }
    
    public void changeSegmentToBlockedWall() {
        this.segments[this.segmentCount - 1].type = Segment.Type.BLOCKED_WALL;
    }
    
    public void changeSegmentToBlockedEdge() {
        this.segments[this.segmentCount - 1].type = Segment.Type.BLOCKED_EDGE;
    }
    
    public double getLastDistance() {
        return this.segments[this.segmentCount - 1].distance;
    }
    
    protected Segment newSegment() {
        if (this.segmentCount == this.segments.length) {
            this.segments = Arrays.copyOf(this.segments, this.segmentCount + 4);
            for (int i = this.segmentCount; i < this.segments.length; ++i) {
                this.segments[i] = new Segment();
            }
        }
        return this.segments[this.segmentCount++];
    }
    
    public static class Segment
    {
        public Type type;
        public final Vector3d position;
        public final Vector3d normal;
        public double distance;
        public boolean onGround;
        public int blockId;
        
        public Segment() {
            this.position = new Vector3d();
            this.normal = new Vector3d();
        }
        
        public void initAsStartSegment(@Nonnull final Vector3d position, final boolean onGround) {
            this.type = Type.START;
            this.position.assign(position);
            this.normal.assign(Vector3d.ZERO);
            this.distance = 0.0;
            this.onGround = onGround;
            this.blockId = Integer.MIN_VALUE;
        }
        
        public void initAsEndSegment(@Nonnull final Vector3d position, final boolean onGround, final double distance) {
            this.type = Type.END;
            this.position.assign(position);
            this.normal.assign(Vector3d.ZERO);
            this.distance = distance;
            this.onGround = onGround;
            this.blockId = Integer.MIN_VALUE;
        }
        
        public void initAsBlockedGroundSegment(@Nonnull final Vector3d position, final double distance, @Nonnull final Vector3d normal, final int blockId) {
            this.type = Type.BLOCKED_GROUND;
            this.position.assign(position);
            this.normal.assign(normal);
            this.distance = distance;
            this.onGround = true;
            this.blockId = blockId;
        }
        
        public void initAsHitGroundSegment(@Nonnull final Vector3d position, final double distance, @Nonnull final Vector3d normal, final int blockId) {
            this.type = Type.HIT_GROUND;
            this.position.assign(position);
            this.normal.assign(normal);
            this.distance = distance;
            this.onGround = true;
            this.blockId = blockId;
        }
        
        public void initAsHitWallSegment(@Nonnull final Vector3d position, final boolean onGround, final double distance, @Nonnull final Vector3d normal, final int blockId) {
            this.type = Type.HIT_WALL;
            this.position.assign(position);
            this.normal.assign(normal);
            this.distance = distance;
            this.onGround = onGround;
            this.blockId = blockId;
        }
        
        public void initAsClimbSegment(@Nonnull final Vector3d position, final double distance, final int blockId) {
            this.type = Type.CLIMB;
            this.position.assign(position);
            this.normal.assign(Vector3d.ZERO);
            this.distance = distance;
            this.onGround = true;
            this.blockId = blockId;
        }
        
        public void initAsMoveSegment(@Nonnull final Vector3d position, final boolean onGround, final double distance) {
            this.type = Type.MOVE;
            this.position.assign(position);
            this.normal.assign(Vector3d.ZERO);
            this.distance = distance;
            this.onGround = onGround;
            this.blockId = Integer.MIN_VALUE;
        }
        
        public void initAsDropSegment(@Nonnull final Vector3d position, final double distance) {
            this.type = Type.DROP;
            this.position.assign(position);
            this.normal.assign(Vector3d.ZERO);
            this.distance = distance;
            this.onGround = true;
            this.blockId = Integer.MIN_VALUE;
        }
        
        public void initAsBlockedDropSegment(@Nonnull final Vector3d position, final double distance) {
            this.type = Type.BLOCKED_DROP;
            this.position.assign(position);
            this.normal.assign(Vector3d.ZERO);
            this.distance = distance;
            this.onGround = false;
            this.blockId = Integer.MIN_VALUE;
        }
        
        public void initAsHitEdgeSegment(@Nonnull final Vector3d position, final double distance) {
            this.type = Type.HIT_EDGE;
            this.position.assign(position);
            this.normal.assign(Vector3d.ZERO);
            this.distance = distance;
            this.onGround = true;
            this.blockId = Integer.MIN_VALUE;
        }
        
        public enum Type
        {
            START(false, false), 
            HIT_GROUND(false, true), 
            MOVE(false, true), 
            BLOCKED_GROUND(true, true), 
            HIT_WALL(false, true), 
            BLOCKED_WALL(true, true), 
            CLIMB(false, false), 
            HIT_EDGE(false, true), 
            BLOCKED_EDGE(true, true), 
            DROP(false, false), 
            BLOCKED_DROP(true, false), 
            END(false, true);
            
            protected final boolean isBlocked;
            protected final boolean canInterpolate;
            
            public boolean isBlocked() {
                return this.isBlocked;
            }
            
            public boolean canInterpolate() {
                return this.canInterpolate;
            }
            
            private Type(final boolean isBlocked, final boolean canInterpolate) {
                this.isBlocked = isBlocked;
                this.canInterpolate = canInterpolate;
            }
        }
    }
}
