package brn.sim.handler;

import java.util.Collection;
import java.util.Iterator;

import jist.swans.field.AbstractField;
import jist.swans.field.AbstractField.NodeMoveEvent;
import jist.swans.misc.Event;
import jist.swans.misc.Location;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetMessage;
import jist.swans.net.NetMessage.Ip;
import jist.swans.route.AbstractRoute;
import jist.swans.route.AbstractRoute.DuplicateEvent;
import jist.swans.route.AbstractRoute.PacketForwardedEvent;
import jist.swans.trans.TransUdp.UdpMessage;
import brn.sim.DataManager;
import brn.sim.DataManager.DataContribution;
import brn.sim.DataManager.DataContributor;
import brn.sim.data.ForwardGraphData;
import brn.swans.route.RouteDsrBrnMsg;
import brn.util.Graphviz;

/**
 * Registers to various events and creates a forwarding graph based on
 * routing information
 *
 * @author kurth
 */
public class ForwardGraphHandlers extends DataContributor {
  private static final long serialVersionUID = 1L;

  private static final String ID = "ForwardGraphHandlers";

  // ////////////////////////////////////////////////
  // locals
  //

  private ForwardGraphData data;


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

  public ForwardGraphHandlers() {
    data = new ForwardGraphData();
  }

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

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

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

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

  // ////////////////////////////////////////////////
  // implementation
  //

  public void setDataManager(DataManager manager) {
    super.setDataManager(manager);
    addData(data, DataManager.LEVEL_IMPORTANT);
  }


  protected void handleDuplicate(DuplicateEvent event) {
    RouteDsrBrnMsg msg = (RouteDsrBrnMsg) event.data;
    // process only data packets
    if (msg.getContent() == null)
      return;

    RouteDsrBrnMsg.OptionId optId = (RouteDsrBrnMsg.OptionId)
      msg.getOption(RouteDsrBrnMsg.OPT_ID);
    int flowId = optId.getAnnoFlowId();

    ForwardGraphData.Node from = (ForwardGraphData.Node) data.nodes.get(new Integer(event.lastIp.getId()));
    ForwardGraphData.Node to = (ForwardGraphData.Node) data.nodes.get(new Integer(event.localAddr.getId()));

    if (null == from)
      data.nodes.put(new Integer(event.lastIp.getId()),
          new ForwardGraphData.Node(event.lastIp, event.lastIp.getId(), -1, -1));
    if (null == to)
      data.nodes.put(new Integer(event.localAddr.getId()),
          new ForwardGraphData.Node(event.localAddr, event.localAddr.getId(), -1, -1));

    // TODO determine channels
    int fromCh = 0;
    int toCh = 0;
    ForwardGraphData.FwdLink lnk = new ForwardGraphData.FwdLink(from, fromCh, to, toCh, flowId);

    ForwardGraphData.FwdLink mapLnk = (ForwardGraphData.FwdLink) data.fwdLinks.get(lnk);
    if (null == mapLnk) {
      data.fwdLinks.put(lnk, lnk);
      mapLnk = lnk;
    }

    mapLnk.incNoDuplicates();
  }

  /**
   * Increases the forwarded packet counter for the flow
   *  (lastIp x localAddr x flowId)
   */
  protected void handlePacketForwarded(PacketForwardedEvent event) {
    int flowId;
    if (event.data instanceof RouteDsrBrnMsg) {
      RouteDsrBrnMsg msg = (RouteDsrBrnMsg) event.data;
      RouteDsrBrnMsg.OptionId optId = (RouteDsrBrnMsg.OptionId) msg
        .getOption(RouteDsrBrnMsg.OPT_ID);
      if (null == optId)
        return;
      flowId = optId.getAnnoFlowId();
    } else {
//      NetMessage.Ip ipMsg = (Ip) event.data;
//      UdpMessage udpMsg = (UdpMessage) ipMsg.getPayload();
//      AnnotatedMessage annoMsg = (AnnotatedMessage) udpMsg.getPayload();
//      flowId = annoMsg.flowId;
      flowId = (Integer) event.anno.get(MessageAnno.ANNO_RTG_FLOWID);
    }

    // arp was not able to resolve the mac address
    if (null == event.lastIp)
      // TODO error message
      return;

    ForwardGraphData.Node from = (ForwardGraphData.Node) data.nodes.get(new Integer(event.lastIp.getId()));
    ForwardGraphData.Node to = (ForwardGraphData.Node) data.nodes.get(new Integer(event.localAddr.getId()));

    if (null == from) {
      from = new ForwardGraphData.Node(event.lastIp, event.lastIp.getId(), -1, -1);
      data.nodes.put(new Integer(event.lastIp.getId()), from);
    }
    if (null == to) {
      to = new ForwardGraphData.Node(event.localAddr, event.localAddr.getId(), -1, -1);
      data.nodes.put(new Integer(event.localAddr.getId()), to);
    }

    // TODO determine channels
    int fromCh = 0;
    int toCh = 0;
    ForwardGraphData.FwdLink lnk = new ForwardGraphData.FwdLink(from, fromCh, to, toCh, flowId);

    ForwardGraphData.FwdLink mapLnk = (ForwardGraphData.FwdLink) data.fwdLinks.get(lnk);
    if (null == mapLnk) {
      data.fwdLinks.put(lnk, lnk);
      mapLnk = lnk;
    }

    mapLnk.incNoPackets();
  }

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

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

  public Graphviz getForwardGraph() {
    Graphviz.DiGraph fwdGraph = new Graphviz.DiGraph(50, 100, false);

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

    Collection coll = data.getLinksCollection();
    Iterator iter = coll.iterator();
    while (iter.hasNext()) {
      ForwardGraphData.FwdLink fwdLink = (ForwardGraphData.FwdLink) iter.next();

      Location locFrom = null == fwdLink.from ? null :
        new Location.Location2D(fwdLink.from.locX, fwdLink.from.locY);
      Location locTo = null == fwdLink.to ? null :
        new Location.Location2D(fwdLink.to.locX, fwdLink.to.locY);

      if (locFrom != null && locTo != null) {
        double offset = fwdLink.flowId * f_y;

        String from = fwdLink.getFromStr() + ":" + fwdLink.flowId;
        String to = fwdLink.getToStr() + ":" + fwdLink.flowId;

        fwdGraph.addNode(from, scaleX * locFrom.getX(),
            scaleY * locFrom.getY() + offset, fwdLink.fromCh);
        fwdGraph.addNode(to, scaleX * locTo.getX(),
            scaleY * locTo.getY() + offset, fwdLink.toCh);
        if (fwdLink.noPackets > 0)
          fwdGraph.addLink(from, to, fwdLink.noPackets);
        if (fwdLink.noDuplicates > 0)
          fwdGraph.addLink(from, to, fwdLink.noDuplicates, 0);
      }
    }

    return fwdGraph;
  }

  public ForwardGraphData getForwardGraphData() {
    return data;
  }

  /*
   * (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 getForwardGraph().getText();
  }

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