// Rep1.nw -- simulating a self-stabilizing replication algorithm
// with the simdistalg package.
// (c) 1997 by Felix Gaertner <theedge@rbg.informatik.tu-darmstadt.de>
// DO NOT EDIT! See file Rep1.nw instead.

import eduni.simjava.*;
import eduni.simanim.*;
import simdistalg.*;
import java.util.Vector;
import java.util.Enumeration;

public class Rep1 extends Simdistalg_applet {
  public void anim_layout() {
    super.anim_layout();
    // parameters for placement spacing
    int v_offset = 30;
    int h_offset = 50;
    int grid_offset = 150;

    Sim_system.add(new Replication_node("A", new String[] { "B", "C" },
        h_offset + grid_offset, v_offset));
    Sim_system.add(new Replication_node("B", new String[] { "A", "C", "D" },
        h_offset, v_offset + grid_offset));
    Sim_system.add(new Replication_node("C", new String[] { "A", "B", "E" },
        h_offset + 2*grid_offset, v_offset + grid_offset));
    Sim_system.add(new Replication_node("D", new String[] { "B", "E" },
        h_offset, v_offset + 2*grid_offset));
    Sim_system.add(new Replication_node("E", new String[] { "C", "D" },
        h_offset + 2*grid_offset, v_offset + 2*grid_offset));
    Sim_node.connect();
  }
  public static int ord(String name) {
    if (name.equals("A")) return 0;
    if (name.equals("B")) return 1;
    if (name.equals("C")) return 2;
    if (name.equals("D")) return 3;
    if (name.equals("E")) return 4;
    return -1;
  }
  public static String chr(int num) {
    switch (num) {
      case 0: return("A");
      case 1: return("B");
      case 2: return("C");
      case 3: return("D");
      case 4: return("E");
    }
    return("ZZZZ"); // TODO
  }
}
class Replication_node extends Sim_node implements Sim_faulty_node {

  String current_name = get_name();
  String current_nbs = "{}";
  String current_state = "up";
  String previous_state = "up";
  String saved_state;
  /* const */ String[] N; // physical neighbours
  Vector neighbours = new Vector(); // up-neighbours
  int j;
  Timeout_counter[] timeouts;
  String current_parent = "-";
  String current_level = "-";
  /* const */ final int n = 5;
  int l;         // level in tree
  String p;      // parent in tree
  int[] tilde_l; // view of neighbour's levels
  Timeout_counter l2_timeout = new Timeout_counter();
  String current_r = "(-)";
  int r = 0; // replication data
  int[] tilde_r;  // view of other's r
  Timeout_counter l3_timeout = new Timeout_counter();
  Sim_uniform_obj private_random = new Sim_uniform_obj("general", 0, 1, 100);

