package com.jwetherell.algorithms.graph;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.jwetherell.algorithms.data_structures.Graph;

/**
 * Bellman-Ford's shortest path. Works on both negative and positive weighted
 * edges. Also detects negative weight cycles. Returns a tuple of total cost of
 * shortest path and the path.
 * 
 * Worst case: O(|V| |E|)
 * 
 * https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm
 * 
 * @author Justin Wetherell <phishman3579@gmail.com>
 */
public class BellmanFord {

    private BellmanFord() { }

    /**
     * Get shortest path for all vertices
     */
    public static Map<Graph.Vertex<Integer>, Graph.CostPathPair<Integer>> getShortestPaths(Graph<Integer> graph, Graph.Vertex<Integer> start) {
        final Map<Graph.Vertex<Integer>, List<Graph.Edge<Integer>>> paths = new HashMap<Graph.Vertex<Integer>, List<Graph.Edge<Integer>>>();
        final Map<Graph.Vertex<Integer>, Graph.CostVertexPair<Integer>> costs = new HashMap<Graph.Vertex<Integer>, Graph.CostVertexPair<Integer>>();

        getShortestPath(graph, start, paths, costs);

        final Map<Graph.Vertex<Integer>, Graph.CostPathPair<Integer>> map = new HashMap<Graph.Vertex<Integer>, Graph.CostPathPair<Integer>>();
        for (Graph.CostVertexPair<Integer> pair : costs.values()) {
            final int cost = pair.getCost();
            final Graph.Vertex<Integer> vertex = pair.getVertex();
            final List<Graph.Edge<Integer>> path = paths.get(vertex);
            map.put(vertex, new Graph.CostPathPair<Integer>(cost, path));
        }
        return map;
    }

    /**
     * Get shortest path to from 'start' to 'end' vertices
     */
    public static Graph.CostPathPair<Integer> getShortestPath(Graph<Integer> graph, Graph.Vertex<Integer> start, Graph.Vertex<Integer> end) {
        if (graph == null)
            throw (new NullPointerException("Graph must be non-NULL."));

        final Map<Graph.Vertex<Integer>, List<Graph.Edge<Integer>>> paths = new HashMap<Graph.Vertex<Integer>, List<Graph.Edge<Integer>>>();
        final Map<Graph.Vertex<Integer>, Graph.CostVertexPair<Integer>> costs = new HashMap<Graph.Vertex<Integer>, Graph.CostVertexPair<Integer>>();
        return getShortestPath(graph, start, end, paths, costs);
    }

    private static Graph.CostPathPair<Integer> getShortestPath(Graph<Integer> graph, 
                                                               Graph.Vertex<Integer> start, Graph.Vertex<Integer> end,
                                                               Map<Graph.Vertex<Integer>, List<Graph.Edge<Integer>>> paths,
                                                               Map<Graph.Vertex<Integer>, Graph.CostVertexPair<Integer>> costs) {
        if (end == null)
            throw (new NullPointerException("end must be non-NULL."));

        getShortestPath(graph, start, paths, costs);

        final Graph.CostVertexPair<Integer> pair = costs.get(end);
        final List<Graph.Edge<Integer>> list = paths.get(end);
        return (new Graph.CostPathPair<Integer>(pair.getCost(), list));
    }

    private static void getShortestPath(Graph<Integer> graph, 
                                        Graph.Vertex<Integer> start,
                                        Map<Graph.Vertex<Integer>, List<Graph.Edge<Integer>>> paths,
                                        Map<Graph.Vertex<Integer>, Graph.CostVertexPair<Integer>> costs) {
        if (graph == null)
            throw (new NullPointerException("Graph must be non-NULL."));
        if (start == null)
            throw (new NullPointerException("start must be non-NULL."));

        for (Graph.Vertex<Integer> v : graph.getVertices())
            paths.put(v, new ArrayList<Graph.Edge<Integer>>());

        // All vertices are INFINITY unless it's the start vertices
        for (Graph.Vertex<Integer> v : graph.getVertices())
            if (v.equals(start))
                costs.put(v, new Graph.CostVertexPair<Integer>(0, v));
            else
                costs.put(v, new Graph.CostVertexPair<Integer>(Integer.MAX_VALUE, v));

        boolean negativeCycleCheck = false;
        for (int i = 0; i < graph.getVertices().size(); i++) {
            // If it's the last vertices, perform a negative weight cycle check.
            // The graph should be finished by the size()-1 time through this loop.
            if (i == (graph.getVertices().size() - 1))
                negativeCycleCheck = true;

            // Compute costs to all vertices
            for (Graph.Edge<Integer> e : graph.getEdges()) {
                final Graph.CostVertexPair<Integer> pair = costs.get(e.getToVertex());
                final Graph.CostVertexPair<Integer> lowestCostToThisVertex = costs.get(e.getFromVertex());

                // If the cost of the from vertex is MAX_VALUE then treat as INIFINITY.
                if (lowestCostToThisVertex.getCost() == Integer.MAX_VALUE)
                    continue;

                final int cost = lowestCostToThisVertex.getCost() + e.getCost();
                if (cost < pair.getCost()) {
                    // Found a shorter path to a reachable vertex
                    pair.setCost(cost);

                    if (negativeCycleCheck) {
                        // Uhh ohh... negative weight cycle
                        throw new IllegalArgumentException("Graph contains a negative weight cycle.");
                    }

                    final List<Graph.Edge<Integer>> list = paths.get(e.getToVertex());
                    list.clear();
                    list.addAll(paths.get(e.getFromVertex()));
                    list.add(e);
                }
            }
        }
    }
}
