package brn.sim.handler;

import jist.swans.Constants;
import jist.swans.misc.Event;
import jist.swans.misc.MessageAnno;
import jist.swans.net.AbstractNet;
import jist.swans.net.NetMessage;
import brn.sim.DataManager;
import brn.sim.DataManager.DataContributor;
import brn.sim.data.AbstractDiagramData;
import brn.sim.data.AveragedTimeLine;
import brn.sim.data.DiagramData;
import brn.sim.data.DiagramDataHist;

public class StatsNetHandler extends DataContributor {

  public class NodeData {
    private DiagramData netSend = null;
    private DiagramData netSendAvg;
    private DiagramData netSendCuml;
    private DiagramData netReceive = null;
    private DiagramData netReceiveAvg;
    private DiagramData netReceiveCuml;
    private DiagramData netQueueLength = null;
//    protected DiagramData netThroughput = null;
    private DiagramData netThroughputAvg = null;
    private int id;
    private String category;
    private DiagramData netQueueLengthAvg;
    private DiagramDataHist netReceiveHist;
    private DiagramData[] serviceTime;
    private DiagramData[] queueingTime;
    private DiagramData[] sojournTime;

    public NodeData(int id) {
      this.id = id;
      this.category = getCategory(id);
      this.serviceTime = new DiagramData[10];
      this.queueingTime = new DiagramData[10];
      this.sojournTime = new DiagramData[10];
    }

    public String getCategory(int id) {
      return "Node " + Integer.toString(id);
    }

    public String getCategory() {
      return category;
    }

    protected DiagramData getNetSend() {
      // NET send counter
      if (null == netSend) {
        netSend = new DiagramData(
            new String[] {category, "Net", "send vs time"}, "time", "s",
                "net send packets", "n/a", AbstractDiagramData.MARK);
        addData(netSend, DataManager.LEVEL_ALL);
      }
      return netSend;
    }

    protected DiagramData getNetSendAvg() {
      // NET send counter
      if (null == netSendAvg) {
        netSendAvg = new DiagramData(
            new AveragedTimeLine("send (avg)", sampleLen, AveragedTimeLine.MODE_R, 1.),
            new String[] {category, "Net", "send (avg)"}, "time", "s",
                "net send packets", "n/a");
        addData(netSendAvg, DataManager.LEVEL_BASIC);
      }
      return netSendAvg;
    }

    protected DiagramData getNetSendCuml() {
      // NET send counter
      if (null == netSendCuml) {
        netSendCuml = new DiagramData(
            new AveragedTimeLine("send (cuml'd)", sampleLen, AveragedTimeLine.MODE_CN, 1.),
            new String[] {category, "Net", "send (cuml'd)"}, "time", "s",
            "sent packets", "no.");
        addData(netSendCuml, DataManager.LEVEL_BASIC);
      }
      return netSendCuml;
    }

    protected DiagramData getNetReceive() {
      // NET send counter
      if (null == netReceive) {
        netReceive = new DiagramData(
            new String[] {category, "Net", "receive vs time"}, "time", "s",
                "net received packets", "n/a", AbstractDiagramData.MARK);
        addData(netReceive, DataManager.LEVEL_ALL);
      }
      return netReceive;
    }

    protected DiagramData getNetReceiveAvg() {
      // NET send counter
      if (null == netReceiveAvg) {
        netReceiveAvg = new DiagramData(
            new AveragedTimeLine("receive (avg)", sampleLen, AveragedTimeLine.MODE_R, 1.),
            new String[] {category, "Net", "receive (avg)"}, "time", "s",
                "net send packets", "n/a");
        addData(netReceiveAvg, DataManager.LEVEL_BASIC);
      }
      return netReceiveAvg;
    }

    protected DiagramData getNetReceiveCuml() {
      // NET send counter
      if (null == netReceiveCuml) {
        netReceiveCuml = new DiagramData(
            new AveragedTimeLine("receive (cuml'd)", sampleLen, AveragedTimeLine.MODE_CN, 1.),
            new String[] {category, "Net", "receive (cuml'd)"}, "time", "s",
            "sent packets", "no.");
        addData(netReceiveCuml, DataManager.LEVEL_BASIC);
      }
      return netReceiveCuml;
    }
    
