// For junctions with more outgoing (out) lanes than incoming (in) lanes.
        private static List <LaneConnectionInfo> AssignLanesMoreOutThanIn(int inLanes, int leftOut, int forwardOut, int rightOut)
        {
            List <NetLane.Flags> outLanes = CreateLaneList(leftOut, forwardOut, rightOut);

            List <List <int> > possibleLaneArrangements = GetAllPossibleLaneConfigurations(inLanes, outLanes.Count);

            List <LaneConnectionInfo> bestLanesInfo = GetLaneSetup(outLanes, possibleLaneArrangements[0]);
            LaneSetupFeatures         bestFeatures  = EvaluateLaneSetup(bestLanesInfo, outLanes);

            // Mod.LogMessage("Initial setup:\n" + ToString(bestLanesInfo) + "\nevaluated as:\n" + bestFeatures.ToString());

            for (int i = 1; i < possibleLaneArrangements.Count; ++i)
            {
                var lanesInfo = GetLaneSetup(outLanes, possibleLaneArrangements[i]);
                var features  = EvaluateLaneSetup(lanesInfo, outLanes);

                // Mod.LogMessage(ToString(lanesInfo) + "\nevaluated as:\n" + features.ToString());

                if (features.IsBetterThan(bestFeatures))
                {
                    // Mod.LogMessage("This is better than previous best");
                    bestFeatures  = features;
                    bestLanesInfo = lanesInfo;
                }
            }

            if (!bestFeatures.valid)
            {
                // Impossible (in theory)
                Mod.LogMessage("Selected setup does not meet minimum requirements! "
                               + string.Join(", ", bestLanesInfo.Select(x => x.ToString()).ToArray())
                               + "Lanes in: " + inLanes
                               + "Lanes out: " + leftOut + "L/" + forwardOut + "F/" + rightOut + "R");
            }

            return(bestLanesInfo);
        }
        // Returns true if the lane setup described by this object is preferred over 'other'. Assumes RHT
        public bool IsBetterThan(LaneSetupFeatures other)
        {
            // Invalid lane setups should never be used.
            if (valid != other.valid)
            {
                return(valid);
            }

            // Avoid L+F+R lanes if possible.
            if (hasLeftFwdRightLane != other.hasLeftFwdRightLane)
            {
                return(!hasLeftFwdRightLane);
            }

            // Avoid L+R if possible.
            if (hasLeftRightLane != other.hasLeftRightLane)
            {
                return(!hasLeftRightLane);
            }

            // Avoid L+F lanes if possible.
            if (hasLeftFwdLane != other.hasLeftFwdLane)
            {
                return(!hasLeftFwdLane);
            }

            // Avoid F+R lanes if possible.
            if (hasFwdRightLane != other.hasFwdRightLane)
            {
                return(!hasFwdRightLane);
            }

            // Prefer setups with a larger number of incoming lanes with forward direction (i.e. limit lane splitting on forward connections).
            if (numFwdLanes != other.numFwdLanes)
            {
                return(numFwdLanes > other.numFwdLanes);
            }

            // Prefer setups where the number of forward connections is (more) evenly distributed among incoming lanes (including L+F and F+R lanes).
            if (fwdConnectionImbalance != other.fwdConnectionImbalance)
            {
                return(fwdConnectionImbalance < other.fwdConnectionImbalance);
            }

            if ((rightConnectionImbalance < 2) != (other.rightConnectionImbalance < 2))
            {
                return(rightConnectionImbalance < 2);
            }

            if ((leftConnectionImbalance < 2) != (other.leftConnectionImbalance < 2))
            {
                return(leftConnectionImbalance < 2);
            }

            // Prefer setups where the number of left connections is (more) evenly distributed among incoming lanes (including L+F and L+R lanes).
            // Difference of 1 is OK. In practice, we are only trying to avoid imbalance between Left-only lanes and Left+Fwd or Left+Right lanes.
            if ((leftConnectionImbalance < 2) != (other.leftConnectionImbalance < 2))
            {
                return(leftConnectionImbalance < 2);
            }

            // As above, but for right-turning lanes.
            if ((rightConnectionImbalance < 2) != (other.rightConnectionImbalance < 2))
            {
                return(rightConnectionImbalance < 2);
            }

            // Prefer setups where left and right turning lanes are (more) proportionally distributed
            if (leftRightOutInRatioImbalance != other.leftRightOutInRatioImbalance)
            {
                return(leftRightOutInRatioImbalance < other.leftRightOutInRatioImbalance);
            }

            // equivalent
            return(false);
        }
        // Identifies all features of a lane setup, which may be needed to determine which setup is best. Assumes RHT
        private static LaneSetupFeatures EvaluateLaneSetup(List <LaneConnectionInfo> lanesInfo, List <NetLane.Flags> outLanes)
        {
            var features = new LaneSetupFeatures();

            // If two in lanes with the same direction (e.g. two forward-only lanes) connect to a different
            // number of out lanes, the lane with more connections MUST be to the left (in RHT) of the other lane.
            for (int i = 1; i < lanesInfo.Count; ++i)
            {
                if (lanesInfo[i - 1].direction == lanesInfo[i].direction)
                {
                    if (lanesInfo[i - 1].GetLaneCount() < lanesInfo[i].GetLaneCount())
                    {
                        features.valid = false;
                        return(features);
                    }
                }
            }

            int minLeftLaneConns  = 255;
            int maxLeftLaneConns  = 0;
            int minFwdLaneConns   = 255;
            int maxFwdLaneConns   = 0;
            int minRightLaneConns = 255;
            int maxRightLaneConns = 0;

            int numLeftInLanes  = 0;
            int numRightInLanes = 0;

            foreach (var laneInfo in lanesInfo)
            {
                features.hasFwdRightLane     |= (laneInfo.direction == NetLane.Flags.ForwardRight);
                features.hasLeftFwdLane      |= (laneInfo.direction == NetLane.Flags.LeftForward);
                features.hasLeftRightLane    |= (laneInfo.direction == NetLane.Flags.LeftRight);
                features.hasLeftFwdRightLane |= (laneInfo.direction == NetLane.Flags.LeftForwardRight);

                int numFwdConns   = 0;
                int numLeftConns  = 0;
                int numRightConns = 0;

                for (int i = laneInfo.firstTarget; i <= laneInfo.lastTarget; ++i)
                {
                    var connectionDir = outLanes[i];

                    switch (connectionDir)
                    {
                    case NetLane.Flags.Left:
                        numLeftConns += 1;
                        break;

                    case NetLane.Flags.Forward:
                        numFwdConns += 1;
                        break;

                    case NetLane.Flags.Right:
                        numRightConns += 1;
                        break;

                    default:
                        break;
                    }
                }

                if (numFwdConns > 0)
                {
                    minFwdLaneConns = Math.Min(minFwdLaneConns, numFwdConns);
                    maxFwdLaneConns = Math.Max(maxFwdLaneConns, numFwdConns);

                    features.numFwdLanes += 1;
                }
                if (numLeftConns > 0)
                {
                    minLeftLaneConns = Math.Min(minLeftLaneConns, numLeftConns);
                    maxLeftLaneConns = Math.Max(maxLeftLaneConns, numLeftConns);

                    numLeftInLanes += 1;
                }
                if (numRightConns > 0)
                {
                    minRightLaneConns = Math.Min(minRightLaneConns, numRightConns);
                    maxRightLaneConns = Math.Max(maxRightLaneConns, numRightConns);

                    numRightInLanes += 1;
                }
            }

            features.leftConnectionImbalance  = Math.Max(0, maxLeftLaneConns - minLeftLaneConns);
            features.fwdConnectionImbalance   = Math.Max(0, maxFwdLaneConns - minFwdLaneConns);
            features.rightConnectionImbalance = Math.Max(0, maxRightLaneConns - minRightLaneConns);

            if (numLeftInLanes > 0 && numRightInLanes > 0)
            {
                int numLeftOutLanes  = 0;
                int numRightOutLanes = 0;

                foreach (var outLane in outLanes)
                {
                    if (outLane == NetLane.Flags.Left)
                    {
                        numLeftOutLanes += 1;
                    }
                    else if (outLane == NetLane.Flags.Right)
                    {
                        numRightOutLanes += 1;
                    }
                }

                float leftOutInRatio  = (float)numLeftOutLanes / numLeftInLanes;
                float rightOutInRatio = (float)numRightOutLanes / numRightInLanes;

                features.leftRightOutInRatioImbalance = Math.Max(leftOutInRatio, rightOutInRatio) / Math.Min(leftOutInRatio, rightOutInRatio);
            }

            return(features);
        }