using SimpleCore;
using SimpleCore.PropType;
using StandardScene.Magnetic.Protocol;
using System;
using System.Collections.Generic;

namespace StandardScene.Magnetic.Tasking
{
    /// <summary>
    /// 将站点路径与站点/边 fields 合并为 FASS 2.0 <c>0xB1</c> 节点序列。
    /// 对齐 backend <c>CarRequestService.GetSendNodes</c>：边属性挂在节点<b>出边</b>上。
    /// </summary>
    public static class Fass2TaskBuilder
    {
        public const byte StartStopPass = 1;
        public const byte StartStopStop = 2;
        public const byte StartStopControlStart = 11;
        public const byte StartStopControlStop = 12;
        public const byte StartStopPrecision = 22;

        public static Fass2TaskPlan BuildPath(int startSiteId, int goalSiteId, Fass2TaskBuildOptions options,
            Func<int, ushort> resolveNodeId, AbstractCar routingCar = null)
        {
            if (resolveNodeId == null)
            {
                throw new ArgumentNullException(nameof(resolveNodeId));
            }

            options ??= new Fass2TaskBuildOptions();
            var siteIds = Fass2RouteHelper.GetSitesBetween(startSiteId, goalSiteId, routingCar);
            if (siteIds.Count == 0)
            {
                throw new InvalidOperationException($"no path from site {startSiteId} to {goalSiteId}");
            }

            var nodes = BuildNodesForSites(siteIds, options, resolveNodeId);
            return new Fass2TaskPlan
            {
                StartSiteId = startSiteId,
                GoalSiteId = goalSiteId,
                SiteIds = siteIds,
                Nodes = nodes,
                Batches = SplitBatches(nodes, options.MaxNodesPerFrame),
                FieldsSignature = Fass2SiteFieldReader.BuildFieldsSignature(siteIds, 0, SimpleLib.GetSite)
            };
        }

        public static Fass2NodeMessage[] BuildSegment(int srcSiteId, int dstSiteId, Fass2TaskBuildOptions options,
            Func<int, ushort> resolveNodeId, AbstractCar routingCar = null)
        {
            var plan = BuildPath(srcSiteId, dstSiteId, options, resolveNodeId, routingCar);
            var nodes = plan.Nodes;
            var array = new Fass2NodeMessage[nodes.Count];
            for (var i = 0; i < nodes.Count; i++)
            {
                array[i] = nodes[i];
            }

            return array;
        }

        public static IReadOnlyList<Fass2NodeMessage[]> SplitBatches(IReadOnlyList<Fass2NodeMessage> nodes, int maxPerFrame = 10)
        {
            if (nodes == null || nodes.Count == 0)
            {
                return Array.Empty<Fass2NodeMessage[]>();
            }

            if (maxPerFrame <= 0 || maxPerFrame > 10)
            {
                maxPerFrame = 10;
            }

            var batches = new List<Fass2NodeMessage[]>();
            for (var offset = 0; offset < nodes.Count; offset += maxPerFrame)
            {
                var count = Math.Min(maxPerFrame, nodes.Count - offset);
                var batch = new Fass2NodeMessage[count];
                for (var i = 0; i < count; i++)
                {
                    batch[i] = nodes[offset + i];
                }

                batches.Add(batch);
            }

            return batches;
        }

        private static List<Fass2NodeMessage> BuildNodesForSites(IReadOnlyList<int> siteIds, Fass2TaskBuildOptions options,
            Func<int, ushort> resolveNodeId)
        {
            var nodes = new List<Fass2NodeMessage>(siteIds.Count);
            for (var i = 0; i < siteIds.Count; i++)
            {
                var siteId = siteIds[i];
                var site = SimpleLib.GetSite(siteId);
                var siteAction = Fass2SiteFieldReader.Read(site);
                var isLast = i == siteIds.Count - 1;
                var nextSiteId = isLast ? -1 : siteIds[i + 1];

                Fass2TrackMotionData trackMotion = null;
                Site currentSite = site;
                Site nextSite = null;
                if (!isLast)
                {
                    nextSite = SimpleLib.GetSite(nextSiteId);
                    var track = Fass2RouteHelper.FindTrack(siteId, nextSiteId);
                    trackMotion = Fass2TrackFieldReader.Read(track);
                }

                var node = new Fass2NodeMessage
                {
                    Node = resolveNodeId(siteId),
                    StartStop = ResolveStartStop(siteAction, isLast),
                    Distance = !isLast ? ComputeEdgeDistance(currentSite, nextSite) : (ushort)0,
                    Speed = !isLast
                        ? SpeedToProtocol(trackMotion?.Speed ?? options.DefaultSpeed)
                        : (ushort)0,
                    Orientation = !isLast ? ResolveOrientation(currentSite, nextSite, trackMotion) : (byte)0,
                    Byroad = siteAction.Byroad ?? trackMotion?.Byroad ?? 0,
                    Direction = siteAction.Direction ?? trackMotion?.Direction ?? 0,
                    Lift = siteAction.Lift ?? 0,
                    Roll = siteAction.Roll ?? 0,
                    Charge = siteAction.Charge ?? 0,
                    Obstacle = siteAction.Obstacle ?? 0,
                    Audio = siteAction.Audio ?? 0,
                    Light = siteAction.Light ?? 0,
                    Rest = siteAction.Rest ?? 0,
                    Clamp = siteAction.Clamp ?? 0,
                    Tray = siteAction.Tray ?? 0,
                    Shutdown = siteAction.Shutdown ?? 0
                };

                if (siteAction.Orientation.HasValue)
                {
                    node.Orientation = siteAction.Orientation.Value;
                }

                nodes.Add(node);
            }

            return nodes;
        }

        private static byte ResolveStartStop(Fass2SiteActionData siteAction, bool isLast)
        {
            if (siteAction.StartStop.HasValue)
            {
                return siteAction.StartStop.Value;
            }

            if (isLast)
            {
                return siteAction.PrecisionStop ? StartStopPrecision : StartStopStop;
            }

            return StartStopPass;
        }

        private static byte ResolveOrientation(Site src, Site dst, Fass2TrackMotionData trackMotion)
        {
            if (trackMotion?.Orientation != null)
            {
                return trackMotion.Orientation.Value;
            }

            if (src == null || dst == null)
            {
                return 0;
            }

            var angle = trackMotion != null && trackMotion.EnableCarAbsoluteDirection
                ? trackMotion.CarAbsoluteDirection
                : Math.Atan2(dst.y - src.y, dst.x - src.x) / Math.PI * 180.0;

            if (trackMotion?.Reverse == true)
            {
                angle += 180;
            }

            angle += trackMotion?.CarDirectionBias ?? 0;
            angle %= 360;
            if (angle < 0)
            {
                angle += 360;
            }

            return (byte)((int)Math.Round(angle / 360.0 * 256) % 256);
        }

        private static ushort ComputeEdgeDistance(Site src, Site dst)
        {
            var dx = dst.x - src.x;
            var dy = dst.y - src.y;
            var length = Math.Sqrt(dx * dx + dy * dy);
            return (ushort)Math.Min(ushort.MaxValue, Math.Max(1, Math.Round(length)));
        }

        /// <summary>
        /// 协议速度单位 0.1 m/min；地图 Speed 默认 m/min，≤1 视为旧版 m/s 兼容。
        /// </summary>
        public static ushort SpeedToProtocol(double speed)
        {
            if (speed > 0 && speed <= 1)
            {
                speed *= 60;
            }

            return (ushort)Math.Max(1, Math.Min(10000, Math.Round(speed * 10)));
        }
    }
}