  Replication_node(String name, String[] nb) {
    super(name, nb, nb);
    add_param(new Anim_param("name", Anim_param.VALUE, name, -9,-5));
    add_param(new Anim_param("nbs", Anim_param.NAME_VALUE, "{}", 30, 0));
    add_param(new Anim_param("state", Anim_param.STATE, "up", 0, 0));
    add_param(new Anim_param("parent", Anim_param.NAME_VALUE, "-", 0, 40));
    add_param(new Anim_param("level", Anim_param.NAME_VALUE, "-", 0, 50));
    add_param(new Anim_param("r", Anim_param.VALUE, "(-)", 0, -5));
    timeouts = new Timeout_counter[nb.length];
    N = new String[nb.length];
    for (int i=0; i<nb.length; i++) {
      N[i] = nb[i];
      timeouts[i] = new Timeout_counter();
      register_timeout(timeouts[i]);
    }
    if (get_name().equals("A")) {
      l = 0;
      p = "A";
    } else { // ``random'' state?!
      l = n;
      p = get_name();
      tilde_l = new int[n];
    }
    register_timeout(l2_timeout);
    current_parent = p;
    current_level = "" + l;
    if (get_name().equals("A")) {
      // nothing
    } else { // non-root initialization:
      tilde_r = new int[n];
    }
    register_timeout(l3_timeout);
    current_r = "(" + r + ")";
  }
  Replication_node(String name, String[] nb, int x, int y) {
    super(name, nb, nb, x, y);
    add_param(new Anim_param("name", Anim_param.VALUE, name, -9,-5));
    add_param(new Anim_param("nbs", Anim_param.NAME_VALUE, "{}", 30, 0));
    add_param(new Anim_param("state", Anim_param.STATE, "up", 0, 0));
    add_param(new Anim_param("parent", Anim_param.NAME_VALUE, "-", 0, 40));
    add_param(new Anim_param("level", Anim_param.NAME_VALUE, "-", 0, 50));
    add_param(new Anim_param("r", Anim_param.VALUE, "(-)", 0, -5));
    timeouts = new Timeout_counter[nb.length];
    N = new String[nb.length];
    for (int i=0; i<nb.length; i++) {
      N[i] = nb[i];
      timeouts[i] = new Timeout_counter();
      register_timeout(timeouts[i]);
    }
    if (get_name().equals("A")) {
      l = 0;
      p = "A";
    } else { // ``random'' state?!
      l = n;
      p = get_name();
      tilde_l = new int[n];
    }
    register_timeout(l2_timeout);
    current_parent = p;
    current_level = "" + l;
    if (get_name().equals("A")) {
      // nothing
    } else { // non-root initialization:
      tilde_r = new int[n];
    }
    register_timeout(l3_timeout);
    current_r = "(" + r + ")";
  }
  public void guarded_commands() {
    saved_state = current_state;
    if (current_state.equals(previous_state)) {
      if (current_state.equals("reset")) {
        current_state = "up";
      }
      else if (current_state.equals("perturbed")) {
        current_state = "up";
      }
    }
    previous_state = saved_state;
    sim_trace(1, "P " + current_name + " " + current_nbs + " " +
        current_state + " " + current_parent + " " + current_level + " " +
        current_r);
    if (!current_state.equals("down")) {
      j = rand_ltd(1,N.length);
      if (timeouts[j-1].timeout()) {
        trace("timed out for neighbour " + N[j-1]);
        if (neighbours.contains(N[j-1])) {
          trace("removing " + N[j-1] + " from the list");
          neighbours.removeElement(N[j-1]);
          current_nbs = "{";
          Enumeration nbss = neighbours.elements();
          if (nbss.hasMoreElements()) {
            current_nbs = current_nbs + ((String) nbss.nextElement());
            while (nbss.hasMoreElements()) {
              current_nbs = current_nbs + "," + ((String) nbss.nextElement());
            }
          }
          current_nbs = current_nbs + "}";
        }
        out(N[j-1], null, "!", 1);
        timeouts[j-1].reset_timeout();
      }
      for (int j=1; j<=N.length; j++) {
        if (in_guard(N[j-1], 1)) {
          in(N[j-1]); // get empty message
          trace("got `living' message from " + N[j-1]);
          if (!neighbours.contains(N[j-1])) {
            trace("adding " + N[j-1] + " to the list");
            neighbours.addElement(N[j-1]);
            current_nbs = "{";
            Enumeration nbss = neighbours.elements();
            if (nbss.hasMoreElements()) {
              current_nbs = current_nbs + ((String) nbss.nextElement());
              while (nbss.hasMoreElements()) {
                current_nbs = current_nbs + "," + ((String) nbss.nextElement());
              }
            }
            current_nbs = current_nbs + "}";
          }
          out(N[j-1], null, "!", 1);
          timeouts[j-1].reset_timeout();
        }
      }
      if (get_name().equals("A")) {
        if (l2_timeout.timeout()) {
          shout(0, 2);
          l2_timeout.reset_timeout();
        }
        for (Enumeration e = neighbours.elements(); e.hasMoreElements(); ) {
          String nextnb = (String) e.nextElement();
          if (in_guard(nextnb, 2)) {
            Object x = in(nextnb, 2); // purge message
            // shout(0, 2);
            // l2_timeout.reset_timeout();
          }
        }
      } else {
        if (!neighbours.contains(p)) {
          l = n;
          trace("noticed that parent " + p + " is down");
          shout(l,2);
          l2_timeout.reset_timeout();
        }
        if (l!=n && l!=tilde_l[Rep1.ord(p)]+1 && tilde_l[Rep1.ord(p)]!=n ) {
          l = tilde_l[Rep1.ord(p)]+1;
          trace("adjusting level to " + l + ", parent is on " + tilde_l[Rep1.ord(p)]);
          shout(l,2);
          l2_timeout.reset_timeout();
        }
        if (l!=n && tilde_l[Rep1.ord(p)]==n) {
          l = n;
          trace("noticed error state in parent " + p);
          shout(l,2);
          l2_timeout.reset_timeout();
        }
        if (neighbours.size()>0) {
          // select random neighbour
          int rnb = rand_ltd(1, neighbours.size());
          // get it's name
          String rnn = (String) neighbours.elementAt(rnb-1);
          // calculate it's index in tilde_l
          int rix = Rep1.ord(rnn);
          //trace("node " + rnn + " is index " + rix);
          if (l==n && tilde_l[rix] < n-1) {
            l = tilde_l[rix] + 1;
            p = rnn;
            trace("choosing new parent " + rnn + " (my new level is " + l +
                ", parent is on " + tilde_l[rix] + ")");
            shout(l,2);
            l2_timeout.reset_timeout();
          }
        }
        if (l2_timeout.timeout()) {
          trace("timed out, telling everybody I'm on level " + l);
          shout(l,2);
          l2_timeout.reset_timeout();
        }
        for (Enumeration e = neighbours.elements(); e.hasMoreElements(); ) {
          String nextnb = (String) e.nextElement();
          if (in_guard(nextnb, 2)) {
            int k = ((Integer) in(nextnb, 2)).intValue();
            tilde_l[Rep1.ord(nextnb)] = k;
            trace("updating view of level of " + nextnb + " to " + k);
            // l2_timeout.reset_timeout();
          }
        }
        current_level = "" + l;
        current_parent = p;
      }
      if (get_name().equals("A")) {
        if (l3_timeout.timeout()) {
          shout(r, 3);
          l3_timeout.reset_timeout();
        }
        for (Enumeration e = neighbours.elements(); e.hasMoreElements(); ) {
          String nextnb = (String) e.nextElement();
          if (in_guard(nextnb, 3)) {
            // shout(r, 3);
            l3_timeout.reset_timeout();
          }
        }
        if (Sim_system.clock()>r*35) {
          r = r+1;
          trace("broadcasting " + r);
          current_r = "(" + r + ")";
          shout(r, 3);
          l3_timeout.reset_timeout();
        }
      } else { // non-root initialization:
        if (r!=tilde_r[Rep1.ord(p)]) {
          r = tilde_r[Rep1.ord(p)];
          current_r = "(" + r + ")";
          trace("received value " + r);
          shout(r,3);
          l3_timeout.reset_timeout();
        }
        if (l3_timeout.timeout()) {
          shout(r,3);
          l3_timeout.reset_timeout();
        }
        for (Enumeration e = neighbours.elements(); e.hasMoreElements(); ) {
          String nextnb = (String) e.nextElement();
          if (in_guard(nextnb, 3)) {
            int k = ((Integer) in(nextnb, 3)).intValue();
            tilde_r[Rep1.ord(nextnb)] = k;
            // l3_timeout.reset_timeout();
          }
        }
      }
    } else{ // node is down
      Sim_event ev = new Sim_event();
      while (sim_waiting() > 0) {
        sim_select(Sim_system.SIM_ANY, ev);
      }
    }
  }

