/*******************************************************************************
 * Copyright (c) 2002-2010 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package com.ibm.wala.util.graph.traverse;

import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import com.ibm.wala.util.graph.NumberedGraph;
import com.ibm.wala.util.intset.IntSet;
import com.ibm.wala.util.intset.IntSetAction;
import com.ibm.wala.util.intset.IntSetUtil;
import com.ibm.wala.util.intset.MutableIntSet;

public class FloydWarshall<T> {

  public interface GetPath<T> {
    List<T> getPath(T from, T to);
  }

  public interface GetPaths<T> {
    Set<List<T>> getPaths(T from, T to);
  }

  protected final NumberedGraph<T> G;
  
  public FloydWarshall(NumberedGraph<T> g) {
    G = g;
  }

  protected int edgeCost(int from, int to) {
    return 1;
  }
  
  protected void pathCallback(int i, int j, int k) {
    
  }
  
  int[][] allPairsShortestPaths() {
    final int[][] result = new int[G.getNumberOfNodes()][G.getNumberOfNodes()];

    for(int i = 0; i < result.length; i++) {
      for(int j = 0; j < result[i].length; j++) {
        result[i][j] = Integer.MAX_VALUE;
      }
    }
    
    for(T from : G) {
      final int fn = G.getNumber(from);
      IntSet tos = G.getSuccNodeNumbers(from);
      tos.foreach(new IntSetAction() {
        public void act(int x) {
          result[fn][x] = edgeCost(fn, x);
        }
      });
    }
    
    for(T kn : G) {
      int k = G.getNumber(kn);
      for(T in : G) {
        int i = G.getNumber(in);
        for(T jn : G) {
          int j = G.getNumber(jn);
          long newLen = (long)result[i][k] + (long)result[k][j];
          if (newLen <= result[i][j]) {
            pathCallback(i, j, k);
          }
          if (newLen < result[i][j]) {
            result[i][j] = (int)newLen;
          }
        }
      }
    }
    
    return result;
  }
  
  public static <T> int[][] shortestPathLengths(NumberedGraph<T> G) {
    return new FloydWarshall<T>(G).allPairsShortestPaths();
  }
  
  public static <T> GetPath<T> allPairsShortestPath(final NumberedGraph<T> G) {
     return new FloydWarshall<T>(G) {
       int[][] next = new int[G.getNumberOfNodes()][G.getNumberOfNodes()];

       @Override
       protected void pathCallback(int i, int j, int k) {
         next[i][j] = k;
       }
       
       private GetPath<T> doit() {
         for(int i = 0; i < next.length; i++) {
           for(int j = 0; j < next[i].length; j++) {
             next[i][j] = -1;
           }
         }
         
         final int[][] paths = allPairsShortestPaths();
         return new GetPath<T>() {
          public List<T> getPath(T from, T to) {
            int fn = G.getNumber(from);
            int tn = G.getNumber(to);
            if (paths[fn][tn] == Integer.MAX_VALUE) {
              throw new UnsupportedOperationException("no path from " + from + " to " + to);
            } else {
              int intermediate = next[fn][tn];
              if (intermediate == -1) {
                return Collections.emptyList();
              } else {
                T in = G.getNode(intermediate);
                List<T> result = new LinkedList<T>(getPath(from, in));
                result.add(in);
                result.addAll(getPath(in, to));
                return result;
              }
            }
          } 
         };
       }
     }.doit();
  }

  public static <T> GetPaths<T> allPairsShortestPaths(final NumberedGraph<T> G) {
    return new FloydWarshall<T>(G) {
      MutableIntSet[][] next = new MutableIntSet[G.getNumberOfNodes()][G.getNumberOfNodes()];

      @Override
      protected void pathCallback(int i, int j, int k) {
        if (next[i][j] == null) {
          next[i][j] = IntSetUtil.make();
        }
        next[i][j].add(k);
      }
      
      private GetPaths<T> doit() {        
        final int[][] paths = allPairsShortestPaths();
        return new GetPaths<T>() {
         public Set<List<T>> getPaths(final T from, final T to) {
           int fn = G.getNumber(from);
           int tn = G.getNumber(to);
           if (paths[fn][tn] == Integer.MAX_VALUE) {
             throw new UnsupportedOperationException("no path from " + from + " to " + to);
           } else {
             MutableIntSet intermediate = next[fn][tn];
             if (intermediate == null) {
               List<T> none = Collections.emptyList();
               return Collections.singleton(none);
             } else {
               final Set<List<T>> result = new HashSet<List<T>>();
              
               intermediate.foreach(new IntSetAction() {
                public void act(int x) {
                  T in = G.getNode(x);
                  for(List<T> pre : getPaths(from, in)) {
                    for(List<T> post : getPaths(in, to)) {
                      List<T> path = new LinkedList<T>(pre);
                      path.add(in);
                      path.addAll(post);
                      result.add(path);
                    }
                  }
                }           
               });
               
               return result;
             }
           }
         } 
        };
      }
    }.doit();
 }

}