    protected DiagramDataHist getNetReceiveHist() {
      // NET send counter
      if (null == netReceiveHist) {
        netReceiveHist = new DiagramDataHist(
            new String[] {category, "Net", "receive (hist)"}, "sender", "n/a",
            "received packets", "no.");
        addData(netReceiveHist, DataManager.LEVEL_BASIC);
      }
      return netReceiveHist;
    }
    
    protected DiagramData getNetThroughputAvg() {
      // NET send counter
      if (null == netThroughputAvg) {
        netThroughputAvg = new DiagramData(
            new AveragedTimeLine("throughput (avg)", sampleLen, AveragedTimeLine.MODE_WA2, 1.),
            new String[] {category, "Net", "throughput (avg)"}, "time", "s",
                "throughput", "Mbit/s");
        addData(netThroughputAvg, DataManager.LEVEL_BASIC);
      }
      return netThroughputAvg;
    }


    protected DiagramData getQueueLength() {
      if (null == netQueueLength) {
        netQueueLength = new DiagramData(
            new String[] {category, "Net", "queue length"},
                "time", "s", "packets in queue", "no.", AbstractDiagramData.SOLID);
        addData(netQueueLength, DataManager.LEVEL_ALL);
      }

      return netQueueLength;
    }

    protected DiagramData getQueueLengthAvg() {
      if (null == netQueueLengthAvg) {
        netQueueLengthAvg = new DiagramData(
            new AveragedTimeLine("queue length (avg)", sampleLen, AveragedTimeLine.MODE_WA, 1.),
            new String[] {category, "Net", "queue length (avg)"},
                "time", "s", "packets in queue", "no.", AbstractDiagramData.SOLID);
        addData(netQueueLengthAvg, DataManager.LEVEL_BASIC);
      }

      return netQueueLengthAvg;
    }

    protected DiagramData getNetServiceTime(int flowId) {
      try {
        if (null == serviceTime[id]) {
          serviceTime[id] = new DiagramData(
              new String[] {category, "Net", "service time flow " + flowId},
              "packet", "id", "service time", "s", AbstractDiagramData.MARK);
          addData(serviceTime[id], DataManager.LEVEL_ADDITIONAL);

        }
      }
      catch (ArrayIndexOutOfBoundsException e) {
        if (id > 50000)
          throw new RuntimeException("id to large");
        DiagramData[] tmp = new DiagramData[id + 100];
        System.arraycopy(serviceTime, 0, tmp, 0, serviceTime.length);
        serviceTime = tmp;
        return getNetServiceTime(id);
      }
      return serviceTime[id];
    }
    
    protected DiagramData getNetQueueingTime(int flowId) {
      try {
        if (null == queueingTime[id]) {
          queueingTime[id] = new DiagramData(
              new String[] {category, "Net", "queueing time flow " + flowId},
              "packet", "id", "queueing time", "s", AbstractDiagramData.MARK);
          addData(queueingTime[id], DataManager.LEVEL_ADDITIONAL);

        }
      }
      catch (ArrayIndexOutOfBoundsException e) {
        if (id > 50000)
          throw new RuntimeException("id to large");
        DiagramData[] tmp = new DiagramData[id + 100];
        System.arraycopy(queueingTime, 0, tmp, 0, queueingTime.length);
        queueingTime = tmp;
        return getNetQueueingTime(id);
      }
      return queueingTime[id];
    }
    
    protected DiagramData getNetSojournTime(int flowId) {
      try {
        if (null == sojournTime[id]) {
          sojournTime[id] = new DiagramData(
              new String[] {category, "Net", "sojourn time flow " + flowId},
              "packet", "id", "sojourn time", "s", AbstractDiagramData.MARK);
          addData(sojournTime[id], DataManager.LEVEL_ADDITIONAL);

        }
      }
      catch (ArrayIndexOutOfBoundsException e) {
        if (id > 50000)
          throw new RuntimeException("id to large");
        DiagramData[] tmp = new DiagramData[id + 100];
        System.arraycopy(sojournTime, 0, tmp, 0, sojournTime.length);
        sojournTime = tmp;
        return getNetSojournTime(id);
      }
      return sojournTime[id];
    }
  }
  

  public class GlobalData {
    private static final String category = "Global";

