package network;

import java.util.Vector;
import java.util.Enumeration;

import general.Set;

/**
 * take a set of switches and a set of links, distribute the 
 * traffic between switches among different routes. 
 * upto to DistributedRoute.routeNumber different routes may be used to
 * distribute traffic, with their relative weight specified by
 * DistributedRoute.weights. Routes longer than DistributedRoute.ratio 
 * relative to the shortest route are not used.
 */
public class DistributedRoute {
  private Set switches;
  private int nSwitch;
  private Set links;
  private int nLink;
  private float[] weights;
  private Vector[] linksOnTheRoute;
  private int routeNumber;	// the maximum number of different routes to use.
  private float ratio;		// the maximum acceptable length of route relative
				// to the shortest route.

  // data structures used to calculate shortest pathes
  boolean[]	selected;
  Vector	switchVector;	// Vector representation of switches
  Link[][]	edge;		// matrix of links
  Vector[]	adjs;		// adjacent switches
  double[]	dist;		// distance matrix
  int[]		pred;		// predecessor matrix

  /** create an instance of DistributedRoute */
  public DistributedRoute(Set Switches, Set Links, float[] Weights, 
			  int RouteNumber, float Ratio) {
    this.switches = Switches;
    this.nSwitch = switches.size();
    this.switchVector = switches.getVector();
    this.links = Links;
    this.nLink = links.size();
    this.weights = Weights;
    this.routeNumber = RouteNumber;
    this.ratio = Ratio;
    this.linksOnTheRoute = new Vector[routeNumber];
  }

  /**
   * distribute the traffic among the links. This will change the
   * refPath and refParam of the links. 
   */
  public void apply() {
    // clear all references of the links
    Link link;
    for( Enumeration e = links.elements(); e.hasMoreElements();) {
      link = (Link)e.nextElement();
      link.refPath.removeAllElements();
      link.refParam.removeAllElements();
    }
    
    // for every Switch pair i and j, get all routes between them,
    // and mark on the links. Because of symmetrity, only Switch pairs
    // (i, j) such that i < j are calculated.
    Set linkSet;
    int i, j, n;
    double shortestPath, length;
    for (i = 0; i < nSwitch - 1; i++) {
      for (j = i + 1; j < nSwitch; j++) {
	linkSet = (Set)links.clone(); // this is the link set we can change
	shortestPath = Double.MAX_VALUE;
	for (n = 0; n < routeNumber; n++) {
	  initData(linkSet);
	  shortestPathes(i);
	  if (dist[j] < shortestPath)
	    shortestPath = dist[j];
	  else if (dist[j]/shortestPath >= ratio)
	    break;
	  linksOnTheRoute[n] = null;
	  try {
	    linksOnTheRoute[n] = getLinksOnTheRoute(i, j, linkSet);
	  }
	  catch (ClassNotFoundException e) {
	  }
	}
	// get the total weight of the different routes
	float sumWeight = 0;
	for (int tmp = 0; tmp < n; tmp++) {
	  sumWeight += weights[tmp];
	}
	// mark the links as used by traffic from Switch i to Switch j
	for (int tmp = 0; tmp < n; tmp++) {
	  markLinks(linksOnTheRoute[tmp], i, j, weights[tmp]/sumWeight);
	}
      }
    }
  }


  /** 
   * mark the Links as used by traffic from Switch i to Switch j
   * for weight w.
   */
  private void markLinks(Vector Links, int i, int j, float w) {
    SwitchPair sp1, sp2;
    // a path from sp.l to sp.r will pass the link from left to right
    sp1 = new SwitchPair( (Switch)switchVector.elementAt(i),
			  (Switch)switchVector.elementAt(j) );
    sp2 = new SwitchPair( (Switch)switchVector.elementAt(j),
			  (Switch)switchVector.elementAt(i) );
    int dst = j;
    for (int index = 0; index < Links.size(); index ++) {
      Link l = (Link)Links.elementAt(index);
      l.refParam.addElement(new Float(w));
      if (l.getL().equals(switchVector.elementAt(dst))) {
	l.refPath.addElement(sp2);
	dst = switchVector.indexOf(l.getR());
      }
      else {
	l.refPath.addElement(sp1);
	dst = switchVector.indexOf(l.getL());
      }
    }
  }

  /**
   * fills "adjs" and "edge" based on set of links: curLink,
   * also initilize dist[], pred[] and selected[].
   * to be used by Dijkstra's algorithm.
   */
  private void initData(Set curLinks) {
    int i, l, r;
    Link tmpLink;
    adjs = new Vector[nSwitch];
    edge = new Link[nSwitch][nSwitch];
    pred = new int[nSwitch];
    selected = new boolean[nSwitch];
    dist = new double[nSwitch];
    for (i = 0; i < nSwitch; i++) {
      adjs[i] = new Vector();
      pred[i] = -1;
      selected[i] = false;
      dist[i] = Double.MAX_VALUE;
    }
    for (Enumeration e = curLinks.elements(); e.hasMoreElements(); ) {
      tmpLink = (Link)e.nextElement();
      l = switchVector.lastIndexOf( tmpLink.getL() );
      r = switchVector.lastIndexOf( tmpLink.getR() );
      
      // every link corresponds to two edges
      edge[l][r] = tmpLink;
      edge[r][l] = tmpLink;
      adjs[l].addElement(new Integer(r));
      adjs[r].addElement(new Integer(l));
    }
  }

  /**
   * get single source shortest pathes.
   * edge[], dist[], pred[] should have already been initialized.
   */
  public void shortestPathes(int src) {
    dist[src] = 0;
    double min;
    int i, j, index = 0;
    for (int count = 0; count < nSwitch; count++) {
      // extract min dist from the src
      min = Double.MAX_VALUE;
      for (i = 0; i < nSwitch; i++) {
	if (selected[i] == false && dist[i] < min) {
	  min = dist[i];
	  index = i;
	}
      }
      selected[index] = true;
      for (Enumeration e = adjs[index].elements(); e.hasMoreElements();) {
	j = ((Integer)e.nextElement()).intValue();
	// do relax on all switches adjacent to the selected one
	if (edge[index][j] != null &&
	    dist[j] > dist[index] + edge[index][j].length) {
	  dist[j] = dist[index] + edge[index][j].length;
	  pred[j] = index;
	}
      }
    }
  }


  /** get Links on the path from Switch i to Switch j.
   *  also remove these Links away from the linkSet */
  private Vector getLinksOnTheRoute(int i, int j, Set Links) throws ClassNotFoundException {
    Link tmpLink;
    Vector theLinks = new Vector();
    int src = i;
    int dst = j;
    int pre;
    while ( (pre = pred[dst]) != -1 ) {
      tmpLink = edge[pre][dst];
      theLinks.addElement(tmpLink);
      Links.remove(tmpLink.getName());
      dst = pre;
    }
    return theLinks;
  } // end of getLinksOnTheRoute()


} // end of class


