package zenith.zov.base.rotation.mods;

import java.util.Random;
import kotlin.Pair;
import net.minecraft.util.math.MathHelper;
import zenith.zov.base.rotation.mods.api.RotationMode;
import zenith.zov.base.rotation.mods.config.InterpolationRotationConfig;
import zenith.zov.utility.game.player.rotation.Rotation;
import zenith.zov.utility.game.player.rotation.RotationDelta;
import zenith.zov.utility.math.IntRange;

public class InterpolationRotationMode extends RotationMode {
    private final InterpolationRotationMode.Sigmoid sigmoid = new InterpolationRotationMode.Sigmoid();
    private final InterpolationRotationMode.Bezier bezier = new InterpolationRotationMode.Bezier();
    private final Random random = new Random();

    public Rotation process(InterpolationRotationConfig config, Rotation modelOut, Rotation targetRotation) {
        Pair<Float, Float> pair = this.calculateFactors(
            zenith.getRotationManager().getCurrentRotation(),
            targetRotation,
            config.getHorizontalSpeedSetting(),
            config.getVerticalSpeedSetting(),
            config.getDirectionChangeFactor(),
            config.getMidPoint()
        );
        return modelOut.towardsLinear(targetRotation, (Float)pair.getFirst(), (Float)pair.getSecond());
    }

    public Pair<Float, Float> calculateFactors(
        Rotation currentRotation,
        Rotation targetRotation,
        IntRange horizontalSpeedSetting,
        IntRange verticalSpeedSetting,
        IntRange directionChangeFactor,
        float midpoint
    ) {
        RotationDelta diff = currentRotation.rotationDeltaTo(targetRotation);
        float yawDiff = diff.getDeltaYaw();
        float pitchDiff = diff.getDeltaPitch();
        float directionChange = 0.0F;
        if (targetRotation != null && zenith.getRotationManager().getPreviousRotationTarget() != null) {
            directionChange = zenith.getRotationManager().getPreviousRotationTarget().targetRotation().angleTo(targetRotation);
            directionChange = MathHelper.clamp(directionChange, 0.0F, 1.0F);
            directionChange *= (float)directionChangeFactor.random() / 100.0F;
        }

        float horizontalSpeed = (float)(targetRotation != null ? horizontalSpeedSetting.random() : horizontalSpeedSetting.getStart()) / 100.0F;
        float verticalSpeed = (float)(targetRotation != null ? verticalSpeedSetting.random() : verticalSpeedSetting.getStart()) / 100.0F;
        float horizontalFactor = this.calculateFactor("Yaw", Math.abs(yawDiff), MathHelper.clamp(horizontalSpeed, 0.0F, 1.0F), directionChange, midpoint);
        float verticalFactor = this.calculateFactor("Pitch", Math.abs(pitchDiff), MathHelper.clamp(verticalSpeed, 0.0F, 1.0F), directionChange, midpoint);
        return new Pair(horizontalFactor * Math.abs(yawDiff), verticalFactor * Math.abs(pitchDiff));
    }

    private float calculateFactor(String name, float rotationDifference, float turnSpeed, float directionChange, float midpoint) {
        float t = MathHelper.clamp(rotationDifference / 180.0F, 0.0F, 1.0F);
        float bezierSpeed = this.bezier.transform(0.05F, 1.0F, 1.0F - t);
        float sigmoidSpeed = this.sigmoid.transform(t);
        return t > midpoint ? bezierSpeed * turnSpeed : sigmoidSpeed * MathHelper.clamp(turnSpeed + directionChange, 0.0F, 1.0F);
    }

    private static class Bezier {
        public float transform(float start, float end, float t) {
            return (1.0F - t) * (1.0F - t) * start + 2.0F * (1.0F - t) * t * 1.0F + t * t * end;
        }
    }

    private static class Sigmoid {
        public float transform(float t) {
            return (float)(1.0 / (1.0 + Math.exp(-0.5 * ((double)t - 0.3))));
        }
    }
}
