using RobotNet.MapShares;
using RobotNet.MapShares.Dtos;
using RobotNet.RobotShares.Dtos;

namespace RobotNet.RobotManager.Services.Traffic;

public class TrafficMath
{
    public static bool Edge1IntersectEdge(TrafficNodeDto centerCircle, double radius, EdgeCaculatorModel edge)
    {
        if (edge.TrajectoryDegree != MapShares.Enums.TrajectoryDegree.One) return true;

        double dx = edge.X2 - edge.X1;
        double dy = edge.Y2 - edge.Y1;
        var length = Math.Sqrt(Math.Pow(dx, 2) + Math.Pow(dy, 2));
        if (length == 0) return Math.Sqrt(Math.Pow(edge.X1 - centerCircle.X, 2) + Math.Pow(edge.Y1 - centerCircle.Y, 2)) <= radius;

        double distance = Math.Abs(dy * centerCircle.X - dx * centerCircle.Y + (edge.X2 * edge.Y1 - edge.X1 * edge.Y2)) / length;
        if (distance > radius) return false;

        double a = dx * dx + dy * dy;
        double b = 2 * (dx * (edge.X1 - centerCircle.X) + dy * (edge.Y1 - centerCircle.Y));
        double c = (edge.X1 - centerCircle.X) * (edge.X1 - centerCircle.X) + (edge.Y1 - centerCircle.Y) * (edge.Y1 - centerCircle.Y) - radius * radius;

        double delta = b * b - 4 * a * c;
        if (delta < 0) return false;

        double t1 = (-b + Math.Sqrt(delta)) / (2 * a);
        double t2 = (-b - Math.Sqrt(delta)) / (2 * a);

        return (t1 >= 0 && t1 <= 1) || (t2 >= 0 && t2 <= 1);
    }

    public static bool Edge2IntersectEdge(TrafficNodeDto centerCircle, double radius, EdgeCaculatorModel edge)
    {
        if (edge.TrajectoryDegree != MapShares.Enums.TrajectoryDegree.Two) return true;

        var length = Math.Sqrt(Math.Pow(edge.X2 - edge.X1, 2) + Math.Pow(edge.Y2 - edge.Y1, 2));
        if (length == 0) return Math.Sqrt(Math.Pow(edge.X1 - centerCircle.X, 2) + Math.Pow(edge.Y1 - centerCircle.Y, 2)) <= radius;

        double step = 0.1 / length;
        for (double t = 0; t <= 1.001; t += step)
        {
            (double x1, double y1) = CurveDegreeTwo(t, edge.X1, edge.Y1, edge.ControlPoint1X, edge.ControlPoint1Y, edge.X2, edge.Y2);
            if (Math.Sqrt(Math.Pow(x1 - centerCircle.X, 2) + Math.Pow(y1 - centerCircle.Y, 2)) < radius) return true;
        }
        return false;
    }

    public static bool Edge3IntersectEdge(TrafficNodeDto centerCircle, double radius, EdgeCaculatorModel edge)
    {
        if (edge.TrajectoryDegree != MapShares.Enums.TrajectoryDegree.Three) return true;

        var length = Math.Sqrt(Math.Pow(edge.X2 - edge.X1, 2) + Math.Pow(edge.Y2 - edge.Y1, 2));
        if (length == 0) return Math.Sqrt(Math.Pow(edge.X1 - centerCircle.X, 2) + Math.Pow(edge.Y1 - centerCircle.Y, 2)) <= radius;

        double step = 0.1 / length;
        for (double t = 0; t <= 1.001; t += step)
        {
            (double x1, double y1) = CurveDegreeThree(t, edge.X1, edge.Y1, edge.ControlPoint1X, edge.ControlPoint1Y, edge.ControlPoint2X, edge.ControlPoint2Y, edge.X2, edge.Y2);
            if (Math.Sqrt(Math.Pow(x1 - centerCircle.X, 2) + Math.Pow(y1 - centerCircle.Y, 2)) < radius) return true;
        }
        return false;
    }

    public static bool EdgeIntersectCircle(TrafficNodeDto centerCircle, double radius, EdgeCaculatorModel edge)
    {
        if (edge.TrajectoryDegree == MapShares.Enums.TrajectoryDegree.One) return Edge1IntersectEdge(centerCircle, radius, edge);
        else if (edge.TrajectoryDegree == MapShares.Enums.TrajectoryDegree.Two) return Edge2IntersectEdge(centerCircle, radius, edge);
        else if (edge.TrajectoryDegree == MapShares.Enums.TrajectoryDegree.Three) return Edge3IntersectEdge(centerCircle, radius, edge);
        return true;
    }

    public static (double x, double y) CurveDegreeTwo(double t, double x1, double y1, double controlPointX, double controlPointY, double x2, double y2)
    {
        var x = (1 - t) * (1 - t) * x1 + 2 * t * (1 - t) * controlPointX + t * t * x2;
        var y = (1 - t) * (1 - t) * y1 + 2 * t * (1 - t) * controlPointY + t * t * y2;
        return (x, y);
    }

