using CommonUsage.Mathematics;
using LessokajiWeaverUtilities.Utilities;
using SimpleCore;
using SimpleCore.Compiler;
using SimpleCore.Library;
using SimpleCore.PropType;
using SimpleLite.Props;
using StandardScene.CarTypes;
using System;
using System.Linq;
using System.Numerics;
using Track = SimpleCore.PropType.Track;

namespace StandardScene.Magnetic.Coders
{
    /// <summary>
    /// 唯一磁循迹器（会话1：Kiva 版与 MWL 版去重；NaiveMagGo 采用含 ${track.Speed} 版本，用户确认）。
    /// C2 物理迁出：原暂置 Kiva.cs（StandardScene.CarTypes），现归 scene.mag 平台插件。
    /// 触发条件：track.Magnet=true 且非倒车段；分叉/汇流路口按顺时针序计算 magSelect。
    /// </summary>
    public class MagneticTrackCoder : ITrackCoder
    {
        public bool Code(SegmentPlan plan, Track track, Site src, Site dst, int i)
        {
            Diagnosis.Post("enter code", "MagTrackCoder", true);
            if (!track.fields.TryGetValue("Magnet", out var mStr) || !bool.TryParse(mStr, out var mBool) ||
                !mBool) return false;

            // do not use magnet navigation for reversing
            if (track.fields.TryGetValue("ReverseDst", out var reverseStr) && int.TryParse(reverseStr, out var rId) &&
                rId == dst.id) return false;

            Diagnosis.Post($">>>> checking {track.id}", "MagTrackCoder", true);
            var magSelect = -1;
            // choose forking/converging path in clockwise order.
            // i.e. 0 is the left-most forking. -1 means no choosing

            float CalculateTangent(Track myTrack, bool checkSrc, int segId)
            {
                var anchorSite = (Site)(checkSrc ? plan.segments[segId - 1] : plan.segments[segId + 1]);

                if (myTrack is UICircularArcTrack arcTrack)
                {
                    var angleStart = arcTrack.Arc.AngleStart;
                    var direction = 1;
                    if (Vector2.Distance(anchorSite.v2, arcTrack.Arc.PointEnd) <
                        Vector2.Distance(anchorSite.v2, arcTrack.Arc.PointStart))
                    {
                        angleStart = arcTrack.Arc.AngleEnd;
                        direction = -1;
                    }

                    angleStart = CommonMath.RoundTh(angleStart);
                    var myAngle = angleStart + direction * 5;
                    var arcTh = CommonMath.RoundTh(myAngle + 90 * direction);
                    Console.WriteLine($">>>> {checkSrc} id: {myTrack.id} th: {arcTh:0.0}");
                    return arcTh;
                }

                if (myTrack is UIBezierTrack bTrack)
                {
                    var add180 = 0f;
                    var controlPoints = bTrack.BezierCurve.ControlPoints;
                    if (Vector2.Distance(anchorSite.v2, controlPoints[0]) >
                        Vector2.Distance(anchorSite.v2, controlPoints.Last())) add180 = 180;
                    var TangentPoint = bTrack.BezierCurve.QueryTangentPoint(anchorSite.v2);
                    Console.WriteLine($">>>>Bezier {checkSrc} id: {myTrack.id} th: {TangentPoint.Angle:0.0} add:{add180} queryid:{anchorSite.id}");
                    return TangentPoint.Angle + add180;
                }

                if (myTrack is UINurbsTrack nTrack)
                {

                }

                // line track
                var myDst = SimpleLib.GetSite(myTrack.GetOther(anchorSite.id));
                var lineTh = (float)(Math.Atan2(myDst.y - anchorSite.y, myDst.x - anchorSite.x) / Math.PI * 180);
                Console.WriteLine($">>>> {checkSrc} id: {myTrack.id} th: {lineTh:0.0}");
                return lineTh;
            }

            bool TestMagSelect(bool checkSrc)
            {
                var anchorSite = checkSrc ? src : dst;
                var myTh = CalculateTangent(track, checkSrc, i);
                var allTracks = anchorSite.relatedTracks.Select(ii => SimpleLib.GetTrack(ii)).ToList();
                if (allTracks.Count <= 2) return false;

                var tracksAndThs = allTracks.Select(myTrack => (myTrack, CalculateTangent(myTrack, checkSrc, i))).ToList();
                var sameHalfTracks = tracksAndThs.Where(item => Math.Abs(LessMath.thDiff(item.Item2, myTh)) < 90)
                    .ToList();
                if (sameHalfTracks.Count > 1)
                {
                    sameHalfTracks = checkSrc
                        ? sameHalfTracks.OrderByDescending(item => LessMath.thDiff(item.Item2, myTh)).ToList()
                        : sameHalfTracks.OrderBy(item => LessMath.thDiff(item.Item2, myTh)).ToList();
                    Console.WriteLine(
                        $">>>> {checkSrc} {track.id} ({string.Join(", ", sameHalfTracks.Select(tt => tt.myTrack.id))})");
                    magSelect = sameHalfTracks.FindIndex(tt => tt.myTrack.id == track.id);
                    return true;
                }

                // check if adjacent to forking/converging path
                Track adjacentTrack;
                int adjacentSegId;
                if (checkSrc)
                {
                    if (i <= 1) return false;
                    adjacentTrack = (Track)plan.segments[i - 2];
                    adjacentSegId = i - 2;
                }
                else
                {
                    if (i >= plan.segments.Count - 2) return false;
                    adjacentTrack = (Track)plan.segments[i + 2];
                    adjacentSegId = i + 2;
                }
                var otherHalfTracks = tracksAndThs.Where(item => Math.Abs(LessMath.thDiff(item.Item2, myTh)) >= 90)
                    .ToList();
                var adjacentTh = CalculateTangent(adjacentTrack, !checkSrc, adjacentSegId);
                otherHalfTracks = checkSrc
                    ? otherHalfTracks.OrderBy(item => LessMath.thDiff(item.Item2, adjacentTh)).ToList()
                    : otherHalfTracks.OrderByDescending(item => LessMath.thDiff(item.Item2, adjacentTh)).ToList();
                Console.WriteLine($">>>> use adjacentTrack  {checkSrc} {adjacentTrack.id} ({string.Join(", ", otherHalfTracks.Select(tt => tt.myTrack.id))})");
                magSelect = otherHalfTracks.FindIndex(tt => tt.myTrack.id == adjacentTrack.id);
                return true;
            }

            if (!TestMagSelect(true)) TestMagSelect(false);

            Console.WriteLine($">>>> {track.id} select {magSelect}");
            var engine = ProgramCoderHelper.PrepareTrackEngine(plan, track, src, dst, i,
                carFields: typeof(BasicCarFields),
                siteFields: typeof(BasicSiteFields),
                trackFields: typeof(BasicTrackFields),
                planFields: typeof(BasicPlanFields));
            var tStr = "agv.MagGo(${src.x},${src.y},${src.id},${dst.x},${dst.y},${dst.id},${track.id}," +
                       $"{magSelect}," +
                       "${track.Speed},${track.Reverse || (track.ReverseDst == dst.id)}," +
                       "${track.CarDirectionBias},${track.EnableCarAbsoluteDirection},${track.CarAbsoluteDirection});";

            if (track.fields.TryGetValue("NaiveMagnet", out var naiveStr) && bool.TryParse(naiveStr, out var naive) &&
                naive)
            {
                var slowDown = i == plan.segments.Count - 2;
                if (slowDown && (!dst.fields.TryGetValue("TagValue", out var tagValStr) ||
                                  !int.TryParse(tagValStr, out _)))
                    throw new Exception($"Destination site {dst.id} must have valid TagValue field!");
                Console.WriteLine($">>>> Naive magnet!");
                tStr = "agv.NaiveMagGo(${src.id},${dst.id},${track.id}," +
                       $"{magSelect}," + "${track.Speed}," +
                       $"{slowDown.ToString().ToLower()}," +
                       "${dst.TagValue}," +
                       "${src.x},${src.y},${dst.x},${dst.y},${track.typeInfo});";
            }

            plan.codeArr[i] += (string)engine.ExecuteExpression($"`{tStr}`", default);

            return true;
        }

        public bool toBlock()
        {
            return true;
        }
    }
}
