using System;
using System.Globalization;
using CrossCutting;

namespace InterpreterLibrary.Models
{
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    public sealed class Line
    {
        
        

        
        
        
        public bool IsVertical => float.IsInfinity(Slope);

        
        
        
        public bool IsHorizontal => Slope == 0;

        
        
        
        public float Slope { get; }

        
        
        
        
        public float Intercept { get; }

        
        
        
        
        
        
        
        
        
        
        public static Line FromPoints(PointF point1, PointF point2)
        {
            return new Line(point1, point2);
        }

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        public static Line FromSlopeIntercept(float slope, float intercept)
        {
            return new Line(slope, intercept);
        }

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        public static Line FromRTheta(float radius, float theta)
        {
            return new Line(radius, theta, false);
        }

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        public static Line FromPointTheta(PointF point, float theta)
        {
            return new Line(point, theta);
        }

        
        
        
        
        
        public float GetAngleBetweenLines(Line secondLine)
        {
            var k2 = secondLine.Slope;

            var isVertical1 = IsVertical;
            var isVertical2 = secondLine.IsVertical;

            
            if (Slope == k2 || isVertical1 && isVertical2)
                return 0;

            float angle;

            if (!isVertical1 && !isVertical2)
            {
                var tanPhi = (k2 > Slope ? k2 - Slope : Slope - k2) / (1 + Slope * k2);
                angle = (float) Math.Atan(tanPhi);
            }
            else
            {
                

                if (isVertical1)
                    angle = (float) (Math.PI / 2 - Math.Atan(k2) * Math.Sign(k2));
                else
                    angle = (float) (Math.PI / 2 - Math.Atan(Slope) * Math.Sign(Slope));
            }

            
            angle *= (float) (180.0 / Math.PI);

            if (angle < 0)
                angle = -angle;

            return angle;
        }

        
        
        
        
        
        
        
        
        
        public PointF? GetIntersectionWith(Line secondLine)
        {
            var k2 = secondLine.Slope;
            var b2 = secondLine.Intercept;

            var isVertical1 = IsVertical;
            var isVertical2 = secondLine.IsVertical;

            PointF? intersection = null;

            if (Slope == k2 || isVertical1 && isVertical2)
            {
                if (Intercept == b2)
                    throw new InvalidOperationException("Identical lines do not have an intersection point.");
            }
            else
            {
                if (isVertical1)
                {
                    intersection = new PointF(Intercept, k2 * Intercept + b2);
                }
                else if (isVertical2)
                {
                    intersection = new PointF(b2, Slope * b2 + Intercept);
                }
                else
                {
                    var x = (b2 - Intercept) / (Slope - k2);
                    intersection = new PointF(x, Slope * x + Intercept);
                }
            }

            return intersection;
        }

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        public PointF? GetIntersectionWith(LineSegment other)
        {
            return other.GetIntersectionWith(this);
        }

        
        
        
        
        
        
        
        
        
        public float DistanceToPoint(PointF point)
        {
            float distance;

            if (!IsVertical)
            {
                var div = (float) Math.Sqrt(Slope * Slope + 1);
                distance = Math.Abs((Slope * point.X + Intercept - point.Y) / div);
            }
            else
            {
                distance = Math.Abs(Intercept - point.X);
            }

            return distance;
        }

        
        
        
        
        
        
        
        
        
        public static bool operator ==(Line line1, Line line2)
        {
            if (ReferenceEquals(line1, line2))
                return true;

            if ((object) line1 == null || (object) line2 == null)
                return false;

            return line1.Slope == line2.Slope && line1.Intercept == line2.Intercept;
        }

        
        
        
        
        
        
        
        
        
        public static bool operator !=(Line line1, Line line2)
        {
            return line1 != line2;
        }

        
        
        
        
        
        public override bool Equals(object obj)
        {
            return obj is Line line && this == line;
        }

        
        
        
        
        public override int GetHashCode()
        {
            return Slope.GetHashCode() + Intercept.GetHashCode();
        }

        
        
        
        
        public override string ToString()
        {
            return string.Format(CultureInfo.InvariantCulture, "k = {0}, b = {1}", Slope, Intercept);
        }

        #region Private Constructors

        private Line(PointF start, PointF end)
        {
            if (start == end)
                throw new ArgumentException("Start point of the line cannot be the same as its end point.");

            Slope = (end.Y - start.Y) / (end.X - start.X);
            Intercept = float.IsInfinity(Slope) ? start.X : start.Y - Slope * start.X;
        }

        private Line(float slope, float intercept)
        {
            Slope = slope;
            Intercept = intercept;
        }

        private Line(float radius, float theta, bool _)
        {
            if (radius < 0)
                throw new ArgumentOutOfRangeException(nameof(radius), "Must be non-negative");

            theta *= (float) (Math.PI / 180);

            float sine = (float) Math.Sin(theta), cosine = (float) Math.Cos(theta);
            var pt1 = new PointF(radius * cosine, radius * sine);

            
            Slope = -cosine / sine;

            if (!float.IsInfinity(Slope))
                Intercept = pt1.Y - Slope * pt1.X;
            else
                Intercept = Math.Abs(radius);
        }

        private Line(PointF point, float theta)
        {
            theta *= (float) (Math.PI / 180);

            Slope = (float) (-1.0f / Math.Tan(theta));

            if (!float.IsInfinity(Slope))
                Intercept = point.Y - Slope * point.X;
            else
                Intercept = point.X;
        }

        #endregion
    }
}
