package overlay;
import java.io.PrintStream;
import java.util.Iterator;
import java.util.Set;
import ow.dht.ByteArray;
import rodex.RodexApplication;
import uk.ac.ic.doc.automed.DSRConfiguration;
import uk.ac.ic.doc.automed.p2p.P2PRegistry;
import uk.ac.ic.doc.automed.p2p.communication.PortInUseException;
import uk.ac.ic.doc.automed.p2p.overlay.OverlayController;
import uk.ac.ic.doc.automed.p2p.overlay.OverlayException;
import uk.ac.ic.doc.automed.p2p.state.PeerStateHandler;
import uk.ac.ic.doc.automed.util.Debugger;
import uk.ac.ic.doc.automed.util.LogOutputHandler;
import uk.ac.ic.doc.automed.util.Logger;
import uk.ac.ic.doc.rodex.p2p.DirectoryServicePeer;
import uk.ac.ic.doc.rodex.p2p.Peer;

import ow.dht.ValueInfo;
import ow.id.ID;
import uk.ac.ic.doc.automed.p2p.overlay.OverlayToolKit;
import utils.AutoMedUtils;

/**
 * OverlayExample.java<br><br>
 *
 * Shows how to construct a test application with several peers joining a
 * Chord-based overlay network
 *
 * @author Duc M Le  <a href="mailto:dmle@doc.ic.ac.uk"><i>dmle@doc.ic.ac.uk</i></a>
 * <br>Created on August 7, 2006
 * <br>Department of Computing, Imperial College
 */

public class OverlayExample {
  private RodexApplication ra;
  private DirectoryServicePeer dsp;
  private Peer[] peers;
  private static final long sleepTime = 50;
  
  private OverlayController oc;
  
  private int debugSection = Debugger.DEBUG_P2P_OVERLAY;
  private int debugLevel = Debugger.DEBUG_LEVEL_STD;
  protected static PrintStream out = System.out;
  
  /**
   * Creates a new instance of OverlayExample
   */
  public OverlayExample() throws Exception {
    ra = new RodexApplication();
  }
  
  /**
   * Return the set of peers
   */
  public Peer[] getPeers() {
    return peers;
  }
  
  /**
   * support an additional tag joinDS
   */
  public void startAutoMedPeers(int numPeers, boolean useRandomID,
    String hostPort, boolean withDSP, boolean joinDS) throws Exception {
    startAutoMedPeers(0, numPeers, useRandomID, hostPort, withDSP, joinDS);
  }
  
  public void startAutoMedPeers(int startNum, int numPeers, boolean useRandomID,
    String hostPort, boolean withDSP, boolean joinDS) throws Exception {
    // start a directory service peer
    
    oc = Peer.getOverlayController();
    
    if (peers == null)
      peers = new Peer[numPeers];
    
    // start overlay peers first
    int endIndex = numPeers-1;
    if (withDSP) {
      endIndex=numPeers-2;
      startPeers(startNum, numPeers-1, useRandomID, hostPort);

      // start DSP
      Logger.debug("","","Starting directory service peer...", debugSection, debugLevel);

      String dspName = "DSP";
      dsp = ra.runDirectoryServicePeer(dspName);

      // the DSP becomes the last node of the overlay
      ID dspKey = (useRandomID) ? OverlayToolKit.createRandomID() :
        OverlayToolKit.createIDFromNumber(numPeers-1);

      dsp.joinOverlay(dspKey, oc.getVirtualHosts()[numPeers-2],
        oc.getVirtualHosts()[numPeers-1]);

      peers[numPeers-1] = dsp;
    } else {
      startPeers(startNum, numPeers, useRandomID, hostPort);
    }
      
    // start AutoMedPeer processes
    if (joinDS) {
      startAutoMedPeerService(peers, endIndex);    
    }    
  }
  
  /**
   * Use withSuperPeer flag to decide whether or not to start a super-peer
   */
  public void startAutoMedPeers(int numPeers, boolean useRandomID,
    String hostPort, boolean withDSP) throws Exception {
    boolean joinDS = true;
    startAutoMedPeers(numPeers, useRandomID, hostPort, withDSP, joinDS);
  }

