/* Learning Net - shlomy boshy 031868912
 * Reinforcement Learning network simulation  
 */
package learnnet;
import java.util.*;

public class HASHlookupTable extends Vector implements lookupTable {
  /* interface for the Q(neighbor,destination) table 
   * created empty - neighbors are added dynamically 
   * implementation:
      A vector of hashtables:
     each dest has a hashtable in the vector
     with: key=Node,value=routing time 
     This makes the actions more efficient.     
     A general hashtable of neighbors->transfer_time was added.
     A vector of dest->index in vector of hashtables
     (only values that were updated appear in the tables)
   */
          
   public static final double UNKNOWN_VALUE=0;          
          
   private Vector destIndex;   
     /* index for 'dest' in hashtables vector */
   public Hashtable neighbors;
   private Random RN;
     
   
   HASHlookupTable(){
     super();
     neighbors = new Hashtable();     
     destIndex = new Vector();     
     RN = new Random();
   }  

  public synchronized Node getMinNodeForDest(Node dest) throws NoSuchElementException {
   /* not implemented in this implementation */ 
   return null;
  }

                    
  public synchronized double getValue(Node N,Node dest) throws NoSuchElementException {
     /* returns Q(N,dest) */
     int i;     
     Hashtable h; 
     Object val;       
     
     /* check if neighbor */
     if (! neighbors.containsKey(N)) {
        Network.debugPrint("Node "+N+"is not my neighbor!");
        throw new NoSuchElementException() ;
      }
     /* get dest hash table */   
     if ((i = destIndex.indexOf(dest))==-1)
        return /*UNKNOWN_VALUE*/ N.BF.getValue(dest); 
     h=(Hashtable)this.elementAt(i);        
     /* get value by node (in dest hash table);*/
     if ((val=h.get(N))==null)
         return /*UNKNOWN_VALUE*/N.BF.getValue(dest);
         else return (new Double((String)val).doubleValue());             
        
  }
     
  public synchronized void setValue(Node N,Node dest,double value) throws NoSuchElementException{
    int i;
    Hashtable h;
    Object obj;
    
      /* check if neighbor */
     if (! neighbors.containsKey(N)) {
        Network.debugPrint("Node "+N+"is not my neighbor!");
        throw new NoSuchElementException();
        }
     /* get dest hash table */   
     if ((i = destIndex.indexOf(dest))==-1)
       { /* create new dest hashTable */
         h= new Hashtable();
         h.put(N,String.valueOf(value));
         addElement(h);
         destIndex.addElement(dest); 
       }
      else {/* add value to existing hashtable for dest */ 
       h = (Hashtable) this.elementAt(i);      
       /* update by formula */
       obj=h.put(N,String.valueOf(value));            
     }

   }  

   public synchronized void addNeighbor(Node N,double transferTime){              
       neighbors.put(N,String.valueOf(transferTime));
   }
   
   public synchronized Enumeration getKeys(Node dest) throws NoSuchElementException{
   /** keys list of neighbors for 'dest'. returns null if no keys for dest */
   int i;
   
   if ((i = destIndex.indexOf(dest))==-1)
      return null;
      else return ((Hashtable)elementAt(i)).keys();

 }
 
 public synchronized double getTransferTimeTo(Node N) throws NoSuchElementException {
   /* get transfer time from 'this' to neighbor 'N' */
   Object val;
   if ((val=neighbors.get(N))==null)
     throw new NoSuchElementException();
     else return (new Double((String)val).doubleValue());             
     
  }

 public synchronized Node getUnknownNeighbor(Node dest){
    Enumeration e;
    Node n;
 
    e = neighbors.keys();
    while (e.hasMoreElements()) {
      n = (Node)e. nextElement();
      if (getValue(n,dest) == UNKNOWN_VALUE)
          return n;
    }
   return null; /* all are known */
  
  }


  public synchronized Node getRandomNeighbor(){
     int j,i;
     Enumeration e = neighbors.keys();
     Node node;
          
       i = RN.nextInt();
       if (i<0) i=-i; 
       i = i%neighbors.size();      
       node = (Node)e.nextElement();
       for (j=0;j<i;j++) 
          node = (Node)e.nextElement();              
     return node;        
  }

  public int getNumNeighbors(){
    return neighbors.size();    
  }

  public int getNumKeys(Node dest){
    int i;   
    if ((i = destIndex.indexOf(dest))==-1)
      return 0; /* no neighbors for dest */
      else return ((Hashtable)elementAt(i)).size();
  }

  public void printQValues(){
     Enumeration e=elements(),eh;
     Hashtable destHash;
     int destNum=0;
     Node nb;     
     while (e.hasMoreElements()) {
       destHash = (Hashtable)e.nextElement();       
       Network.debugPrint("  dest="+destIndex.elementAt(destNum));
       eh=destHash.keys();
       while (eh.hasMoreElements()) {
         nb = (Node)eh.nextElement(); 
         Network.debugPrint("    neighbor="+nb+",value="+destHash.get(nb));
       
       }             
      destNum++; 
     }      
  }

 public Enumeration getNeighbors(){
    return neighbors.keys();        
  }
 

  public void printNeighbors(){
    Enumeration e=neighbors.keys();    
    Node node;
    while (e.hasMoreElements()){
       node = (Node)e.nextElement();
       Network.debugPrint("Neighbor="+node);
    }
  }
 
  public static void main(String args[]) {
     /** driver to check HASHlookupTable */
  HASHlookupTable HQ = new HASHlookupTable();
  Node x1=new Node(null,0);
  Node x2=new Node(null,1);
  Node x3=new Node(null,2);
  Node ds1=new Node(null,3);
  Node ds2=new Node(null,4);
  Enumeration e;
  double val;
  
  HQ.addNeighbor(x1,1);
  HQ.addNeighbor(x2,2);
  HQ.addNeighbor(x3,3);
  Network.debugPrint("Time to X2=" + HQ.getTransferTimeTo(x2));
  Network.debugPrint("Time to X3=" + HQ.getTransferTimeTo(x3));
  //Network.debugPrint("Time to DS1=" + HQ.getTransferTimeTo(ds1));

  //Network.debugPrint("Val=" + HQ.getValue(ds2,ds1)); //exception
  Network.debugPrint("Val=" + HQ.getValue(x1,ds1)); // 0
  HQ.setValue(x1,ds1,0.5);
  Network.debugPrint("Val=" + HQ.getValue(x1,ds1)); // 0.5
  HQ.setValue(x2,ds1,0.3);
  HQ.setValue(x2,ds2,0.1);
  Network.debugPrint("Val=" + HQ.getValue(x1,ds1)); // 0.5
  Network.debugPrint("Val=" + HQ.getValue(x2,ds1)); // 0.3
  Network.debugPrint("Val=" + HQ.getValue(x2,ds2)); // 0.1
  Network.debugPrint("Val=" + HQ.getValue(x1,ds2)); // 0

  
  Network.debugPrint("Neighbors of ds1");
  e = HQ.getKeys(ds1); 
  while (e.hasMoreElements()) {
        x1 = (Node)e.nextElement();
        val = HQ.getValue(x1,ds1);
        Network.debugPrint("Val=" + val); 
  }
  Network.debugPrint("Neighbors of ds2");
  e = HQ.getKeys(ds2); 
  while (e.hasMoreElements()) {
        x1 = (Node)e.nextElement();
        val = HQ.getValue(x1,ds2);
        Network.debugPrint("Val=" + val); 
  }

 }
 
}
