//=============================================================================
// Copyright 2006-2010 Daniel W. Dyer
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=============================================================================
package org.uncommons.watchmaker.examples.travellingsalesman;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.uncommons.maths.combinatorics.PermutationGenerator;
import org.uncommons.watchmaker.framework.FitnessEvaluator;

/**
 * Naive brute-force solution to the travelling salesman problem. It would take about
 * a day and a half to brute-force the 15-city travelling salesman problem on a home
 * computer using this implementation.  However, this is a not the best possible
 * implementation that is guaranteed to find a the shortest route (for example there
 * is no branch-and-bound optimisation).
 * @author Daniel Dyer
 */
public class BruteForceTravellingSalesman implements TravellingSalesmanStrategy
{
    private final DistanceLookup distances;


    /**
     * @param distances Information about the distances between cities.
     */
    public BruteForceTravellingSalesman(DistanceLookup distances)
    {
        this.distances = distances;
    }

    
    /**
     * {@inheritDoc}
     */
    public String getDescription()
    {
        return "Brute Force";
    }

    
    /**
     * To reduce the search space we will only consider routes that start
     * and end at one city (whichever is first in the collection).  All other
     * possible routes are equivalent to one of these routes since start city
     * is irrelevant in determining the shortest cycle.
     * @param cities The list of destinations, each of which must be visited
     * once.
     * @param progressListener Call-back for receiving the status of the
     * algorithm as it progresses.  May be null.
     * @return The shortest route that visits each of the specified cities once.
     */
    public List<String> calculateShortestRoute(Collection<String> cities,
                                               ProgressListener progressListener)
    {
        Iterator<String> iterator = cities.iterator();
        String startCity = iterator.next();
        Collection<String> destinations = new ArrayList<String>(cities.size() - 1);
        while (iterator.hasNext())
        {
            destinations.add(iterator.next());
        }

        FitnessEvaluator<List<String>> evaluator = new RouteEvaluator(distances);
        PermutationGenerator<String> generator = new PermutationGenerator<String>(destinations);
        long totalPermutations = generator.getTotalPermutations();
        long count = 0;
        List<String> shortestRoute = null;
        double shortestDistance = Double.POSITIVE_INFINITY;
        List<String> currentRoute = new ArrayList<String>(cities.size());
        while (generator.hasMore())
        {
            List<String> route = generator.nextPermutationAsList(currentRoute);
            route.add(0, startCity);
            double distance = evaluator.getFitness(route, null);
            if (distance < shortestDistance)
            {
                shortestDistance = distance;
                shortestRoute = new ArrayList<String>(route);
            }
            ++count;
            if (count % 1000 == 0 && progressListener != null)
            {
                progressListener.updateProgress(((double) count) / totalPermutations * 100);
            }
        }
        if (progressListener != null)
        {
            progressListener.updateProgress(100); // Finished.
        }
        return shortestRoute;
    }
}
