/*
 * Aleph Toolkit
 *
 * Copyright 1997, Brown University, Providence, RI.
 * 
 *                         All Rights Reserved
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose other than its incorporation into a
 * commercial product is hereby granted without fee, provided that the
 * above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Brown University not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 * 
 * BROWN UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ANY
 * PARTICULAR PURPOSE.  IN NO EVENT SHALL BROWN UNIVERSITY BE LIABLE FOR
 * ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
package aleph.examples;

import aleph.Aleph;
import aleph.Join;
import aleph.PE;
import aleph.RemoteFunction;
import aleph.RemoteThread;
import aleph.comm.*;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Hashtable;
import java.util.Random;

/**
 * Minimum Spanning Tree
 *
 * J. Bentley. "A Parallel Algorithm for Constructing Minimum Spanning Trees."
 * J. of Algorithms 1:51-59 (1980).
 *
 * Adapted from the Cid benchmark written by Martin C. Carlisle
 *
 * @author Weisheng Xu
 * @date   Nov 1997
 */
public class MST implements Serializable {

  private static Random random = new Random(1234); // for random graphs
  private static final int RANGE=2048; // largest weight

   // the static data member which will hold the different parts of the graph
   // on different PEs
  static Vertex myVertexList = null;

  static int numvert;
  static int numproc;
  static int perproc;

 public static void main(String args[]) {
   numproc = PE.numPEs();
   numvert = 64;		// default number of vertexes

   try {
     if (args.length > 0) {
       int num = Integer.parseInt(args[0]);
       if (num > 0)
	 numvert = num;
     }
   } catch (NumberFormatException e) {
     System.err.println("usage: mst <#num_of_vertics>");
     Aleph.exit();
   }

   // convert to a power of 2
   numproc = toPowerOf2(numproc);
   numvert = toPowerOf2(numvert);

   // in case we have fewer vertices
   if (numvert < numproc)
     numproc = numvert;

   // show the numproc & numvert
   // numvert may be different from the number given
   System.out.println("numproc="+numproc+" numvert="+numvert+"\n");
 
   perproc = numvert / numproc;

   MST mst = new MST();

   System.out.println("Make graph");
   long start = System.currentTimeMillis();

   try {
     mst.makeGraph();
   } catch (Exception e) {
     e.printStackTrace();
   }

   System.out.println("Elapsed time: " +
		      ((double) (System.currentTimeMillis() - start)) / 1000.0
		      + " seconds\n");
   System.out.flush();

   System.out.println("Compute mst");
   start = System.currentTimeMillis();

   long dist = 0;
   try {
     dist = mst.computeMst();
   } catch (Exception e) {
     e.printStackTrace();
     Aleph.exit();
   }
   System.out.println("Elapsed time: " +
		      ((double) (System.currentTimeMillis() - start)) / 1000.0
		      + " seconds\n");
   System.out.println("MST has cost " + dist);
   System.out.flush();
   Aleph.exit();
 }

   // return the closest number which is a power of 2
   static int toPowerOf2(int value) {
     int guess = 1;
     while (guess <= value)
       guess = (guess << 1);
     return (guess >> 1);
   }

   // randomly generate a graph of numvert nodes
   void makeGraph() {

     // we don't really return a global Graph object; the graph is distributed
     // uniformly on numproc PEs, each part denoted by -myVertexList- 
     // respectively

     System.out.println("phase 1");
     InitThread fork = new InitThread(numvert, perproc);
     Join       join = new Join();
     Enumeration e = PE.roundRobin();
     for (int i = 0; i < numproc; i++)
       fork.start((PE) e.nextElement(), join);
     join.waitFor();

     System.out.println("phase 2");
     AddEdgeThread fork1 = new AddEdgeThread(numvert, perproc);
     e = PE.allPEs();
     for (int i = 0; i < numproc; i++)
       fork1.start((PE) e.nextElement(), join);
     join.waitFor();

   }

   // create numvert/numproc nodes on each PE
   // the nodes on each PE are put in a linked list
   class InitThread extends RemoteThread {
     int numvert;
     int perproc;

     public InitThread(int numvert, int perproc) {
       this.numvert = numvert;
       this.perproc = perproc;
     }

     public void run() {
       // get my index
       int j = PE.thisPE().getIndex();

       Vertex v0 = null;

       if (perproc > 1) {
         v0 = new Vertex(Integer.MAX_VALUE, null, new Hashtable(), j*perproc+perproc-1);

         Vertex v;
         for (int i=perproc-2; i>0; i--) {
           v = new Vertex(Integer.MAX_VALUE, v0, new Hashtable(), j*perproc+i);
           v0 = v;
         }
       }

       myVertexList = new Vertex(Integer.MAX_VALUE, v0, new Hashtable(), j*perproc);
     }

   }

   // insert edge info to each node's hashtable
   class AddEdgeThread extends RemoteThread {
     int numvert;
     int perproc;
     public AddEdgeThread(int numvert, int perproc) {
       this.numvert = numvert;
       this.perproc = perproc;
     }
     
     public void run() {
       // get my index
       Vertex vj = myVertexList;
       for (; vj != null; vj = vj.next) {
         for (int i = 0; i < numvert; i++) {
            if (i != vj.myNo.intValue()) {
               int dist = computeDist(i, vj.myNo.intValue());
               // use unique vertex number as key
               (vj.edgeHash).put(new Integer(i), new Integer(dist));
            }
         }
       }
     }
   }