  public int rand_ltd(int low, int high) {
    return (int)(Math.round(((high-low+1)*private_random.sample())-0.5) + low);
  }
  public void shout(int level, int layer) {
    for (Enumeration e = neighbours.elements(); e.hasMoreElements(); ) {
      String nextnb = (String) e.nextElement();
      out(nextnb, new Integer(level), "" + level, layer);
    }
  }
  public void down() {
    trace("down() called");
    current_state = "down";
  }
  public void up() {
    trace("up() called");
    current_state = "up";
  }
  public void reset() {
    trace("reset() called");
    current_state = "reset";
    neighbours.removeAllElements();
    for (int j=0; j<N.length; j++) timeouts[j].reset_timeout();
    current_nbs = "{";
    Enumeration nbss = neighbours.elements();
    if (nbss.hasMoreElements()) {
      current_nbs = current_nbs + ((String) nbss.nextElement());
      while (nbss.hasMoreElements()) {
        current_nbs = current_nbs + "," + ((String) nbss.nextElement());
      }
    }
    current_nbs = current_nbs + "}";
    sim_trace(1, "P " + current_name + " " + current_nbs + " " +
        current_state + " " + current_parent + " " + current_level + " " +
        current_r);
    if (!get_name().equals("A")) {
      l = n;
      p = get_name();
      tilde_l = null;   // re-new tilde_l
      tilde_l = new int[n];
      current_parent = p;
      current_level = "" + l;
    }
    if (get_name().equals("A")) {
      r = 0;
    } else { // non-root reset actions:
      r = 0;
      tilde_r = new int[n];
    }
    current_r = "(" + r + ")";
  }
  public void perturb() {
    trace("perturb() called");
    current_state = "perturbed";
    
    if (!get_name().equals("A")) {
      l = rand_ltd(0, n);
      if (neighbours.size()>0) {
        p = (String) neighbours.elementAt(rand_ltd(1, neighbours.size())-1);
      } else {
        p = get_name();
      }
      for (int i=0; i<tilde_l.length; i++) tilde_l[i] = rand_ltd(0,n);
      current_parent = p;
      current_level = "" + l;
    }
    if (get_name().equals("A")) {
      // nothing (stable storage in root)
    } else { // non-root perturb actions:
      r = rand_ltd(0,1000);
      for (int i=0; i<tilde_r.length; i++) tilde_r[i] = rand_ltd(0,1000);
    }
  }
  public void byzantine() {
    trace("byzantine() called");
    current_state = "byzantine";
  }

  public void trace(String msg) {
    System.out.println(get_name() + ": " + msg);
  }
}

