/// <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>
        /// <returns></returns>
        public static CheapestInsertionResult CalculateBestPlacement(
            IProblemWeights problem,
            IRoute route,
            ICollection<int> customers)
        {
            IInsertionCosts costs = new BinaryHeapInsertionCosts();

            return CheapestInsertionHelper.CalculateBestPlacement(problem, route, customers, costs);
        }
        /// <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>
        /// 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="seed_customer"></param>
        /// <param name="seed_customer_ratio"></param>
        /// <returns></returns>
        public static CheapestInsertionResult CalculateBestPlacement(
            IProblemWeights problem,
            IRoute route,
            ICollection<int> customers,
            int seed_customer,
            double seed_customer_ratio)
        {
            IInsertionCosts costs = new BinaryHeapInsertionCosts();

            // 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)
                            {
                                float cost_quantity = (float)problem.WeightMatrix[customer_before][customer] +
                                    (float)problem.WeightMatrix[customer][customer_after] -
                                    (float)problem.WeightMatrix[customer_before][customer_after];
                                float seed_cost = (float)problem.WeightMatrix[seed_customer][customer] +
                                    (float)problem.WeightMatrix[customer][seed_customer];
                                costs.Add(customer_before, customer_after, customer,
                                    (float)(cost_quantity + (seed_customer_ratio * seed_cost)));
                            }

                            // 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)
                            {
                                float cost_quantity = (float)problem.WeightMatrix[customer_before][customer] +
                                    (float)problem.WeightMatrix[customer][customer_after] -
                                    (float)problem.WeightMatrix[customer_before][customer_after];
                                float seed_cost = (float)problem.WeightMatrix[seed_customer][customer] +
                                    (float)problem.WeightMatrix[customer][seed_customer];
                                costs.Add(customer_before, customer_after, customer,
                                    (float)(cost_quantity + (seed_customer_ratio * seed_cost)));
                            }

                            // 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>
        /// 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;
        }