  /**
   * Start the AutoMed's non-structured service for each peer
   */
  private void startAutoMedPeerService(Peer[] peers, int endIndex) throws Exception {
    boolean answeredInitRep = false;
    for (int i = 0; i <= endIndex; i++) {
      // use the same default dsr config file
      Peer p = peers[i];
      String pname = p.getName();
      
      
      // initialise local AutoMed repository
      // skip asking question if the answer is 'no' for the first peer
      if (!answeredInitRep && AutoMedUtils.dialogYesNo("Re-initialise local repository?", 0) == 1) {
        AutoMedUtils.banner("Initialising peer repository "+DSRConfiguration.getDefaultConfigFileName()+"...");
        Peer.initRepository();
      }
      answeredInitRep = true;
      
      // init registry service
      Logger.debug("","","Initialising registry attached to peer "+pname+"...",
        debugSection, debugLevel);
      
      try {
        P2PRegistry registry = p.initRegistry();
      } catch (PortInUseException piue) {
        // registry is already created locally? try to use it
        Logger.debug(pname, "", "Registry seems to be available, using it instead!",
          debugSection, debugLevel);
      }
      
      // find directory service peer
      Logger.debug("","","Peer "+pname+": sending hellos to find DS...",
        debugSection, debugLevel);
      p.initHelloProtocol();
      
      while (p.getPeerState() < PeerStateHandler.STATE_HELLO_COMPLETE) {
        try {
          Thread.sleep(sleepTime);
          if (Debugger.atSectionDebugLevel(debugSection,
            debugLevel)) {
            System.out.print(".");
          }
        } catch (InterruptedException ie) {
          //
        }
      }
      System.out.println("\n");
      
      // authentication
      Logger.debug("","","Peer "+pname+": authenticating to DS...",
        debugSection, debugLevel);
      p.initAuthentication();
      p.authenticate();
      
      // messaging service
      Logger.debug("","","Initialising messaging service for peer "+pname,
        debugSection, debugLevel);
      p.initMessagingPeer();
    }    
  }
  
  /**
   * Overlay peers
   */
  public void startPeers(int numPeers, boolean useRandomID) throws Exception {
    startPeers(numPeers, useRandomID, null);
  }
  
    public void startPeers(int numPeers, boolean useRandomID, 
    String hostPort) throws Exception {
      startPeers(0, numPeers, useRandomID, hostPort);
    }
    
  public void startPeers(int startNum, int numPeers, boolean useRandomID, 
    String hostPort) throws Exception {
    // start a directory service peer
    out.println("Starting a set of "+numPeers+ " peers...");
    
    oc = Peer.getOverlayController();
    
    if (peers == null)
      peers = new Peer[numPeers];
    
    // start normal peers with overlay capability
    for (int i = 0; i < numPeers; i++) {
      String pname = oc.getVirtualHosts()[i];
      ID key = null;
      if (useRandomID) { // get a random ID
        key = OverlayToolKit.createRandomID();
      } else { // generate ID from peer's index
        int peerIndex = ((startNum + i) == 0) ? 1 : startNum + i;
        key = OverlayToolKit.createIDFromNumber(peerIndex);
      }
      
      // use the same default dsr config file
      String dsrCfgFile = DSRConfiguration.getDefaultConfigFileName();
      Peer p = Peer.newPeerProcess(pname, new PeerStateHandler(),
        dsrCfgFile);
      
      // overlay
      Logger.debug("","","Initialising Overlay Service for peer "+pname,
        debugSection, debugLevel);
      
      // a new host joins the previous host's DHT
      if (i > 0) {
        p.joinOverlay(key, oc.getVirtualHosts()[i-1],
          oc.getVirtualHosts()[i]);
      } else if (hostPort == null) {  // first host starts its own DHT
        p.joinOverlay(key, null,
          oc.getVirtualHosts()[i]); 
      } else if (hostPort != null) {  // first host to join with an existing DHT
        p.joinOverlay(key, hostPort,
          oc.getVirtualHosts()[i]);         
      }
      
      Logger.debug("OverlayExample","startPeers","peer ID: "+p.getIDString(),
        debugSection, debugLevel);
      
      peers[i] = p;
    }
  }
  