    private DiagramData netDrop = null;
    private DiagramDataHist macQueueLengthHist = null;
    private DiagramDataHist netDropHist;
    private DiagramDataHist netServiceTimeMax;
    private DiagramDataHist netQueueingTimeMax;
    private DiagramDataHist netSojournTimeMax;

    protected DiagramData getNetDiscard() {
      if (null == netDrop) {
        netDrop = new DiagramData(
            new String[] {category, "Net", "drop vs time"}, "time", "s",
            "node", "net id", AbstractDiagramData.MARK);
        addData(netDrop, DataManager.LEVEL_ALL);
      }

      return netDrop;
    }

    protected DiagramDataHist getNetDiscardHist() {
      if (null == netDropHist) {
        netDropHist = new DiagramDataHist(
            new String[] {category, "Net", "drop (hist)"}, "node", "net id",
            "drops", "count");
        addData(netDropHist, DataManager.LEVEL_BASIC);
      }

      return netDropHist;
    }

    public DiagramDataHist getMacQueueLengthHist() {
      if (null == macQueueLengthHist) {
        macQueueLengthHist = new DiagramDataHist(new String[] {category, "Net",
            "queue length (max)"}, "node", "net id", "packets in queue (max)", "no.",
            DiagramDataHist.MODE_MAX);
        addData(macQueueLengthHist, DataManager.LEVEL_BASIC);
      }
      return macQueueLengthHist;
    }
    
    public DiagramDataHist getNetServiceTimeMax() {
      if (null == netServiceTimeMax) {
        netServiceTimeMax = new DiagramDataHist(new String[] {category, "Net",
            "service time (max)"}, "node", "id", "service time (max)", "s",
            DiagramDataHist.MODE_MAX);
        addData(netServiceTimeMax, DataManager.LEVEL_BASIC);
      }
      return netServiceTimeMax;
    }

    public DiagramDataHist getNetQueueingTimeMax() {
      if (null == netQueueingTimeMax) {
        netQueueingTimeMax = new DiagramDataHist(new String[] {category, "Net",
            "queueing time (max)"}, "node", "id", "queueing time (max)", "s",
            DiagramDataHist.MODE_MAX);
        addData(netQueueingTimeMax, DataManager.LEVEL_BASIC);
      }
      return netQueueingTimeMax;
    }

    public DiagramDataHist getNetSojournTimeMax() {
      if (null == netSojournTimeMax) {
        netSojournTimeMax = new DiagramDataHist(new String[] {category, "Net",
            "sojourn time (max)"}, "node", "id", "sojourn time (max)", "s",
            DiagramDataHist.MODE_MAX);
        addData(netSojournTimeMax, DataManager.LEVEL_BASIC);
      }
      return netSojournTimeMax;
    }
    
  }

  private static final String ID = "StatsNetHandler";

  private double sampleLen;

  private NodeData[] nodeData;

  private GlobalData globalData;


  public StatsNetHandler(double sampleLen) {
    this.sampleLen = sampleLen;
    this.nodeData = new NodeData[20];
    this.globalData = new GlobalData();
  }

  protected NodeData getNodeData(int id) {
    try {
      if (null == nodeData[id])
        nodeData[id] = new NodeData(id);
    }
    catch (ArrayIndexOutOfBoundsException e) {
      if (id > 50000)
        throw new RuntimeException("id to large");
      NodeData[] tmp = new NodeData[id + 100];
      System.arraycopy(nodeData, 0, tmp, 0, nodeData.length);
      nodeData = tmp;
      return getNodeData(id);
    }
    return nodeData[id];
  }

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

