package brn.sim.handler;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;

import jist.swans.field.AbstractField;
import jist.swans.field.AbstractField.NodeMoveEvent;
import jist.swans.mac.MacAddress;
import jist.swans.misc.Event;
import jist.swans.misc.Location;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetAddress;
import jist.swans.net.NetMessage;
import jist.swans.route.AbstractRoute;
import brn.sim.DataManager;
import brn.sim.DriverRemote;
import brn.sim.DataManager.DataContribution;
import brn.sim.DataManager.DataContributor;
import brn.sim.data.LinkTableData;
import brn.swans.route.metric.AbstractRouteMetric;
import brn.swans.route.metric.ArpTableInterface;
import brn.swans.route.metric.LinkTable;
import brn.swans.route.metric.AbstractRouteMetric.LinkTableCreatedEvent;
import brn.util.Graphviz;

@SuppressWarnings("unchecked")
public class LinkTableHandlers extends DataContributor {

  private static final String ID = "LinkTableHandlers";

  //  private Map mapNodeToLinkTable = new HashMap();
  private Map mapIdToLinkTable = new HashMap();

  private Map mapArpInterfaces = new HashMap();

  private Map mapNodes = new HashMap();

  private Map mapDstCandidateLinks = new HashMap();

  // only for viz purposes ...
  private Random random = new Random(System.currentTimeMillis());

  private static class LinkTableEntry {
    public LinkTable linkTable;
    public int nodeId;
    public LinkTableEntry(LinkTable linkTable, int nodeId) {
      super();
      this.linkTable = linkTable;
      this.nodeId = nodeId;
    }
  }

  // ////////////////////////////////////////////////
  // initialization
  //

  public LinkTableHandlers() {
  }

  public void registerHandlers() {
    super.registerHandlers();

    Event.addHandler(AbstractRouteMetric.LinkTableCreatedEvent.class,
        new Event.Handler() {
      public void handle(Event event) {
        handleLinkTableCreated((LinkTableCreatedEvent) event);
      }
    });

//    Event.addHandler(AbstractRouteMetric.LinkMetricChangedEvent.class,
//        new Event.Handler() {
//      public void handle(Event event) {
//        handleLinkMetricChanged((LinkMetricChangedEvent) event);
//      }
//    });

    Event.addHandler(AbstractRoute.CandSelectedEvent.class, new Event.Handler() {
      public void handle(Event event) {
        handleCandidateSelected(event);
      }
    });

    Event.addHandler(AbstractField.NodeMoveEvent.class, new Event.Handler() {
      public void handle(Event event) {
        handleNodeMove((AbstractField.NodeMoveEvent) event);
      }
    });
  }


  // ////////////////////////////////////////////////
  // handlers
  //

  protected void handleLinkTableCreated(LinkTableCreatedEvent event) {
    LinkTableEntry entry = new LinkTableEntry(event.linkTable, event.nodeId);
//    mapNodeToLinkTable.put(new Integer(event.nodeId), entry);

    int id = addData(new String[] {"Node "+event.nodeId, "Links", "Link Table"},
        DriverRemote.ITEM_TYPE_LINKTABLE, DataManager.LEVEL_BASIC);
    if (0 <= id)
      mapIdToLinkTable.put(new Integer(id), entry);
  }

  protected void handleNodeMove(NodeMoveEvent event) {
    LinkTableData.Node node = (LinkTableData.Node) mapNodes.get(new Integer(event.nodeId));
    if (null == node) {
      node = new LinkTableData.Node(event.nodeId, event.loc.getX(), event.loc.getY());
      mapNodes.put(new Integer(event.nodeId), node);
    }

    node.locX = event.loc.getX();
    node.locY = event.loc.getY();
  }

//  private void handleLinkMetricChanged(LinkMetricChangedEvent event) {
//  }

  // ////////////////////////////////////////////////
  // operations
  //

  /**
   * Registers an arp table in gui.
   *
   * @param nodeId
   * @param arpTable
   */
  protected void addArpTable(int nodeId, ArpTableInterface arpTable) {
    int id = addData(new String[] {"Node "+nodeId,"Arp Table"},
        DriverRemote.ITEM_TYPE_ARPTABLE, DataManager.LEVEL_BASIC);
    if (0 <= id)
      mapArpInterfaces.put(new Integer(id), arpTable);
  }

  /**
   * @param event
   */
  protected void handleCandidateSelected(Event event) {
    AbstractRoute.CandSelectedEvent ev = (AbstractRoute.CandSelectedEvent) event;
    NetMessage.Ip ipMsg = (NetMessage.Ip) ev.data;
    NetAddress netSrc = new NetAddress(ev.nodeId);
    NetAddress netDst = ipMsg.getDst();
    Integer flowId = (Integer) ev.anno.get(MessageAnno.ANNO_RTG_FLOWID);
  
    Map mapCandidateLinks = (Map) mapDstCandidateLinks.get(netDst);
    if (null == mapCandidateLinks) {
      mapCandidateLinks = new HashMap();
      mapDstCandidateLinks.put(netDst, mapCandidateLinks);
  
      int id = addData(new String[] {"Flow "+flowId, "Routing", "Candidate Graph"},
          DriverRemote.ITEM_TYPE_LINKTABLE, DataManager.LEVEL_BASIC);
      if (0 <= id)
        mapDstCandidateLinks .put(new Integer(id), mapCandidateLinks);
    }
  
    List lstLinks = new ArrayList();
    int metric = random.nextInt(200) + 100;
    for (int i = 0; i < ev.candidates.size(); i++) {
      MacAddress cand = (MacAddress) ev.candidates.get(i);
      NetAddress netCand = new NetAddress((int)cand.getId());
  
      lstLinks.add(new LinkTableData.Link(netSrc, netCand, metric , 0));
    }
    mapCandidateLinks.put(netSrc, lstLinks);
  }