  public void displayOverlayStatus() {
    // show self address
    for (int i = 0; i < peers.length; i++) {
      displayOverlayStatus(i);
    }
  }
  
  public void displayOverlayStatus(int peerIndex) {
    // show self address
    
    Peer p = peers[peerIndex];
    
//    if (Debugger.atSectionDebugLevel(debugSection, debugLevel)) {
      Logger.debug("", "", "Displaying status for peer " + p.getName());
      OverlayController oc = p.getOverlayController();
      
      out.print("ID and address:\n ");
      out.println(p.getSelfIDAddressPair());
      
      // show routing table
      out.println("Routing table:");
      out.println(p.getRoutingTableString());
      
      // show last key and route
      out.print("Last key: ");
      out.println(p.getLastKeyString());
      out.print("Last route: ");
      out.println(p.getLastRouteString());
//    }
  }
  
  public void displayLocalKeys(int peerIndex) {
    Peer p = peers[peerIndex];
    OverlayController oc = p.getOverlayController();
    StringBuffer sb = new StringBuffer();
    if (Debugger.atSectionDebugLevel(debugSection, debugLevel)) {
      Logger.debug("", "", "Displaying local keys at peer " + p.getName());
      
      Set ids = p.getMyKeys();
      if (ids != null) {
        for (Iterator it = ids.iterator(); it.hasNext();) {
          ID id = (ID) it.next();
          sb.append("ID: " + id.toString()).append("\n");
        }
      } else {
        sb.append("Empty!");
      }
      
      out.println(sb.toString());
    }
  }
  
  public void displayLocalValues(int peerIndex) {
    Peer p = peers[peerIndex];
    OverlayController oc = p.getOverlayController();
    StringBuffer sb = new StringBuffer();
    if (Debugger.atSectionDebugLevel(debugSection, debugLevel)) {
      Logger.debug("", "", "Displaying ALL local values at peer " + p.getName());
      
      Set ids = p.getMyKeys();
      if (ids != null) {
        for (Iterator it = ids.iterator(); it.hasNext();) {
          ID id = (ID) it.next();
          Set vals = p.getMyValues(id);
          sb.append("ID: " + id.toString()).append("\n");
          if (vals != null) {
            for (Iterator itv = vals.iterator(); itv.hasNext();) {
              ValueInfo vi = (ValueInfo) itv.next();
              sb.append("  -> value: " + vi.getValue() + " " + (vi.getTTL()/1000) + "(s)")
              .append("\n");
            }
          } else {
            sb.append("   empty").append("\n");
          }
        }
      } else {
        sb.append("No keys!");
      }
      
      out.println(sb.toString());
    }
  }
  
  public int displayPeerList() {
    String[] names = new String[peers.length];
    for (int i = 0; i < peers.length; i++) {
      names[i] = peers[i].getName();
    }
    out.println("Choose a peer to proceed:");
    return AutoMedUtils.displayItemList(names);
  }
  
  public void get(int peerIndex, final String key) throws OverlayException {
    //
    Peer p = peers[peerIndex];
    Set vals = p.get(key);
    displayVals("key="+key,vals);
  }
  
  public void get(int peerIndex, final ID key) throws OverlayException {
    Peer p = peers[peerIndex];
    Set vals = p.get(key);
    displayVals("key="+key.toString(),vals);
  }
  
