using System;
using SuperStarTrek.Space;

namespace SuperStarTrek.Utils
{
    internal class DirectionAndDistance
    {
        private readonly float _fromX;
        private readonly float _fromY;

        private DirectionAndDistance(float fromX, float fromY)
        {
            _fromX = fromX;
            _fromY = fromY;
        }

        internal static DirectionAndDistance From(Coordinates coordinates) => From(coordinates.X, coordinates.Y);

        internal static DirectionAndDistance From(float x, float y) => new DirectionAndDistance(x, y);

        internal (float Direction, float Distance) To(Coordinates coordinates) => To(coordinates.X, coordinates.Y);

        internal (float Direction, float Distance) To(float x, float y)
        {
            var deltaX = x - _fromX;
            var deltaY = y - _fromY;

            return (GetDirection(deltaX, deltaY), GetDistance(deltaX, deltaY));
        }

        // The algorithm here is mathematically equivalent to the following code in the original,
        // where X is deltaY and A is deltaX
        //     8220 X=X-A:A=C1-W1:IFX<0THEN8350
        //     8250 IFA<0THEN8410
        //     8260 IFX>0THEN8280
        //     8270 IFA=0THENC1=5:GOTO8290
        //     8280 C1=1
        //     8290 IFABS(A)<=ABS(X)THEN8330
        //     8310 PRINT"DIRECTION =";C1+(((ABS(A)-ABS(X))+ABS(A))/ABS(A)):GOTO8460
        //     8330 PRINT"DIRECTION =";C1+(ABS(A)/ABS(X)):GOTO8460
        //     8350 IFA>0THENC1=3:GOTO8420
        //     8360 IFX<>0THENC1=5:GOTO8290
        //     8410 C1=7
        //     8420 IFABS(A)>=ABS(X)THEN8450
        //     8430 PRINT"DIRECTION =";C1+(((ABS(X)-ABS(A))+ABS(X))/ABS(X)):GOTO8460
        //     8450 PRINT"DIRECTION =";C1+(ABS(X)/ABS(A))
        //     8460 PRINT"DISTANCE =";SQR(X^2+A^2):IFH8=1THEN1990
        private static float GetDirection(float deltaX, float deltaY)
        {
            var deltaXDominant = Math.Abs(deltaX) > Math.Abs(deltaY);
            var fractionalPart = deltaXDominant ? deltaY / deltaX : -deltaX / deltaY;
            var nearestCardinal = deltaXDominant switch
            {
                true => deltaX > 0 ? 7 : 3,
                false => deltaY > 0 ? 1 : 5
            };

            var direction = nearestCardinal + fractionalPart;
            return direction < 1 ? direction + 8 : direction;
        }

        private static float GetDistance(float deltaX, float deltaY) =>
            (float)Math.Sqrt(Math.Pow(deltaX, 2) + Math.Pow(deltaY, 2));
    }
}