using RobotNet.MapManager.Data;
using RobotNet.MapShares.Dtos;
using RobotNet.MapShares.Enums;

namespace RobotNet.MapManager.Services;

public class ServerHelper
{
    public static Edge CreateEdge(Map map, Guid startNodeId, Guid endNodeId, TrajectoryDegree trajectoryDegree, double cpX1 = 0, double cpY1 = 0, double cpX2 = 0, double cpY2 = 0)
    {
        return new Edge()
        {
            MapId = map.Id,
            StartNodeId = startNodeId,
            EndNodeId = endNodeId,
            TrajectoryDegree = trajectoryDegree,
            DirectionAllowed = map.EdgeDirectionAllowedDefault,
            RotationAllowed = map.EdgeRotationAllowedDefault,
            MaxSpeed = trajectoryDegree == TrajectoryDegree.One ? map.EdgeStraightMaxSpeedDefault : map.EdgeCurveMaxSpeedDefault,
            MaxRotationSpeed = map.EdgeMaxRotationSpeedDefault,
            MaxHeight = map.EdgeMaxHeightDefault,
            MinHeight = map.EdgeMinHeightDefault,
            Actions = "[]",
            AllowedDeviationXy = map.EdgeAllowedDeviationXyDefault,
            AllowedDeviationTheta = map.EdgeAllowedDeviationThetaDefault,
            ControlPoint1X = cpX1,
            ControlPoint1Y = cpY1,
            ControlPoint2X = cpX2,
            ControlPoint2Y = cpY2,
        };
    }

    public static Node CreateNode(Map map, double x, double y)
    {
        return new Node()
        {
            MapId = map.Id,
            Name = map.NodeNameAutoGenerate ? $"{map.NodeNameTemplateDefault}{++map.NodeCount}" : string.Empty,
            X = x,
            Y = y,
            Theta = 0,
            AllowedDeviationXy = map.NodeAllowedDeviationXyDefault,
            AllowedDeviationTheta = map.NodeAllowedDeviationThetaDefault,
            Actions = "[]",
        };
    }

    public static EdgeDto CreateEdgeDto(Edge edge, Node startNode, Node endNode)
    {
        return new EdgeDto()
        {
            Id = edge.Id,
            MapId = edge.MapId,
            StartNodeId = edge.StartNodeId,
            EndNodeId = edge.EndNodeId,
            TrajectoryDegree = edge.TrajectoryDegree,
            ControlPoint1X = edge.ControlPoint1X,
            ControlPoint1Y = edge.ControlPoint1Y,
            ControlPoint2X = edge.ControlPoint2X,
            ControlPoint2Y = edge.ControlPoint2Y,
            DirectionAllowed = edge.DirectionAllowed,
            RotationAllowed = edge.RotationAllowed,
            MaxSpeed = edge.MaxSpeed,
            MaxRotationSpeed = edge.MaxRotationSpeed,
            MaxHeight = edge.MaxHeight,
            MinHeight = edge.MinHeight,
            Actions = edge.Actions,
            AllowedDeviationXy = edge.AllowedDeviationXy,
            AllowedDeviationTheta = edge.AllowedDeviationTheta,
            StartNode = new NodeDto()
            {
                Id = startNode.Id,
                Name = startNode.Name,
                MapId = startNode.MapId,
                Theta = startNode.Theta,
                X = startNode.X,
                Y = startNode.Y,
                Actions = startNode.Actions,
                AllowedDeviationXy = startNode.AllowedDeviationXy,
                AllowedDeviationTheta = startNode.AllowedDeviationTheta,
            },
            EndNode = new NodeDto()
            {
                Id = endNode.Id,
                Name = endNode.Name,
                MapId = endNode.MapId,
                Theta = endNode.Theta,
                X = endNode.X,
                Y = endNode.Y,
                AllowedDeviationXy = endNode.AllowedDeviationXy,
                AllowedDeviationTheta = endNode.AllowedDeviationTheta,
                Actions = endNode.Actions,
            },
        };
    }

    public static NodeDto CreateNodeDto(Node node)
    {
        return new NodeDto()
        {
            Id = node.Id,
            Name = node.Name,
            MapId = node.MapId,
            Theta = node.Theta,
            X = node.X,
            Y = node.Y,
            AllowedDeviationXy = node.AllowedDeviationXy,
            AllowedDeviationTheta = node.AllowedDeviationTheta,
            Actions = node.Actions,
        };
    }

    public static Edge? GetClosesEdge(double x, double y, IEnumerable<Node> nodes, IEnumerable<Edge> edges, double allowDistance)
    {
        double minDistance = double.MaxValue;
        Edge? edgeResult = null;
        foreach (var edge in edges)
        {
            if (edge is not null)
            {
                var startNode = nodes.FirstOrDefault(n => n.Id == edge.StartNodeId);
                var endNode = nodes.FirstOrDefault(n => n.Id == edge.EndNodeId);
                if (startNode is null || endNode is null) continue;
                var distance = DistanceToSegment(x, y, startNode.X, startNode.Y, endNode.X, endNode.Y);
                if (distance < minDistance)
                {
                    minDistance = distance;
                    edgeResult = edge;
                }
            }
        }
        if (minDistance > allowDistance) return null;
        return edgeResult;
    }

    private static double DistanceToSegment(double x, double y, double x1, double y1, double x2, double y2)
    {
        double dx = x2 - x1;
        double dy = y2 - y1;

        double segmentLengthSquared = dx * dx + dy * dy;

        double t;
        if (segmentLengthSquared == 0)
        {
            t = 0;
        }
        else
        {
            t = ((x - x1) * dx + (y - y1) * dy) / segmentLengthSquared;
            t = Math.Max(0, Math.Min(1, t));
        }

        double nearestX = x1 + t * dx;
        double nearestY = y1 + t * dy;

        double distanceSquared = (x - nearestX) * (x - nearestX) + (y - nearestY) * (y - nearestY);

        return Math.Sqrt(distanceSquared);
    }
}
