// Carnegie Mellon University
//   Information Networking Institute and
//   School of Computer Science
//
// Master Thesis: A Monitoring Tool for Overlay Network
// By: TungFai Chan and Annie Cheng
//
// File: NeighborTableManager.java
// Path: eventbase/neighbor
// Description: Manage table recorded collection of neighbor tables.
//              Each node in the group has it's own collection of neighbors.

package eventbase.neighbor;

import java.util.*;
import eventbase.*;

public class NeighborTableManager {

  final String OBJNAME = "NeighborTableManager";
  // collection of neighbor table index at each neighbor
  private Vector memberList;

  public NeighborTableManager() {
    this.memberList = new Vector();
  }

  public void addMember (OverlayHost host) {
    if (!existMember(host)) {
      NbrTable nbrTab = new NbrTable(host);
      this.memberList.addElement(nbrTab);
    }else {
      System.err.println(OBJNAME + ": Member already exist");
    }
  }

  public boolean existMember (OverlayHost host) {
    for (Enumeration e=memberList.elements(); e.hasMoreElements();)
      if (((NbrTable)e.nextElement()).getHost().equals(host))
        return true;

    return false;
  }

  public void addNeighbor(double timestamp, OverlayHost host,
                          OverlayHost neighbor) {
    NbrTable nbrTab = getNbrTable(host);
    if (nbrTab != null)
      nbrTab.addNeighbor(timestamp, neighbor);
  }

  public void deleteNeighbor(double timestamp, OverlayHost host,
                             OverlayHost neighbor, int reason) {
    NbrTable nbrTab = getNbrTable(host);
    if (nbrTab != null)
      nbrTab.deleteNeighbor(timestamp, neighbor, reason);
  }

  public void printAllNbrTable() {
    NbrTable tbl;
    for (Enumeration e = memberList.elements(); e.hasMoreElements();)
      ((NbrTable) e.nextElement()).printNbrTable();
  }

  // for Display purpose
  // returns a Vector of DisplayNeighborTableEntry elements
  public Vector getNbrTableForDisplay(int hostid, double time) {
    OverlayHost tmpHost = new OverlayHost(hostid);
    NbrTable nbr_table = getNbrTable(tmpHost);
    if (nbr_table == null) {
      System.err.println("ERROR:" + OBJNAME +
                         ":getNbrTableForDisplay - neighbor table requested not exist!");
      return null;
    }
    return nbr_table.getTableForDisplay(time);
  }

  private NbrTable getNbrTable(OverlayHost member) {
    NbrTable ret_val;
    for (Enumeration e=memberList.elements(); e.hasMoreElements();) {
      ret_val = (NbrTable)e.nextElement();
      if (ret_val.getHost().equals(member))
        return ret_val;
    }
    System.err.println("ERROR:" + OBJNAME + " NBR Table NOT FOUND");
    return null;
  }

  public int getMemorySize() {
    int retVal = 0;
    for (Enumeration e=memberList.elements(); e.hasMoreElements();)
      retVal += ((NbrTable)e.nextElement()).getMemorySize();
    return retVal;
  }
}

class NbrTable implements NeighborType{
  final String OBJNAME = "NbrTable";
  private OverlayHost member;
  private Vector nbrList;

  NbrTable(OverlayHost host) {
    this.member = host;
    this.nbrList = new Vector();
  }

  OverlayHost getHost() {
    return member;
  }

  void addNeighbor(double time, OverlayHost host) {
    NeighborTableEntry tempEntry = getNeighborTableEntry(host);
    if (tempEntry == null) { // neighbor not on the list
      NeighborTableEntry newEntry = new NeighborTableEntry (host, time);
      nbrList.add(newEntry);
    }
    else { // neighbor must left and come back again now
      tempEntry.addTimeJoinNeighbor(time);
    }
  }

  void deleteNeighbor(double time, OverlayHost host, int reason) {
    NeighborTableEntry tempEntry = getNeighborTableEntry(host);
    if (tempEntry != null) { // neighbor not on the list
      tempEntry.addTimeCeaseNeighbor(time, reason);
    }else { // error
    //  08/10 - ahcheng
    //  because dead info is propagated to every node, not just to neighbors
    //  as a result, it is possible if the node that's dead is not on your
    //  neighbor list.
    //  System.err.println("deleteNeighbor(), deleting neighbor that doesn't exist");
    }
  }

  boolean exsitNeighbor(OverlayHost host) {
    for (Enumeration e= nbrList.elements(); e.hasMoreElements();)
      if (((NeighborTableEntry)e.nextElement()).getNeighbor().equals(host))
        return true;
    return false;
  }

  // return the index to neighbor
  int findNeighbor(OverlayHost host) {
    for (int i = 0; i < nbrList.size(); i++) {
      if (((NeighborTableEntry)nbrList.get(i)).getNeighbor().equals(host))
        return i;
    }
    return -1;
  }

  // return neighbor table entry
  NeighborTableEntry getNeighborTableEntry(OverlayHost host) {
    NeighborTableEntry ret_val;
    for (Enumeration e=nbrList.elements(); e.hasMoreElements();) {
      ret_val = (NeighborTableEntry)e.nextElement();
      if (ret_val.getNeighbor().equals(host))
        return ret_val;
    }
    return null;
  }

  Vector getTableForDisplay(double time) {
      NeighborTableEntry entry;
      Vector retVal = new Vector();
      for (Enumeration e=nbrList.elements(); e.hasMoreElements();) {
        entry = (NeighborTableEntry)e.nextElement();
        JoinLeavePair jlpair = entry.getJoinLeavePair(time);
        if (jlpair != null) {
          double leaveTime = jlpair.getTimeCeaseNeighbor();
          int reasonToLeave = jlpair.getReasonToLeave();
          if (leaveTime > time)  { // neighbor not leave yet
            leaveTime = -1;
            reasonToLeave = UNKNOWN;
          }
          DisplayNeighborTableEntry element =
            new DisplayNeighborTableEntry(entry.getNeighbor(),
                                          jlpair.getTimeSinceNeighbor(),
                                          leaveTime,
                                          reasonToLeave);
          retVal.addElement(element);
        }
      }
      return retVal;
  }

  int getMemorySize() {
    int retVal = 0;
    for (Enumeration e = nbrList.elements(); e.hasMoreElements();)
        retVal += ((NeighborTableEntry)e.nextElement()).getMemorySize();
    return retVal;
  }


  void printNbrTable() {
    System.out.println();
    System.out.println("Neighbor Table for " + member.getName() +
                       " id(" + member.getID() + ")");
    for (Enumeration e = nbrList.elements(); e.hasMoreElements();)
        ((NeighborTableEntry)e.nextElement()).print();
    System.out.println();
  }
}