  /**
   * Registers all net handlers.
   */
  public void registerHandlers() {
    super.registerHandlers();

    // Add net handlers

    Event.addHandler(AbstractNet.EnqueueEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractNet.EnqueueEvent ev = (AbstractNet.EnqueueEvent) event;
        if (null != ev.queue) {
          NodeData nodeData2 = getNodeData(ev.nodeId);
          nodeData2.getQueueLength().addNextTimePoint(ev.time, ev.queue.size());
          nodeData2.getQueueLengthAvg().addNextTimePoint(ev.time, ev.queue.size());
          globalData.getMacQueueLengthHist().addNextValue(ev.nodeId, ev.queue.size());
        }
      }
    });

    Event.addHandler(AbstractNet.DequeueEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractNet.DequeueEvent ev = (AbstractNet.DequeueEvent) event;
        if (null != ev.queue) {
          NodeData nodeData2 = getNodeData(ev.nodeId);
          nodeData2.getQueueLength().addNextTimePoint(ev.time, ev.queue.size());
          nodeData2.getQueueLengthAvg().addNextTimePoint(ev.time, ev.queue.size());
        }
      }
    });

    Event.addHandler(AbstractNet.SendToMacEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractNet.SendToMacEvent ev = (AbstractNet.SendToMacEvent) event;
        NodeData nodeData2 = getNodeData(ev.nodeId);
        nodeData2.getNetSend().addNextTimePoint(ev.time, ev.localAddr.getId());
        nodeData2.getNetSendAvg().addNextTimePoint(ev.time, ev.localAddr.getId());
        nodeData2.getNetSendCuml().addNextTimePoint(ev.time, ev.localAddr.getId());
        
        if (null == ev.anno)
          return;
        Integer packetId = (Integer) ev.anno.get(MessageAnno.ANNO_RTG_PACKETID);
        Integer flowId = (Integer) ev.anno.get(MessageAnno.ANNO_RTG_FLOWID);
        if (null == packetId || null == flowId)
          return;
        double queueingTime = ev.time - (Long)ev.anno.get(MessageAnno.ANNO_NET_ARRIVAL);
        queueingTime /= (double) Constants.SECOND;

        nodeData2.getNetQueueingTime(flowId).addNextPoint(packetId, queueingTime);
        globalData.getNetQueueingTimeMax().addNextValue(ev.nodeId, queueingTime);
      }
    });
    
    Event.addHandler(AbstractNet.SendToMacFinishEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractNet.SendToMacFinishEvent ev = (AbstractNet.SendToMacFinishEvent) event;
        if (null == ev.anno)
          return;

        Integer packetId = (Integer) ev.anno.get(MessageAnno.ANNO_RTG_PACKETID);
        Integer flowId = (Integer) ev.anno.get(MessageAnno.ANNO_RTG_FLOWID);
        if (null == packetId || null == flowId)
          return;

        double sojournTime = ev.time - (Long)ev.anno.get(MessageAnno.ANNO_NET_ARRIVAL);
        sojournTime /= (double) Constants.SECOND;
        double serviceTime = ev.time - (Long)ev.anno.get(MessageAnno.ANNO_NET_SERVICE);
        serviceTime /= (double) Constants.SECOND;

        getNodeData(ev.nodeId).getNetServiceTime(flowId).addNextPoint(packetId, serviceTime);
        globalData.getNetServiceTimeMax().addNextValue(ev.nodeId, serviceTime);
        
        getNodeData(ev.nodeId).getNetSojournTime(flowId).addNextPoint(packetId, sojournTime);
        globalData.getNetSojournTimeMax().addNextValue(ev.nodeId, sojournTime);
      }
    });

    Event.addHandler(AbstractNet.RecvFromMacEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractNet.RecvFromMacEvent ev = (AbstractNet.RecvFromMacEvent) event;
        NodeData nodeData2 = getNodeData(ev.nodeId);
        nodeData2.getNetReceive().addNextTimePoint(ev.time, ev.localAddr.getId());
        nodeData2.getNetReceiveAvg().addNextTimePoint(ev.time, ev.localAddr.getId());
        nodeData2.getNetReceiveCuml().addNextTimePoint(ev.time, ev.localAddr.getId());

        if (ev.data instanceof NetMessage.Ip) {
          NetMessage.Ip ipmsg = (NetMessage.Ip) ev.data;
          double throughput = (double) ipmsg.getSize() * 8 / 1000000;
          nodeData2.getNetThroughputAvg().addNextTimePoint(ev.time,
              throughput); // byte to Mbit
          nodeData2.getNetReceiveHist().addNextValue(ipmsg.getSrc().getId());
        }
      }
    });

    Event.addHandler(AbstractNet.LinkDropEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractNet.LinkDropEvent ev = (AbstractNet.LinkDropEvent) event;
        globalData.getNetDiscard().addNextTimePoint(ev.time, (double) ev.nodeId);
        globalData.getNetDiscardHist().addNextValue((double) ev.nodeId);
      }
    });
  }

}
