/// <summary>
        /// Executes a solver procedure.
        /// </summary>
        /// <param name="problem"></param>
        /// <returns></returns>
        internal override MaxTimeSolution Solve(MaxTimeProblem problem)
        {
            MaxTimeCalculator calculator = new MaxTimeCalculator(problem);

            // get the seed customers.
            ICollection<int> seeds = _seed_selector.SelectSeeds(
                problem, _k);
            double[] weights = new double[seeds.Count];

            // start the seed routes.
            List<int> selectable_customers = problem.Customers;
            MaxTimeSolution routes = new MaxTimeSolution(
                problem.Size, true);
            foreach (int seed in seeds)
            {
                routes.Add(seed);
                selectable_customers.Remove(seed);
            }

            if (!routes.IsValid())
            {
                throw new Exception();
            }

            // keep a list of cheapest insertions.
            IInsertionCosts costs = new BinaryHeapInsertionCosts();

            // keep looping until all customers have been placed.
            while (selectable_customers.Count > 0)
            {
                // try and place into every route.
                CheapestInsertionResult best_result = new CheapestInsertionResult();
                best_result.Increase = float.MaxValue;
                int best_route_idx = -1;

                CheapestInsertionResult best_result_above_max = new CheapestInsertionResult();
                best_result_above_max.Increase = float.MaxValue;
                int best_route_above_max_idx = -1;

                for (int route_idx = 0; route_idx < routes.Count; route_idx++)
                {
                    IRoute current_route = routes.Route(route_idx);

                    // choose the next customer.
                    CheapestInsertionResult result =
                        CheapestInsertionHelper.CalculateBestPlacement(problem, current_route, selectable_customers, costs);
                    if (result.Customer == result.CustomerAfter)
                    {
                        throw new Exception();
                    }
                    // get the current weight
                    double weight = weights[route_idx];
                    if (result.Increase < best_result.Increase)
                    {
                        if (weight + result.Increase + calculator.DeliveryTime < problem.Max.Value)
                        { // route will still be inside bounds.
                            best_result = result;
                            best_route_idx = route_idx;
                        }
                        else
                        { // route will become above max.
                            if (result.Increase < best_result_above_max.Increase)
                            {
                                best_result_above_max = result;
                                best_route_above_max_idx = route_idx;
                            }
                        }
                    }
                }

                // do the placement if a placement is found without max violation.
                // else do the placement in the above max route.
                CheapestInsertionResult placement_result = new CheapestInsertionResult();
                placement_result.Increase = double.MaxValue;
                int placement_result_idx = -1;
                if (best_route_idx >= 0)
                { // best placement found.
                    placement_result = best_result;
                    placement_result_idx = best_route_idx;
                }
                else
                { // best placement found but only above max.
                    placement_result = best_result_above_max;
                    placement_result_idx = best_route_above_max_idx;
                }

                // do the actual placement.
                weights[placement_result_idx] = calculator.CalculateOneRouteIncrease(
                    weights[placement_result_idx], placement_result.Increase);
                selectable_customers.Remove(placement_result.Customer);
                //routes.Route(placement_result_idx).InsertAfterAndRemove(
                //    placement_result.CustomerBefore, placement_result.Customer, placement_result.CustomerAfter);
                routes.Route(placement_result_idx).InsertAfter(
                    placement_result.CustomerBefore, placement_result.Customer);

                if (!routes.IsValid())
                {
                    throw new Exception();
                }
            }

            return routes;
        }
        /// <summary>
        /// Tries to relocate customers using the source the same as the CrossExchange but places customers using cheapest insertion.
        /// </summary>
        /// <param name="problem"></param>
        /// <param name="solution"></param>
        /// <param name="route1_idx"></param>
        /// <param name="route2_idx"></param>
        /// <param name="max"></param>
        /// <returns></returns>
        public bool Improve(MaxTimeProblem problem, MaxTimeSolution solution,
            int route1_idx, int route2_idx, double max)
        {
            int max_window = 10;

            IRoute route1 = solution.Route(route1_idx);
            IRoute route2 = solution.Route(route2_idx);

            double total_before = problem.Time(solution.Route(route1_idx)) +
                problem.Time(solution.Route(route2_idx));

            int route1_customers = route1.Count;
            int route2_customers = route2.Count;

            double[] route1_cumul = problem.TimeCumul(route1);
            double[] route2_cumul = problem.TimeCumul(route2);

            // build all edge weights.
            List<Edge> route1_edges = new List<Edge>(route1.Edges());
            List<Edge> route2_edges = new List<Edge>(route2.Edges());
            double[] route1_weights = new double[route1_edges.Count];
            for (int idx = 0; idx < route1_edges.Count; idx++)
            {
                Edge edge = route1_edges[idx];
                route1_weights[idx] = problem.WeightMatrix[edge.From][edge.To];
            }
            double[] route2_weights = new double[route2_edges.Count];
            for (int idx = 0; idx < route2_edges.Count; idx++)
            {
                Edge edge = route2_edges[idx];
                route2_weights[idx] = problem.WeightMatrix[edge.From][edge.To];
            }

            List<EdgePair> route2_pairs = new List<EdgePair>();
            for (int i_idx = 0; i_idx < route2_edges.Count - 2; i_idx++)
            {
                Edge i = route2_edges[i_idx];
                double i_weight = route2_weights[i_idx];
                double weight_before_i = route2_cumul[i_idx];

                int k_idx_max = route2_edges.Count;
                if (k_idx_max > i_idx + 2 + max_window)
                {
                    k_idx_max = i_idx + 2 + max_window;
                }
                for (int k_idx = i_idx + 2; k_idx < k_idx_max; k_idx++)
                {
                    Edge k = route2_edges[k_idx];
                    double k_weight = route2_weights[k_idx];
                    double weight_after_k = route2_cumul[route2_cumul.Length - 1] - route2_cumul[k_idx + 1];
                    double weight_between_route = route2_cumul[k_idx] - route2_cumul[i_idx + 1];

                    route2_pairs.Add(new EdgePair()
                    {
                        First = i,
                        FirstWeight = i_weight,
                        Second = k,
                        SecondWeight = k_weight,
                        Between = new List<int>(route2.Between(i.To, k.From)),
                        WeightTotal = i_weight + k_weight,
                        WeightAfter = weight_after_k,
                        WeightBefore = weight_before_i,
                        WeightBetween = weight_between_route,
                        CustomersBetween = k_idx - i_idx
                    });
                }
            }

            // try to relocate all and find best pair.
            double route1_weight = route1_cumul[route1_cumul.Length - 1];
            EdgePair best = null;
            CheapestInsertionResult best_result = new CheapestInsertionResult();
            double best_extra = double.MaxValue;
            foreach (EdgePair pair2 in route2_pairs)
            {
                // calculate cheapest insertion.
                CheapestInsertionResult result =
                    CheapestInsertionHelper.CalculateBestPlacement(problem, route1,
                    pair2.First.To, pair2.Second.From);

                double extra_route2 = problem.WeightMatrix[pair2.First.From][pair2.Second.To];

                // check if the result has a net-decrease.
                if (result.Increase + extra_route2 < pair2.WeightTotal - 0.01)
                { // there is a net decrease.
                    // calculate the real increase.
                    double new_weight = problem.Time(route1_weight + result.Increase + pair2.WeightBetween, route1_customers +
                        pair2.CustomersBetween);

                    // check the max.
                    if (new_weight < max && new_weight < best_extra)
                    { // the route is smaller than max.
                        best_extra = new_weight;
                        best_result = result;
                        best = pair2;
                    }
                }
            }

            if (best != null)
            {
                if (route2.Last == best.Second.To)
                {
                    //throw new Exception();
                }
                route2.ReplaceEdgeFrom(best.First.From, best.Second.To);

                int previous = best_result.CustomerBefore;
                foreach (int customer in best.Between)
                {
                    route1.ReplaceEdgeFrom(previous, customer);

                    previous = customer;
                }
                route1.ReplaceEdgeFrom(previous, best_result.CustomerAfter);

                // check validity.
                if (!route2.IsValid())
                {
                    throw new Exception();
                }
                if (!route1.IsValid())
                {
                    throw new Exception();
                }

                if (route1.Count + route2.Count != route1_customers + route2_customers)
                {
                    throw new Exception();
                }

                double total_after = problem.Time(solution.Route(route1_idx)) +
                    problem.Time(solution.Route(route2_idx));
                if (total_after >= total_before)
                {
                    throw new Exception("this is not an improvement!");
                }

                return true;
            }
            return false;
        }
        /// <summary>
        /// Returns the customer that least increases the length of the given route.
        /// </summary>
        /// <param name="problem"></param>
        /// <param name="route"></param>
        /// <param name="customers"></param>
        /// <param name="costs"></param>
        /// <returns></returns>
        public static CheapestInsertionResult CalculateBestPlacement(
            IProblemWeights problem,
            IRoute route,
            ICollection<int> customers,
            IInsertionCosts costs)
        {
            // initialize the best placement result.
            CheapestInsertionResult best = new CheapestInsertionResult();
            best.Increase = float.MaxValue;

            // loop over all customers in the route.
            if (route.Count > 0)
            { // there have to be at least two customers.
                IEnumerator<int> route_enumerator = route.GetEnumerator();
                if (!route_enumerator.MoveNext())
                { // this route is empty
                    throw new ArgumentException("Route needs to be initialized with at least two customers!");
                }
                int customer_before = route_enumerator.Current;
                int customer_after = -1;
                while (route_enumerator.MoveNext())
                { // keep moving!
                    customer_after = route_enumerator.Current;
                    InsertionCost cost = costs.PopCheapest(customer_before, customer_after);
                    bool found = false;
                    while(!found)
                    { // test if the costs are empty.
                        if (cost == null)
                        { // re-initialize the costs with all customers under consideration.
                            foreach (int customer in customers)
                            {
                                costs.Add(customer_before, customer_after, customer,
                                    (float)problem.WeightMatrix[customer_before][customer] +
                                    (float)problem.WeightMatrix[customer][customer_after] -
                                    (float)problem.WeightMatrix[customer_before][customer_after]);
                            }

                            // pop the cheapest again!
                            cost = costs.PopCheapest(customer_before, customer_after);
                        }
                        else if (customers.Contains(cost.Customer))
                        { // the customer is found!
                            found = true;
                        }
                        else
                        { // pop the cheapest again!
                            cost = costs.PopCheapest(customer_before, customer_after);
                        }
                    }

                    if (cost.Cost < best.Increase)
                    { // the costs is better than the current cost!
                        best = new CheapestInsertionResult()
                            {
                                Customer = cost.Customer,
                                CustomerAfter = customer_after,
                                CustomerBefore = customer_before,
                                Increase = cost.Cost
                            };
                        if (best.Increase == 0)
                        { // immidiately return if smaller than epsilon.
                            return best;
                        }
                    }

                    // set the after to the before.
                    customer_before = route_enumerator.Current;
                }

                // if the round is a round try first-last.
                if (route.IsRound)
                { // the route is a round!
                    customer_after = route.First;
                    InsertionCost cost = costs.PopCheapest(customer_before, customer_after);
                    bool found = false;
                    while (!found)
                    { // test if the costs are empty.
                        if (cost == null)
                        { // re-initialize the costs with all customers under consideration.
                            foreach (int customer in customers)
                            {
                                costs.Add(customer_before, customer_after, customer,
                                    (float)problem.WeightMatrix[customer_before][customer] +
                                    (float)problem.WeightMatrix[customer][customer_after] -
                                    (float)problem.WeightMatrix[customer_before][customer_after]);
                            }

                            // pop the cheapest again!
                            cost = costs.PopCheapest(customer_before, customer_after);
                        }
                        else if (customers.Contains(cost.Customer))
                        { // the customer is found!
                            found = true;
                        }
                        else
                        { // pop the cheapest again!
                            cost = costs.PopCheapest(customer_before, customer_after);
                        }
                    }

                    if (cost.Cost < best.Increase)
                    { // the costs is better than the current cost!
                        best = new CheapestInsertionResult()
                        {
                            Customer = cost.Customer,
                            CustomerAfter = customer_after,
                            CustomerBefore = customer_before,
                            Increase = cost.Cost
                        };
                        if (best.Increase == 0)
                        { // immidiately return if smaller than epsilon.
                            return best;
                        }
                    }
                }
            }
            else
            { // route needs to be initialized.
                throw new ArgumentException("Route needs to be initialized with at least two customers!");
            }

            // return result.
            return best;
        }
        /// <summary>
        /// Searches for the best place to insert the given two customers abstracting the distance between them.
        /// </summary>
        /// <param name="problem"></param>
        /// <param name="route"></param>
        /// <param name="from"></param>
        /// <param name="to"></param>
        /// <returns></returns>
        public static CheapestInsertionResult CalculateBestPlacement(
            IProblemWeights problem,
            IRoute route,
            int from,
            int to)
        {
            // initialize the best placement result.
            CheapestInsertionResult result
                = new CheapestInsertionResult();
            result.Customer = -1; // this property is useless here!
            result.CustomerAfter = -1;
            result.CustomerBefore = -1;
            result.Increase = double.MaxValue;

            if (!route.IsEmpty)
            {
                double new_weight = double.MaxValue;
                double old_weight = 0;

                int previous = -1;
                int first = -1;
                foreach (int current in route)
                {
                    if (previous >= 0)
                    { // only if the previous is known.
                        // calculate the new weights.
                        new_weight = problem.Weight(previous, from)
                                + (problem.Weight(to, current));

                        // calculate the old weights.
                        old_weight = problem.Weight(previous, current);

                        // calculate the difference.
                        double difference = new_weight - old_weight;
                        if (result.Increase > difference)
                        {
                            result.CustomerAfter = current;
                            result.CustomerBefore = previous;
                            result.Increase = difference;
                        }
                    }
                    else
                    { // store the first city for later.
                        first = current;
                    }

                    // go to the next loop.
                    previous = current;
                }

                // test last-to-first if the route is a round.
                if (route.IsRound)
                {
                    // calculate the new weights.
                    new_weight = problem.Weight(previous, from)
                            + (problem.Weight(to, first));

                    // calculate the old weights.
                    old_weight = problem.Weight(previous, first);

                    // calculate the difference.
                    double difference = new_weight - old_weight;
                    if (result.Increase > difference)
                    {
                        result.CustomerBefore = previous;
                        result.CustomerAfter = first;
                        result.Increase = difference;
                    }
                }
            }
            else
            { // route needs to be initialized.
                throw new ArgumentException("Route needs to be initialized with at least one customer!");
            }

            // return result.
            return result;
        }
        /// <summary>
        /// Searches for the best place to insert the given customer.
        /// </summary>
        /// <param name="problem"></param>
        /// <param name="route"></param>
        /// <param name="customer"></param>
        /// <returns></returns>
        public static CheapestInsertionResult CalculateBestPlacement(
            IProblemWeights problem,
            IRoute route,
            int customer)
        {
            // initialize the best placement result.
            double[][] weights = problem.WeightMatrix;

            CheapestInsertionResult result
                = new CheapestInsertionResult();
            result.Customer = customer;
            result.CustomerAfter = -1;
            result.CustomerBefore = -1;
            result.Increase = double.MaxValue;

            double difference = double.MaxValue;
            if (!route.IsEmpty)
            {
                double new_weight = double.MaxValue;
                double old_weight = 0;

                int previous = -1;
                int first = -1;
                foreach (int current in route)
                {
                    if (previous >= 0)
                    { // the previous customer exists.
                        // only if the previous is known.

                        // calculate the old weights.
                        //old_weight = problem.Weight(previous, current);
                        old_weight = weights[previous][current];

                        // calculate the new weights.
                        //new_weight = problem.Weight(previous, customer);
                        new_weight = weights[previous][customer];

                        //new_weight = new_weight +
                        //    problem.Weight(customer, current);
                        new_weight = new_weight +
                            weights[customer][current];

                        // calculate the difference.
                        difference = new_weight - old_weight;
                        if (result.Increase > difference)
                        {
                            result.CustomerAfter = current;
                            result.CustomerBefore = previous;
                            result.Increase = difference;

                            // if the difference is equal to or smaller than epsilon we have search enough.
                            if (difference == 0)
                            {
                                // result is the best we will be able to get.
                                return result;
                            }
                        }
                    }
                    else
                    { // store the first city for later.
                        first = current;
                    }

                    // go to the next loop.
                    previous = current;
                }

                // set the pervious to the last.
                //previous = route.Last;

                // test last-to-first if the route is a round.
                if (route.IsRound)
                {
                    // calculate the new weights.
                    //new_weight = problem.Weight(previous, customer)
                    //        + (problem.Weight(customer, first));
                    new_weight = weights[previous][customer]
                        + weights[customer][first];

                    // calculate the old weights.
                    //old_weight = problem.Weight(previous, first);
                    old_weight = weights[previous][first];

                    // calculate the difference.
                    difference = new_weight - old_weight;
                    if (result.Increase > difference)
                    {
                        result.CustomerAfter = first;
                        result.CustomerBefore = previous;
                        result.Increase = difference;
                    }
                }
            }
            else
            { // route needs to be initialized.
                throw new ArgumentException("Route needs to be initialized with at least one customer!");
            }

            // return result.
            return result;
        }
        /// <summary>
        /// Executes a solver procedure.
        /// </summary>
        /// <param name="problem"></param>
        /// <returns></returns>
        internal override MaxTimeSolution Solve(MaxTimeProblem problem)
        {
            // create the solution.
            MaxTimeSolution solution = new MaxTimeSolution(problem.Size, true);

            // keep placing customer until none are left.
            List<int> customers = new List<int>(problem.Customers);

            double max = problem.Max.Value - (problem.Max.Value * _delta_percentage);

            // keep a list of cheapest insertions.
            IInsertionCosts costs = new BinaryHeapInsertionCosts();
            double percentage = _threshold_percentage;
            while (customers.Count > 0)
            {
                // try and distribute the remaining customers if there are only a few left.
                if (customers.Count < problem.Size * percentage)
                {
                    bool succes = true;
                    while (succes && customers.Count > 0)
                    {
                        succes = false;
                        CheapestInsertionResult best = new CheapestInsertionResult();
                        best.Increase = float.MaxValue;
                        int best_idx = -1;
                        for (int route_idx = 0; route_idx < solution.Count; route_idx++)
                        {
                            IRoute route = solution.Route(route_idx);
                            CheapestInsertionResult result =
                                CheapestInsertionHelper.CalculateBestPlacement(problem, route, customers);
                            if (best.Increase > result.Increase)
                            {
                                best = result;
                                best_idx = route_idx;
                            }
                        }

                        IRoute best_route = solution.Route(best_idx);
                        double route_time = problem.Time(best_route);
                        if (route_time + best.Increase < max)
                        { // insert the customer.
                            best_route.InsertAfter(best.CustomerBefore, best.Customer);
                            customers.Remove(best.Customer);

                            this.Improve(problem, solution, max, best_idx);

                            succes = true;
                        }
                    }
                }

                // select a customer using some heuristic.
                if (customers.Count > 0)
                {
                    // select a customer using some heuristic.
                    int customer = -1;
                    if (_use_seed)
                    { // use a seeding heuristic.
                        customer = this.SelectSeed(problem, problem.MaxTimeCalculator, solution, customers);
                    }
                    else
                    { // just select a random customer.
                        customer = customers[Tools.Math.Random.StaticRandomGenerator.Get().Generate(customers.Count)];
                    }
                    customers.Remove(customer);

                    // start a route r.
                    IRoute current_route = solution.Add(customer);
                    solution[solution.Count - 1] = 0;

                    while (customers.Count > 0)
                    {
                        //OsmSharp.Tools.Output.OutputStreamHost.WriteLine("{0}/{1} placed!",
                        //    customers.Count, problem.Size);

                        // calculate the best placement.
                        CheapestInsertionResult result;
                        if (_use_seed_cost)
                        { // use the seed cost; the cost to the seed customer.
                            result = CheapestInsertionHelper.CalculateBestPlacement(problem, current_route, customers,
                                customer, _lambda);

                            // calculate the 'real' increase.
                            result.Increase = (problem.WeightMatrix[result.CustomerBefore][result.Customer] +
                                problem.WeightMatrix[result.Customer][result.CustomerAfter]) -
                                problem.WeightMatrix[result.CustomerBefore][result.CustomerAfter];
                        }
                        else
                        { // just use cheapest insertion.
                            result = CheapestInsertionHelper.CalculateBestPlacement(problem, current_route, customers, costs);
                        }

                        // calculate the new weight.
                        solution[solution.Count - 1] = problem.Time(solution.Route(solution.Count - 1));
                        double potential_weight = problem.MaxTimeCalculator.CalculateOneRouteIncrease(solution[solution.Count - 1],
                            result.Increase);
                        // cram as many customers into one route as possible.
                        if (potential_weight < max)
                        {
                            // insert the customer, it is
                            customers.Remove(result.Customer);
                            current_route.InsertAfter(result.CustomerBefore, result.Customer);

                            // free some memory in the costs list.
                            costs.Remove(result.CustomerBefore, result.CustomerAfter);

                            // update the cost of the route.
                            solution[solution.Count - 1] = potential_weight;

                            // improve if needed.
                            if (((problem.Size - customers.Count) % _k) == 0)
                            { // an improvement is descided.
                                // apply the inter-route improvements.
                                int count_before = solution.Route(solution.Count - 1).Count;

                                solution[solution.Count - 1] = this.ImproveIntraRoute(problem,
                                    current_route, solution[solution.Count - 1]);
                                if (!solution.IsValid())
                                {
                                    throw new Exception();
                                }
                                int count_after = solution.Route(solution.Count - 1).Count;

                                // also to the inter-improvements.
                                current_route = this.Improve(problem, solution, max, solution.Count - 1);
                            }
                        }
                        else
                        {// ok we are done!
                            this.Improve(problem, solution, max, solution.Count - 1);

                            // break the route.
                            break;
                        }
                    }
                    //else
                    //{// ok we are done!
                    //    solution[solution.Count - 1] = this.ImproveIntraRoute(problem,
                    //        current_route, solution[solution.Count - 1]);

                    //    if (!solution.IsValid())
                    //    {
                    //        throw new Exception();
                    //    }
                    //    int count_after = solution.Route(solution.Count - 1).Count;

                    //    this.Improve(problem, solution, max, solution.Count - 1);

                    //    // break the route.
                    //    break;
                    //}
                }
            }

            // remove empty routes.
            for (int route_idx = solution.Count - 1; route_idx >= 0; route_idx--)
            {
                if (solution.Route(route_idx).IsEmpty)
                {
                    solution.Remove(route_idx);
                }
            }

            return solution;
        }