   int computeDist(int i, int j) {
     return(Math.abs(random.nextInt() % RANGE) + 1);
   }

   /**
    * BlueReturn class
    */
   class BlueReturn implements Serializable {
     public Vertex vert;
     public int dist;

     public BlueReturn(int dist, Vertex vert) {
        this.dist = dist;
        this.vert = vert;
     }

     public BlueReturn() {
        this.dist = Integer.MAX_VALUE;
        this.vert = null;
     }
   }

   // compute the MST; parts of the graph are located on the PEs where the 
   // computing will be done
   long computeMst() {
     long cost = 0;

     // insert first node on PE[0]
     Vertex inserted = myVertexList;
     myVertexList = inserted.next;
     numvert--;

     // announce insertion and find next one
     // since inserted won't be changed, we use a simple Java object instead
     // of a GlobalObject
     while (numvert > 0) {
        BlueRuleThread fork0 = new BlueRuleThread(
				   new Vertex(inserted), numproc, 0);
        BlueReturn br = (BlueReturn) fork0.run();
        inserted = br.vert;
        cost += br.dist;

        if (inserted == null) 
          System.out.println("inserted=null! numvert="+numvert+" cost="+cost);

        numvert--;
     }

     return cost;
   }

   class BlueRuleThread extends RemoteFunction {
     Vertex inserted;
     int nproc;
     int pn;

     public BlueRuleThread(Vertex inserted, int nproc, int pn) {
        this.inserted = inserted;
        this.nproc = nproc;
        this.pn = pn;
      }

      public Object run() {

        BlueRuleThread fork, fork1;
        Join join, join1; 
        BlueReturn retleft = new BlueReturn();
        BlueReturn retright = new BlueReturn(); 

        if (nproc > 1) {
          fork = new BlueRuleThread(inserted, nproc/2, pn+nproc/2);
	  join = new Join();
          fork.start(PE.getPE(pn+nproc/2), join);
	  try {
	    retleft = (BlueReturn) join.nextElement();
          } catch (NoSuchElementException e) {
	    System.out.println("left NoSuchElementException: "+e);
          }

          fork1 = new BlueRuleThread(inserted, nproc/2, pn);
	  join1 = new Join();
          fork1.start(PE.thisPE(), join1);
          try {
            retright = (BlueReturn) join1.nextElement();
          } catch (NoSuchElementException e) {
            System.out.println("right NoSuchElementException: "+e);
          }
          if (retleft.dist < retright.dist) {
              retright.dist = retleft.dist;
              retright.vert = retleft.vert;
          }

          // return retright
	  return (Object)retright;

        } else {
	  if (myVertexList != null) {
            if (inserted.myNo.equals(myVertexList.myNo)) {
               // remove it
               myVertexList = myVertexList.next;
            }
	  }

          return (Object) blueRule(inserted, myVertexList);
        }
      }


      BlueReturn blueRule(Vertex inserted, Vertex vlist) {

        if (vlist == null) {
           return new BlueReturn();
        }

        Vertex prev = vlist;
	// get copy of the first vertex
        BlueReturn retval = new BlueReturn(vlist.mindist, new Vertex(vlist));
        Hashtable hash = vlist.edgeHash;

        Integer ind = (Integer) hash.get(inserted.myNo);
        if (ind != null) {
           int dist = ind.intValue();
           if (dist < retval.dist) {
              vlist.mindist = dist;
              retval.dist = dist;
           }
        } else {
           System.out.println("Not found: "+vlist.myNo+"<->"+inserted.myNo);
           Aleph.exit(0);
        }

        int count = 0;
        // we are guaranteed that inserted is not the first one on the list
        for (Vertex tmp=vlist.next; tmp!=null; prev=tmp, tmp=tmp.next) {
           count++;
           if (inserted.myNo.equals(tmp.myNo)) {      // remove it
              prev.next = tmp.next;
           } else {
              hash = tmp.edgeHash;
              int dist0 = tmp.mindist;
              ind = (Integer) hash.get(inserted.myNo);
              if (ind != null) {
                 int dist = ind.intValue();
                 if (dist < dist0) {
                    tmp.mindist = dist;
                    dist0 = dist;
                 }
              } else
                 System.out.println("Not found: "+tmp.myNo+"<->"+inserted.myNo);
              if (dist0 < retval.dist) {
                retval.vert = new Vertex(tmp);    // copy the vertex
                retval.dist = dist0;
              }
           }
        }

        return retval;
      }
   }
class Vertex implements Serializable {
  public int mindist;
  public Integer myNo;		// unique vertex number
  public transient Vertex next;	// don't copy
  public transient Hashtable edgeHash; // don't copy

  public Vertex(int mindist, Vertex next, Hashtable edgeHash, int myNo) {
    this.mindist = mindist;
    this.next = next;
    this.edgeHash = edgeHash;
    this.myNo = new Integer(myNo);
  }

  // with this constructor, only 'mindist' & 'myNo' are copied
  // fields 'next' & 'edgeHash' are assigned null 
  // so that we can avoid unnecessary data communication during compustMST
  public Vertex(Vertex v) {
    this.mindist = v.mindist;
    this.next = null;
    this.edgeHash = null;
    this.myNo = v.myNo;
  }
}
}