  public void displayVals(String query, Set vals) {
    StringBuffer sb = new StringBuffer();
    sb.append("------------------ Look up result: ---------------------------------\n");
    sb.append("Query: "+query+"\n\n");
    if (vals != null && vals.size() > 0) {
      for (Iterator it = vals.iterator(); it.hasNext();) {
        ValueInfo v = (ValueInfo) it.next();
        sb.append("value: " + v.getValue() + " " + (v.getTTL() / 1000) + "(s)");
        
        ByteArray secret = v.getSecret();
        if (secret != null) {
          sb.append(" " + secret);
        }
        
        sb.append("\n");
      }
    } else {
      sb.append("Not found! \n");
    }
    sb.append("-------------------------------------------------------------------\n");
    
    out.println(sb);
  }
  
  public void put(int peerIndex, String key, String value, int ttl) throws OverlayException {
    //
    Peer p = peers[peerIndex];
    Logger.debug("","","Peer "+p.getName()+": Putting test (key,value) = ("+key+","+value+").",
      debugSection, debugLevel);
    p.put(key, value, ttl);
  }
  
  public void finalize() {
    //
    for (int i = 0; i < peers.length; i++) {
      peers[i].finalize();
    }
    
    peers = null;
    dsp = null;
    ra = null;
  }
  
  public static void main(String[] args) throws Exception {
    
    Debugger.setSectionDebugLevel(Debugger.DEBUG_HELLO, Debugger.DEBUG_LEVEL_ALL);
//    Debugger.setSectionDebugLevel(Debugger.DEBUG_DIRECTORY, Debugger.DEBUG_LEVEL_ERROR);
//    Debugger.setSectionDebugLevel(Debugger.DEBUG_P2P, Debugger.DEBUG_LEVEL_PACKET);
    Debugger.setSectionDebugLevel(Debugger.DEBUG_P2P_OVERLAY, Debugger.DEBUG_LEVEL_STD);
    Debugger.setSectionDebugLevel(Debugger.DEBUG_GENERAL, Debugger.DEBUG_LEVEL_STD);
    Logger.setOutputHandler(new LogOutputHandler(LogOutputHandler.CONSOLE +
      LogOutputHandler.FILE));
    
    // IMPORTANT: Make sure these hosts are configured in /etc/hosts file
    int numPeers = OverlayController.DEF_NUM_HOSTS;
    
    Peer.initOverlay(numPeers);
    
    OverlayExample oex = null;
    do {
      
      if (oex != null && AutoMedUtils.dialogYesNo("Start a new set of peers ?", 0) == 1) {
        oex.finalize();
        
        oex = new OverlayExample();
        oex.startPeers(numPeers, true);
      } else if (oex == null) {
        oex = new OverlayExample();
        oex.startPeers(numPeers, true);
      }
      
      if (AutoMedUtils.dialogYesNo("Display Overlay Network status ?", 0) == 1) {
        oex.displayOverlayStatus();
      }
      
      int peerIndex  = oex.displayPeerList();
      
      String tk = "tkey_"+System.currentTimeMillis();
      String tv = "tval_"+System.currentTimeMillis();
      int ttl = 60 * 60 * 1000; // 1h
      
      if (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": put a test (key,value) ?", 1) == 1) {
        OverlayController.waitBeforeAccess();
        oex.put(peerIndex, tk, tv, ttl);
      }
      
      if (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": display overlay status ?", 1) == 1) {
        oex.displayOverlayStatus(peerIndex);
      }
      
      if (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": get local keys ?", 1) == 1) {
        oex.displayLocalKeys(peerIndex);
      }
      
      if (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": get ALL local values ?", 1) == 1) {
        oex.displayLocalValues(peerIndex);
      }
      
      if (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": get key '"+tk+"' from network ?", 1) == 1) {
        oex.get(peerIndex, tk);
      }
      
      if (AutoMedUtils.dialogYesNo("Peer "+peerIndex+": try a different key ?", 1) == 1) {
        String key = AutoMedUtils.getCmdLineInput("Enter key: ");
        oex.get(peerIndex, key);
      }
      
      
    } while (AutoMedUtils.dialogYesNo("Run again?", 0) == 1);
    
    AutoMedUtils.banner("~END~");
    System.exit(0);
  }
}