    public static (double x, double y) CurveDegreeThree(double t, double x1, double y1, double controlPoint1X, double controlPoint1Y, double controlPoint2X, double controlPoint2Y, double x2, double y2)
    {
        var x = Math.Pow(1 - t, 3) * x1 + 3 * Math.Pow(1 - t, 2) * t * controlPoint1X + 3 * Math.Pow(t, 2) * (1 - t) * controlPoint2X + Math.Pow(t, 3) * x2; ;
        var y = Math.Pow(1 - t, 3) * y1 + 3 * Math.Pow(1 - t, 2) * t * controlPoint1Y + 3 * Math.Pow(t, 2) * (1 - t) * controlPoint2Y + Math.Pow(t, 3) * y2;
        return (x, y);
    }

    public static double CalTurningRadius(AgentModel model, double offsetLength, double offsetWidth)
    {
        var navigationPointX = offsetLength - model.NavigationPointX;
        var navigationPointY = offsetWidth - model.NavigationPointY;
        var length = model.Length + 2 * offsetLength;
        var width = model.Width + 2 * offsetWidth;
        double d1 = Math.Sqrt(navigationPointX * navigationPointX + navigationPointY * navigationPointY);
        double d2 = Math.Sqrt((length - navigationPointX) * (length - navigationPointX) + navigationPointY * navigationPointY);
        double d3 = Math.Sqrt(navigationPointX * navigationPointX + (width - navigationPointY) * (width - navigationPointY));
        double d4 = Math.Sqrt((length - navigationPointX) * (length - navigationPointX) + (width - navigationPointY) * (width - navigationPointY));

        return Math.Max(Math.Max(d1, d2), Math.Max(d3, d4));
    }

    public static TrafficLockedShapeDto CalRobotRectShape(AgentModel model, double offsetLength, double offsetWidth, double x, double y, double theta)
    {
        double x1 = model.NavigationPointX - offsetLength;
        double y1 = model.NavigationPointY - offsetWidth;
        double x2 = model.Length + model.NavigationPointX + offsetLength;
        double y2 = model.NavigationPointY - offsetWidth;
        double x3 = model.Length + model.NavigationPointX + offsetLength;
        double y3 = model.Width + model.NavigationPointY + offsetWidth;
        double x4 = model.NavigationPointX - offsetLength;
        double y4 = model.Width + model.NavigationPointY + offsetWidth;

        return new()
        {
            Type = TrafficLockedShapeType.Rectangle,
            X1 = x1 * Math.Cos(theta) - y1 * Math.Sin(theta) + x,
            Y1 = x1 * Math.Sin(theta) + y1 * Math.Cos(theta) + y,
            X2 = x2 + Math.Cos(theta) - y2 * Math.Sin(theta) + x,
            Y2 = x2 * Math.Sin(theta) + y2 * Math.Cos(theta) + y,
            X3 = x3 + Math.Cos(theta) - y3 * Math.Sin(theta) + x,
            Y3 = x3 * Math.Sin(theta) + y3 * Math.Cos(theta) + y,
            X4 = x4 + Math.Cos(theta) - y4 * Math.Sin(theta) + x,
            Y4 = x4 * Math.Sin(theta) + y4 * Math.Cos(theta) + y
        };
    }

    public static bool RectIntersectCircle(TrafficNodeDto centerCircle, double radius, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
    {
        double xMin = Math.Min(Math.Min(x1, x2), Math.Min(x3, x4));
        double xMax = Math.Max(Math.Max(x1, x2), Math.Max(x3, x4));
        double yMin = Math.Min(Math.Min(y1, y2), Math.Min(y3, y4));
        double yMax = Math.Max(Math.Max(y1, y2), Math.Max(y3, y4));

        double xClosest = Math.Max(xMin, Math.Min(centerCircle.X, xMax));
        double yClosest = Math.Max(yMin, Math.Min(centerCircle.Y, yMax));

        double distance = Math.Sqrt((centerCircle.X - xClosest) * (centerCircle.X - xClosest) + (centerCircle.Y - yClosest) * (centerCircle.Y - yClosest));
        return distance <= radius;
    }

    public static bool RectIntersectRect(TrafficLockedShapeDto rect1, TrafficLockedShapeDto rect2)
    {
        double x1Min = Math.Min(Math.Min(rect1.X1, rect1.X2), Math.Min(rect1.X3, rect1.X4));
        double x1Max = Math.Max(Math.Max(rect1.X1, rect1.X2), Math.Max(rect1.X3, rect1.X4));
        double y1Min = Math.Min(Math.Min(rect1.Y1, rect1.Y2), Math.Min(rect1.Y3, rect1.Y4));
        double y1Max = Math.Max(Math.Max(rect1.Y1, rect1.Y2), Math.Max(rect1.Y3, rect1.Y4));

        double x2Min = Math.Min(Math.Min(rect2.X1, rect2.X2), Math.Min(rect2.X3, rect2.X4));
        double x2Max = Math.Max(Math.Max(rect2.X1, rect2.X2), Math.Max(rect2.X3, rect2.X4));
        double y2Min = Math.Min(Math.Min(rect2.Y1, rect2.Y2), Math.Min(rect2.Y3, rect2.Y4));
        double y2Max = Math.Max(Math.Max(rect2.Y1, rect2.Y2), Math.Max(rect2.Y3, rect2.Y4));

        bool xOverlap = x1Min <= x2Max && x2Min <= x1Max;
        bool yOverlap = y1Min <= y2Max && y2Min <= y1Max;

        return xOverlap && yOverlap;
    }
}