  protected Map getArpTable(int id) throws RemoteException {
    ArpTableInterface arp = (ArpTableInterface)
        mapArpInterfaces.get(new Integer(id));
    return arp.getArpTable();
  }

  protected LinkTableData getLinkTable(int id) {
    LinkTableEntry entry = (LinkTableEntry) mapIdToLinkTable.get(new Integer(id));

    List lstLinks = new ArrayList();
    Iterator iterLinks = entry.linkTable.getLinks().iterator();
    while (null != iterLinks && iterLinks.hasNext()) {
      LinkTable.LinkData link = (LinkTable.LinkData) iterLinks.next();

      lstLinks.add(new LinkTableData.Link(link));
    }

    return new LinkTableData(entry.nodeId, new ArrayList(mapNodes.values()),
        lstLinks);
  }

  protected DataContribution getCandidateGraph(int id) {
    Map mapCandidateLinks = (Map) mapDstCandidateLinks.get(new Integer(id));
    List lstAllLinks = new ArrayList();

    Iterator iter = mapCandidateLinks.values().iterator();
    while (null != iter && iter.hasNext()) {
      List lstLinks = (List) iter.next();
      lstAllLinks.addAll(lstLinks);
    }

    return new LinkTableData(id, new ArrayList(mapNodes.values()),
        lstAllLinks);
  }

  protected Graphviz toGraphViz(LinkTableData linkTable) {
    Graphviz.DiGraph linkTableGraph = new Graphviz.DiGraph(50, 100, false);

    final double scaleX = 10;
    final double scaleY = 2;
    final double f_y = 0;//2;

    // build hash map of nodes
    Map mapNodes = new HashMap();
    List nodes = linkTable.getNodes();
    for (int i = 0; i < nodes.size(); i++) {
      LinkTableData.Node node = (LinkTableData.Node) nodes.get(i);
      mapNodes.put(new Integer(node.nodeId), node);
    }

    // iterate through all links
    Collection coll = linkTable.getLinks();
    Iterator iter = coll.iterator();
    while (iter.hasNext()) {
      double offset = f_y;
      LinkTable.LinkData link = (LinkTable.LinkData) iter.next();

      NetAddress fromAddr = (NetAddress) link.from();
      NetAddress toAddr = (NetAddress) link.to();
      int metric = link.metric();

      LinkTableData.Node nodeFrom = (LinkTableData.Node) mapNodes.get(
          new Integer(fromAddr.getId()));
      Location locFrom = (null == nodeFrom ? null :
        new Location.Location2D(nodeFrom.locX, nodeFrom.locY));

      LinkTableData.Node nodeTo = (LinkTableData.Node) mapNodes.get(
          new Integer(toAddr.getId()));
      Location locTo = (null == nodeTo ? null :
        new Location.Location2D(nodeTo.locX, nodeTo.locY));

      if (locFrom != null && locTo != null) {
        String from = fromAddr.toString();
        String to = toAddr.toString();

        linkTableGraph.addNode(from, scaleX * locFrom.getX(),
            scaleY * locFrom.getY() + offset, 0);
        linkTableGraph.addNode(to, scaleX * locTo.getX(),
            scaleY * locTo.getY() + offset, 0);
        linkTableGraph.addLink(from, to, metric);
      }
    }

    return linkTableGraph;
  }

  /*
   * (non-Javadoc)
   * @see brn.sim.DataManager.DataContributor#getContribution(int)
   */
  public DataContribution getContribution(int id, String[] path) {
    if (mapDstCandidateLinks.containsKey(new Integer(id)))
      return getCandidateGraph(id);
    return getLinkTable(id);
  }

  /*
   * (non-Javadoc)
   * @see brn.sim.DataManager.DataContributor#getId()
   */
  public String getId() {
    return ID;
  }

  /*
   * (non-Javadoc)
   * @see brn.sim.DataManager.DataContributor#getContent(int, brn.sim.DataManager.DataContribution)
   */
  public String getContent(int id, DataContribution contribution) throws Exception {
    return toGraphViz(getLinkTable(id)).getText();
  }

  /*
   * (non-Javadoc)
   * @see brn.sim.DataManager.DataContributor#getFileName(int, brn.sim.DataManager.DataContribution)
   */
  public String getFileName(int id, String[] path, DataContribution contribution) {
    return DataManager.getPath(path) + ".dot";
  }

}
