/// <summary>
        /// Gets the minimum weight from customer1 -> customer3 while inserting customer2 and the best direction and turns to use.
        /// </summary>
        public static float CalculateCheapestInsert(this float[][] weights, float[] penalties, int directedId1, int id2, int directedId3,
            bool includeTurn1, bool includeTurn3, out int departureOffset1, out int arrivalOffset3, out int turn2)
        {
            // extract existing data.
            int id1, turn1, arrivalId1, departureId1, arrivalOffset1, temp;
            DirectedHelper.ExtractAll(directedId1, out arrivalId1, out departureId1, out id1, out turn1);
            DirectedHelper.ExtractOffset(turn1, out arrivalOffset1, out temp);
            int id3, turn3, arrivalId3, departureId3, departureOffset3;
            DirectedHelper.ExtractAll(directedId3, out arrivalId3, out departureId3, out id3, out turn3);
            DirectedHelper.ExtractOffset(turn3, out temp, out departureOffset3);

            // calculate current weight.
            var weightBefore = weights[departureId1][arrivalId3];
            if (includeTurn1)
            {
                weightBefore += penalties[turn1];
            }
            if (includeTurn3)
            {
                weightBefore += penalties[turn3];
            }

            // evaluate all possibilities.
            var best = float.MaxValue;
            var base1 = id1 * 2;
            var base2 = id2 * 2;
            var base3 = id3 * 2;
            departureOffset1 = Constants.NOT_SET;
            arrivalOffset3 = Constants.NOT_SET;
            turn2 = Constants.NOT_SET;
            for (var do1 = 0; do1 < 2; do1++)
            {
                var d1 = base1 + do1;
                var turn1Weight = 0f;
                if (includeTurn1)
                {
                    turn1 = DirectedHelper.BuildTurn(arrivalOffset1, do1);
                    turn1Weight = penalties[turn1];
                }
                for (var ao3 = 0; ao3 < 2; ao3++)
                {
                    var a3 = base3 + ao3;
                    var turn3Weight = 0f;
                    if (includeTurn3)
                    {
                        turn3 = DirectedHelper.BuildTurn(departureOffset3, ao3);
                        turn3Weight = penalties[turn3];
                    }
                    for (var t = 0; t < 4; t++)
                    {
                        int ao2, do2;
                        DirectedHelper.ExtractOffset(t, out ao2, out do2);

                        var weight = weights[d1][base2 + ao2] +
                            weights[base2 + do2][a3] +
                            penalties[t] + turn1Weight +
                            turn3Weight;

                        if (weight < best)
                        {
                            best = weight;
                            departureOffset1 = do1;
                            arrivalOffset3 = ao3;
                            turn2 = t;
                        }
                    }
                }
            }
            return best - weightBefore;
        }
        /// <summary>
        /// Applies this operator.
        /// </summary>
        public bool Apply(SequenceDirectedProblem problem, SequenceDirectedObjective objective, Tour solution, out float delta)
        {
            var weights = problem.Weights;

            delta = objective.Zero;

            var fitnessBefore = objective.Calculate(problem, solution);

            var enumerator = solution.GetEnumerator();

            while (enumerator.MoveNext())
            {
                var previous            = enumerator.Current;
                var previousDepartureId = DirectedHelper.ExtractDepartureId(previous);
                var current             = solution.GetNeigbour(previous);
                if (current == Constants.NOT_SET)
                { // last of open.
                    continue;
                }
                var next          = solution.GetNeigbour(current);
                var nextArrivalId = Constants.NOT_SET;
                if (next != Constants.NOT_SET)
                {
                    nextArrivalId = DirectedHelper.ExtractArrivalId(next);
                }
                int currentTurn, currentId, currentArrivalId, currentDepartureId;
                DirectedHelper.ExtractAll(current, out currentArrivalId, out currentDepartureId, out currentId, out currentTurn);

                var weightBefore  = weights[previousDepartureId][currentArrivalId];
                var weightAfter   = float.MaxValue;
                var newDirectedId = Constants.NOT_SET;
                if (next == Constants.NOT_SET)
                { // there is no next, only one option here:
                    // 0 or 1: arrival id offset = 0 OR
                    // 2 or 3: arrival id offset = 1
                    if (currentArrivalId % 2 == 0)
                    { // check turn = 2.
                        weightAfter = weights[previousDepartureId][currentArrivalId + 1];
                        if (weightAfter < weightBefore)
                        { // do the switch.
                            newDirectedId = DirectedHelper.BuildDirectedId(currentId, 2);
                        }
                    }
                    else
                    { // check turn = 0
                        weightAfter = weights[previousDepartureId][currentArrivalId - 1];
                        if (weightAfter < weightBefore)
                        { // do the switch.
                            newDirectedId = DirectedHelper.BuildDirectedId(currentId, 0);
                        }
                    }
                }
                else
                { // three options left, excluding the current one of 4.
                    weightBefore += weights[currentDepartureId][nextArrivalId];
                    weightBefore += problem.TurnPenalties[currentTurn];

                    currentArrivalId   = currentArrivalId - (currentArrivalId % 2);
                    currentDepartureId = currentDepartureId - (currentDepartureId % 2);
                    for (var i = 0; i < 4; i++)
                    {
                        if (i == currentTurn)
                        {
                            continue;
                        }

                        int arrivalOffset, departureOffset;
                        DirectedHelper.ExtractOffset(i, out arrivalOffset, out departureOffset);
                        var newWeightAfter = weights[previousDepartureId][currentArrivalId + arrivalOffset]
                                             + weights[currentDepartureId + departureOffset][nextArrivalId]
                                             + problem.TurnPenalties[i];
                        if (newWeightAfter < weightAfter &&
                            newWeightAfter < weightBefore)
                        {
                            newDirectedId = DirectedHelper.BuildDirectedId(currentId, i);
                            weightAfter   = newWeightAfter;
                        }
                    }
                }

                // switch if a new directed if was found.
                if (newDirectedId != Constants.NOT_SET)
                {
                    solution.Replace(current, newDirectedId);
                }
            }

            var fitnessAfter = objective.Calculate(problem, solution);

            delta = objective.Subtract(problem, fitnessBefore, fitnessAfter);

            return(objective.CompareTo(problem, delta, objective.Zero) > 0);
        }
        /// <summary>
        /// Applies this operator.
        /// </summary>
        public bool Apply(TSProblem problem, TSPObjective objective, Tour solution, out float delta)
        {
            if (problem.Weights.Length <= 2)
            {
                delta = 0;
                return(false);
            }

            var weights = problem.Weights;

            delta = 0;

            // test switching directions in random order.
            if (_pool == null || solution.Count != _pool.Size)
            { // create a new pool.
                _pool = new RandomPool(solution.Count);
            }
            else
            { // just reset the existing one.
                _pool.Reset();
            }

            while (_pool.MoveNext())
            {
                var previous            = solution.GetDirectedId(_pool.Current);
                var previousDepartureId = DirectedHelper.ExtractDepartureId(previous);
                var current             = solution.GetNeigbour(previous);
                if (current == Constants.NOT_SET)
                { // last of open.
                    continue;
                }
                var next          = solution.GetNeigbour(current);
                var nextArrivalId = Constants.NOT_SET;
                if (next != Constants.NOT_SET)
                {
                    nextArrivalId = DirectedHelper.ExtractArrivalId(next);
                }
                int currentTurn, currentId, currentArrivalId, currentDepartureId;
                DirectedHelper.ExtractAll(current, out currentArrivalId, out currentDepartureId, out currentId, out currentTurn);

                var weightBefore  = weights[previousDepartureId][currentArrivalId];
                var weightAfter   = float.MaxValue;
                var newDirectedId = Constants.NOT_SET;
                if (next == Constants.NOT_SET)
                { // there is no next, only one option here:
                    // 0 or 1: arrival id offset = 0 OR
                    // 2 or 3: arrival id offset = 1
                    if (currentArrivalId % 2 == 0)
                    { // check turn = 2.
                        weightAfter = weights[previousDepartureId][currentArrivalId + 1];
                        if (weightAfter < weightBefore)
                        { // do the switch.
                            newDirectedId = DirectedHelper.BuildDirectedId(currentId, 2);
                        }
                    }
                    else
                    { // check turn = 0
                        weightAfter = weights[previousDepartureId][currentArrivalId - 1];
                        if (weightAfter < weightBefore)
                        { // do the switch.
                            newDirectedId = DirectedHelper.BuildDirectedId(currentId, 0);
                        }
                    }
                }
                else
                { // three options left, excluding the current one of 4.
                    weightBefore += weights[currentDepartureId][nextArrivalId];
                    weightBefore += problem.TurnPenalties[currentTurn];

                    currentArrivalId   = currentArrivalId - (currentArrivalId % 2);
                    currentDepartureId = currentDepartureId - (currentDepartureId % 2);
                    for (var i = 0; i < 4; i++)
                    {
                        if (i == currentTurn)
                        {
                            continue;
                        }

                        int arrivalOffset, departureOffset;
                        DirectedHelper.ExtractOffset(i, out arrivalOffset, out departureOffset);
                        var newWeightAfter = weights[previousDepartureId][currentArrivalId + arrivalOffset]
                                             + weights[currentDepartureId + departureOffset][nextArrivalId]
                                             + problem.TurnPenalties[i];
                        if (newWeightAfter < weightAfter &&
                            newWeightAfter < weightBefore)
                        {
                            newDirectedId = DirectedHelper.BuildDirectedId(currentId, i);
                            weightAfter   = newWeightAfter;
                        }
                    }
                }

                // switch if a new directed if was found.
                if (newDirectedId != Constants.NOT_SET)
                {
                    delta = weightAfter - weightBefore; // negative when improvement.

                    solution.Replace(current, newDirectedId);
                }
            }

            return(delta < 0);
        }