package profiles;

import java.util.Vector;

/**
 * La tabella di routing. E' il fulcro del sistema di routing ad-hoc.
 * Attorno a quest'oggetto passivo si può costruire un'oggetto attivo che
 * curi l'aggiornamento della rete e ne stabilisca una politica.
 * Da un punto di vista algoritmico, tutte le azioni sono però già definite
 * all'interno di questa classe.
 *
 * @author Lorenzo Moretti
 * @version 1.0
 */

public class RoutingTable {
  public final int MAX_SEARCH_LEVEL = 20;
  Vector listeners = new Vector();
  Vector neightbours = new Vector();
  Device thisDevice;

  /**
   * Crea una RoutingTable per il dispositivo d.
   * @param d il dispositivo proprietario della RoutingTable
   */
  public RoutingTable(Device d) {
    thisDevice = d;
  }

  /**
   * Aggiunge un dispositivo come vicino. Automaticamente vengono aggiunti
   * alla routing table tutti gli elementi prossimi al dispositivo aggiunto.
   * @param d
   */
  public void addDevice(Device d) {
    if (neightbours.indexOf(d) == -1) {
      neightbours.addElement(d);
      for (int i = 0; i < listeners.size(); i++) {
        ((RoutingTableListener)listeners.elementAt(i)).deviceAdded(d);
      }
    }
  }

  /**
   * Rimuove un dispositvo dall'elenco dei dispositivi prossimi (raggiungibili
   * con un solo passo). Vengono notificati tutti i listener della RoutingTable.
   * @param d il dispositivo (prossimo) da rimuovere.
   */
  public void removeDevice(Device d) {
    if (neightbours.indexOf(d) != -1) {
      neightbours.removeElement(d);
      for (int i = 0; i < listeners.size(); i++) {
        ((RoutingTableListener)listeners.elementAt(i)).deviceRemoved(d);
      }
    }
  }

  /**
   * Aggiunge un listener a questa RoutingTable.
   * @param rtl il listener
   */
  public void addRoutingTableListener(RoutingTableListener rtl) {
    listeners.addElement(rtl);
  }

  /**
   * Rimuove un listener da questa RoutingTable.
   * @param rtl il listener
   */
  public void removeRoutingTableListener(RoutingTableListener rtl) {
    listeners.removeElement(rtl);
  }

  /**
   * Trova un percorso dal dispotivo corrente fino al dispositivo di
   * destinatione. Il percorso non è necessariamente quello minimo.
   * @param d il dispositivo da raggiungere
   * @return un vettore di Device, l'ultimo dei quali è la destinazione. Se il
   * vettore è vuoto (size = 0), non è stato trovato un percorso.
   */
  public Vector getRouteTo(String id) {
    Vector res = new Vector();

    for (int i = 0; i < neightbours.size(); i++) {
      if (((Device)neightbours.elementAt(i)).getId().equals(id)) {
        res.addElement(((Device)neightbours.elementAt(i)).getId());
        return res;
      }
    }

    for (int i = 0; i < neightbours.size(); i++) {
      Vector v = ((Device)neightbours.elementAt(i)).getRoutingTable().
          getRouteTo(id, MAX_SEARCH_LEVEL);
      if (v != null) {
        v.insertElementAt(((Device)neightbours.elementAt(i)).getId(),0);
        res = v;
        break;
      }
    }

    res.insertElementAt(thisDevice.getId(),0);
    res.removeElementAt(0);
    return res;
  }

  // Ricorsivamente continua l'esplorazione
  protected Vector getRouteTo(String id, int depth) {
    for (int i = 0; i < neightbours.size(); i++) {
      if ( ( (Device) neightbours.elementAt(i)).getId().equals(id)) {
        Vector res = new Vector();
        res.addElement(((Device)neightbours.elementAt(i)).getId());
        return res;
      }
    }

    if (depth == 0) {
      return null;
    }

    for (int i = 0; i < neightbours.size(); i++) {
      Vector v = ((Device)neightbours.elementAt(i)).getRoutingTable().getRouteTo(id, depth - 1);
      if (v != null) {
        v.insertElementAt(((Device)neightbours.elementAt(i)).getId(),0);
        return v;
      }
    }
    return null;
  }


  /**
   * Aggiorna la tabella tramite le informazioni contenute in BasicService.
   * @param bs il servizio su cui si basa.
   */
  public void update(BasicService bs) {
    Vector devs = bs.getDevicesInRange(thisDevice);

    // solo per notificare i listener
    for (int i = 0; i < devs.size(); i++) {
      Device d = (Device)devs.elementAt(i);
      if (!neightbours.contains(d))
        addDevice(d);
      else
        neightbours.removeElement(d);
      for (int j = 0; j < neightbours.size(); j++) {
        removeDevice((Device)neightbours.elementAt(j));
      }
    }

    neightbours = devs;

    for (int i = 0; i < neightbours.size(); i++) {
      ((Device)neightbours.elementAt(i)).getRoutingTable().
          removeCiclicLoop(thisDevice);
    }
  }

  /**
   * Rimozione dei cicli interni.
   * @param d
   */
  void removeCiclicLoop(Device d) {
    if (neightbours.size() == 0) return;
    for (int i = neightbours.size()-1; i >= 0; i--) {
      Device dev = (Device)neightbours.elementAt(i);
      if (dev.getId().equals(d.getId())) {
        neightbours.removeElementAt(i);
      } else {
        dev.getRoutingTable().removeCiclicLoop(d);
      }
    }
  }

  /**
   * Serializza in una stringa la tabella.
   * @return la tabella in forma di testo.
   * @see TextMessage
   */
  public String toString() {
    StringBuffer sbuff = new StringBuffer();
    sbuff.append("[");
    for (int i = 0; i < neightbours.size(); i++) {
      sbuff.append(((Device)neightbours.elementAt(i)).toString());
      if (i < neightbours.size()-1)
        sbuff.append(",");
    }

    sbuff.append("]");
    return sbuff.toString();
  }
}
