package brn.sim.handler;

import jist.runtime.Main;
import jist.runtime.Util;
import jist.swans.Constants;
import jist.swans.mac.AbstractMac;
import jist.swans.mac.Mac802_11Message;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacDcfMessage;
import jist.swans.mac.MacDumbMessage;
import jist.swans.mac.MacMessage;
import jist.swans.misc.Event;
import jist.swans.misc.MessageAnno;

import org.apache.log4j.Logger;

import brn.sim.DataManager;
import brn.sim.DataManager.DataContribution;
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;
import brn.sim.data.MacStats;
import brn.sim.data.PropertiesData;

public class StatsMacHandler extends DataContributor {

  public static final Logger log = Logger.getLogger(StatsMacHandler.class);

  public class NodeData {
    private DiagramData macDiscard = null;
    private DiagramData shortRetry = null;
    private DiagramData longRetry = null;
    private DiagramData macNumberOfNotRecvAcks = null;
    private DiagramData macNumberOfRecvAcks = null;
    private DiagramData macDuplicate = null;
    private DiagramData macSend = null;
    private DiagramData macReceive = null;
    private DiagramData macReceiveSrc = null;
    private DiagramDataHist macReceiveHist = null;
    private DiagramData sendImmediate = null;
    private DiagramData sendImmediateDelay = null;
    private DiagramData macQueueLength = null;
    private DiagramData promiscReceive = null;
    private DiagramData macReceiveSrcPid;
//    private int id;
    private String category;
    private DiagramData macBackoff;
    private DiagramData macTXOP;
    private DiagramData macCw;
    private DiagramData[] forward;
    private MacStats stats;

    public NodeData(int id) {
//      this.id = id;
      this.category = getCategory(id);
      this.forward = new DiagramData[10];
    }

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

    public String getCategory() {
      return category;
    }

    protected DiagramData getMacDiscard() {
      // Mac Discard counter
      if (null == macDiscard) {
        macDiscard = new DiagramData(
            new String[] {category, "Mac", "discard vs time"}, "time", "s",
                "discarded packets", "n/a", AbstractDiagramData.MARK);
        addData(macDiscard, DataManager.LEVEL_BASIC);
      }

      return macDiscard;
    }

    protected DiagramData getMacNumberOfNotRecvAcks() {
      // Mac CandidateSet counter
      if (null == macNumberOfNotRecvAcks) {
        macNumberOfNotRecvAcks = new DiagramData(
            new String[] {category, "Mac", "not recv acks vs time"},
                "time", "s", "not received acks per packet", "n/a", AbstractDiagramData.MARK);
        addData(macNumberOfNotRecvAcks, DataManager.LEVEL_ALL);
      }

      return macNumberOfNotRecvAcks;
    }

    protected DiagramData getMacNumberOfRecvAcks() {
      // Mac Retry counter
      if (null == macNumberOfRecvAcks) {
        macNumberOfRecvAcks = new DiagramData(
            new String[] {category, "Mac", "recv acks vs time"}, "time", "s",
            "received acks per packet", "n/a", AbstractDiagramData.MARK);
        addData(macNumberOfRecvAcks, DataManager.LEVEL_ALL);
      }

      return macNumberOfRecvAcks;
    }

    protected DiagramData getShortRetry() {
      if (null == shortRetry) {
        shortRetry = new DiagramData(
            new String[] {category, "Mac", "short retries vs time"}, "time", "s",
                "retry counter", "n/a", AbstractDiagramData.MARK);
        addData(shortRetry, DataManager.LEVEL_ALL);

        DiagramData shortRetryRated = new DiagramData(
            new AveragedTimeLine("short retries vs time (avg)", sampleLen, AveragedTimeLine.MODE_R, 1.),
            new String[] {category, "Mac", "short retries vs time (avg)"},
            "time", "s", "avg retries", "n/a");
        shortRetry.addChain(shortRetryRated);
        addData(shortRetryRated, DataManager.LEVEL_BASIC);
      }

      return shortRetry;
    }

    protected DiagramData getLongRetry() {
      if (null == longRetry) {
        longRetry = new DiagramData(
            new String[] {category, "Mac", "long retries vs time"}, "time", "s",
                "retry counter", "n/a", AbstractDiagramData.MARK);
        addData(longRetry, DataManager.LEVEL_ALL);

        DiagramData longRetryRated = new DiagramData(
            new AveragedTimeLine("long retries vs time (avg)", sampleLen, AveragedTimeLine.MODE_R, 1.),
            new String[] {category, "Mac", "long retries vs time (avg)"},
            "time", "s", "avg retries", "n/a");
        longRetry.addChain(longRetryRated);
        addData(longRetryRated, DataManager.LEVEL_BASIC);
      }

      return longRetry;
    }


    protected DiagramData getMacSend() {
      // MAC send counter
      if (null == macSend) {
        macSend = new DiagramData(
            new String[] {category, "Mac", "send vs time"}, "time", "s",
                "mac send packets", "n/a", AbstractDiagramData.MARK);
        addData(macSend, DataManager.LEVEL_ALL);

        DiagramData macSendCuml = new DiagramData(
            new AveragedTimeLine("send (cuml'd)", sampleLen, AveragedTimeLine.MODE_CN, 1.),
            new String[] {category, "Mac", "send (cuml'd)"}, "time", "sec",
            "sent packets", "no.");
        macSend.addChain(macSendCuml);
        addData(macSendCuml, DataManager.LEVEL_BASIC);

        DiagramData macSendRated = new DiagramData(
            new AveragedTimeLine("send (avg'd)", sampleLen, AveragedTimeLine.MODE_R, 1.),
              new String[] {category, "Mac", "send (avg)"}, "time", "s",
                "mac send packets", "n/a");
        macSendCuml.addChain(macSendRated);
        addData(macSendRated, DataManager.LEVEL_BASIC);
      }
      return macSend;
    }

    protected DiagramData getMacReceive() {
      // mac receive counter
      if (null == macReceive) {
        macReceive = new DiagramData(
            new String[] {category, "Mac", "receive vs time"}, "time", "s",
                "mac receive packets", "n/a", AbstractDiagramData.MARK);
        addData(macReceive, DataManager.LEVEL_ALL);

        DiagramData macRecvCuml = new DiagramData(
            new AveragedTimeLine("receive (cuml'd)", sampleLen, AveragedTimeLine.MODE_CN, 1.),
            new String[] {category, "Mac", "receive (cuml'd)"}, "time", "sec",
            "received packets", "no.");
        macReceive.addChain(macRecvCuml);
        addData(macRecvCuml, DataManager.LEVEL_BASIC);

        DiagramData macReceiveRated = new DiagramData(
            new AveragedTimeLine("receive (avg'd)", sampleLen, AveragedTimeLine.MODE_R, 1.),
            new String[] {category, "Mac", "receive (avg)"}, "time", "s",
            "mac receive packets", "n/a");
        macRecvCuml.addChain(macReceiveRated);
        addData(macReceiveRated, DataManager.LEVEL_BASIC);
      }
      return macReceive;
    }
    
    protected DiagramData getMacReceiveSrc() {
      if (null == macReceiveSrc) {
        macReceiveSrc = new DiagramData(
            new String[] {category, "Mac", "receive src vs time"}, "time", "s",
                "frame src", "id", AbstractDiagramData.MARK);
        addData(macReceiveSrc, DataManager.LEVEL_ALL);
      }
      return macReceiveSrc;
    }

    protected DiagramData getMacReceiveSrcPid() {
      if (null == macReceiveSrcPid) {
        macReceiveSrcPid = new DiagramData(
            new String[] {category, "Mac", "receive packet vs src "}, "packet", "id",
                "frame src", "id", AbstractDiagramData.MARK);
        addData(macReceiveSrcPid, DataManager.LEVEL_ALL);
      }
      return macReceiveSrcPid;
    }

    protected DiagramDataHist getMacReceiveHist() {
      if (null == macReceiveHist) {
        macReceiveHist = new DiagramDataHist(
            new String[] {category, "Mac", "receive (hist)"}, "sender", "n/a",
            "received packets", "no.");
        addData(macReceiveHist, DataManager.LEVEL_BASIC);
      }
      return macReceiveHist;
    }

    protected DiagramData getMacDuplicate() {
      if (null == macDuplicate) {
        macDuplicate = new DiagramData(
            new String[] {category, "Mac", "duplicate vs time"},
                "time", "s", "duplicate packets", "n/a", AbstractDiagramData.MARK);
        addData(macDuplicate, DataManager.LEVEL_ADDITIONAL);
      }

      return macDuplicate;
    }

    public DiagramData getMacCw() {
      if (null == macCw) {
        macCw = new DiagramData(
            new String[] {category, "Mac", "contention window vs time"},
                "time", "s", "contention window", "slots");
        addData(macCw, DataManager.LEVEL_ALL);
      }

      return macCw;
    }

    public DiagramData getMacBackoff() {
      if (null == macBackoff) {
        macBackoff = new DiagramData(
            new String[] {category, "Mac", "backoff vs time"},
                "time", "s", "backoff time", "ms", AbstractDiagramData.MARK);
        addData(macBackoff, DataManager.LEVEL_ALL);

        DiagramData macBackoffCuml = new DiagramData(
            new AveragedTimeLine("backoff vs time (cuml)", sampleLen, AveragedTimeLine.MODE_CC, 1.),
            new String[] {category, "Mac", "backoff vs time (cuml)"},
            "time", "s", "cumulated backoff", "ms");
        macBackoff.addChain(macBackoffCuml);
        addData(macBackoffCuml, DataManager.LEVEL_BASIC);

        DiagramData macBackoffRated = new DiagramData(
            new AveragedTimeLine("backoff vs time (avg)", sampleLen, AveragedTimeLine.MODE_WA2, 1.),
            new String[] {category, "Mac", "backoff vs time (avg)"},
            "time", "s", "backoff (avg)", "ms");
        macBackoffCuml.addChain(macBackoffRated);
        addData(macBackoffRated, DataManager.LEVEL_BASIC);
      }

      return macBackoff;
    }

    public DiagramData getMacTXOP() {
      if (null == macTXOP) {
        macTXOP = new DiagramData(
            new String[] {category, "Mac", "TXOP vs time"},
                "time", "s", "TXOP time", "ms", AbstractDiagramData.MARK);
        addData(macTXOP, DataManager.LEVEL_ALL);

        DiagramData macBackoffCuml = new DiagramData(
            new AveragedTimeLine("TXOP vs time (cuml)", sampleLen, AveragedTimeLine.MODE_CC, 1.),
            new String[] {category, "Mac", "TXOP vs time (cuml)"},
            "time", "s", "cumulated TXOP", "ms");
        macTXOP.addChain(macBackoffCuml);
        addData(macBackoffCuml, DataManager.LEVEL_BASIC);

        DiagramData macBackoffRated = new DiagramData(
            new AveragedTimeLine("TXOP vs time (avg)", sampleLen, AveragedTimeLine.MODE_WA2, 1.),
            new String[] {category, "Mac", "TXOP vs time (avg)"},
            "time", "s", "TXOP (avg)", "ms");
        macBackoffCuml.addChain(macBackoffRated);
        addData(macBackoffRated, DataManager.LEVEL_BASIC);
      }

      return macTXOP;
    }

    public DiagramData getSendImmediate() {
      if (null == sendImmediate) {
        sendImmediate = new DiagramData(
            new String[] {category, "Mac", "send immediate"},
            "time", "s", "packet", "id", AbstractDiagramData.MARK);
        addData(sendImmediate, DataManager.LEVEL_ADDITIONAL);
      }
      return sendImmediate;
    }

    public DiagramData getPromiscReceive() {
      if (null == promiscReceive) {
        promiscReceive = new DiagramData(
            new String[] {category, "Mac", "promisc received"},
            "time", "s", "packet", "id", AbstractDiagramData.MARK);
        addData(promiscReceive, DataManager.LEVEL_ADDITIONAL);
      }
      return promiscReceive;
    }

    public DiagramData getSendImmediateDelay() {
      if (null == sendImmediateDelay) {
        sendImmediateDelay = new DiagramData(
            new String[] {category, "Mac", "send immediate delay"},
            "time", "s", "delay", "us", AbstractDiagramData.MARK);
        addData(sendImmediateDelay, DataManager.LEVEL_ALL);

        DiagramData sendImmediateDelayRated = new DiagramData(
            new AveragedTimeLine("send immediate delay (avg)", sampleLen, AveragedTimeLine.MODE_A, 1.),
            new String[] {category, "Mac", "send immediate delay (avg)"},
            "time", "s", "delay (avg)", "us");
        sendImmediateDelay.addChain(sendImmediateDelayRated);
        addData(sendImmediateDelayRated, DataManager.LEVEL_ADDITIONAL);
      }
      return sendImmediateDelay;
    }

    public DiagramData getForward(int id) {
      try {
        if (null == forward[id]) {
          forward[id] = new DiagramData(
              new String[] {category, "Mac", "forward flow " + id},
              "time", "s", "packet", "id", AbstractDiagramData.MARK);
          addData(forward[id], DataManager.LEVEL_ADDITIONAL);

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

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

      return macQueueLength;
    }

    public MacStats getStats() {
      if (null == stats) {
        stats = new MacStats();

        String[] path = new String[] { category, "Mac", "stats" };
        DataContribution contrib = new PropertiesData(stats, path);
        addData(contrib, DataManager.LEVEL_IMPORTANT);
      }
      return stats;
    }
  }

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

    protected DiagramData macDiscard = null;
    protected DiagramData shortRetry = null;
    protected DiagramData longRetry = null;
    protected DiagramData macNumberOfNotRecvAcks = null;
    protected DiagramData macNumberOfRecvAcks = null;
    protected DiagramData macForwarder = null;
    protected DiagramData macDuplicate = null;
    protected DiagramDataHist macDuplicateHist = null;
    protected DiagramDataHist macBackoffHist = null;
    protected DiagramDataHist macCwHist = null;
    protected DiagramData macIgnoredAcks = null;
    protected DiagramData sendImmediate = null;
    protected DiagramDataHist sendImmediateHist = null;
    protected DiagramDataHist sendHist = null;
    protected DiagramDataHist recvHist = null;
    protected DiagramData sendImmediateDelay = null;
    protected DiagramData forward = null;
    protected DiagramData ackCancelation = null;
    protected DiagramDataHist macQueueLengthHist = null;
    protected DiagramData promiscReceive = null;
    protected DiagramData promiscReceiveTime = null;
    private MacStats stats;

    private DiagramDataHist  macForwarderHist;

    private DiagramDataHist shortRetryHist;

    private DiagramDataHist longRetryHist;

    private DiagramDataHist macIgnoredAcksHist;

    private DiagramDataHist promiscReceiveTimeHist;

    private DiagramDataHist ackCancelationHist;

    protected DiagramDataHist getMacDuplicateHist() {
      if (null == macDuplicateHist) {
        macDuplicateHist = new DiagramDataHist(
            new String[] {category, "Mac", "duplicate (hist)"},
            "node", "mac addr", "duplicates", "amount");
        addData(macDuplicateHist, DataManager.LEVEL_BASIC);
      }
      return macDuplicateHist;
    }

    protected DiagramData getMacDuplicate() {
      if (null == macDuplicate) {
        macDuplicate = new DiagramData(
            new String[] {category, "Mac", "duplicate vs time"},
            "time", "s", "duplicate packets", "n/a", AbstractDiagramData.MARK);
        addData(macDuplicate, DataManager.LEVEL_BASIC);
      }

      return macDuplicate;
    }

    protected DiagramData getMacForwarder() {
      if (null == macForwarder) {
        macForwarder = new DiagramData(
            new String[] {category, "Mac", "forwarder vs time"},
            "time", "s", "forwarder", "mac id", AbstractDiagramData.MARK);
        addData(macForwarder, DataManager.LEVEL_ALL);
      }
      return macForwarder;
    }
    
    protected DiagramDataHist getMacForwarderHist() {
      if (null == macForwarderHist) {
        macForwarderHist = new DiagramDataHist(
            new String[] {category, "Mac", "forwarder (hist)"},
            "node", "mac id", "occurence", "count");
        addData(macForwarderHist, DataManager.LEVEL_BASIC);
      }
      return macForwarderHist;
    }


    protected DiagramData getMacDiscard() {
      // Mac Discard counter
      if (null == macDiscard) {
        macDiscard = new DiagramData(
            new String[] {category, "Mac", "discard vs time"}, "time", "s",
            "node", "mac id", AbstractDiagramData.MARK);
        addData(macDiscard, DataManager.LEVEL_BASIC);
      }

      return macDiscard;
    }

    protected DiagramData getMacNumberOfNotRecvAcks() {
      // Mac CandidateSet counter
      if (null == macNumberOfNotRecvAcks) {
        macNumberOfNotRecvAcks = new DiagramData(
            new String[] {category, "Mac", "not recv acks vs time"},
            "time", "s", "not received acks per packet", "n/a", AbstractDiagramData.MARK);
        addData(macNumberOfNotRecvAcks, DataManager.LEVEL_ADDITIONAL);
      }

      return macNumberOfNotRecvAcks;
    }

    protected DiagramData getMacNumberOfRecvAcks() {
      // Mac Retry counter
      if (null == macNumberOfRecvAcks) {
        macNumberOfRecvAcks = new DiagramData(
            new String[] {category, "Mac", "recv acks vs time"}, "time", "s",
            "received acks per packet", "n/a", AbstractDiagramData.MARK);
        addData(macNumberOfRecvAcks, DataManager.LEVEL_ADDITIONAL);
      }

      return macNumberOfRecvAcks;
    }

    protected DiagramData getShortRetry() {
      if (null == shortRetry) {
        shortRetry = new DiagramData(
            new String[] {category, "Mac", "short retries vs time"}, "time", "s",
            "node", "mac id", AbstractDiagramData.MARK);
        addData(shortRetry, DataManager.LEVEL_ALL);
      }

      return shortRetry;
    }
    protected DiagramDataHist getShortRetryHist() {
      if (null == shortRetryHist) {
        shortRetryHist = new DiagramDataHist(
            new String[] {category, "Mac", "short retries (hist)"},
            "node", "mac id", "retries", "count");
        addData(shortRetryHist, DataManager.LEVEL_BASIC);
      }

      return shortRetryHist;
    }

    protected DiagramData getLongRetry() {
      if (null == longRetry) {
        longRetry = new DiagramData(
            new String[] {category, "Mac", "long retries vs time"}, "time", "s",
            "node", "mac id", AbstractDiagramData.MARK);
        addData(longRetry, DataManager.LEVEL_ALL);
      }

      return longRetry;
    }

    protected DiagramDataHist getLongRetryHist() {
      if (null == longRetryHist) { 
        longRetryHist = new DiagramDataHist(
            new String[] {category, "Mac", "long retries (hist)"},
            "node", "mac id", "retries", "count");
        addData(longRetryHist, DataManager.LEVEL_BASIC);
      }

      return longRetryHist;
    }

    protected DiagramDataHist getMacBackoffHist() {
      if (null == macBackoffHist) {
        macBackoffHist = new DiagramDataHist(new String[] {category, "Mac",
            "backoff (cuml)"}, "node", "mac addr", "backoff (cuml)", "ms",
            DiagramDataHist.MODE_CUML);
        addData(macBackoffHist, DataManager.LEVEL_BASIC);
      }
      return macBackoffHist;
    }

    protected DiagramDataHist getMacCwHist() {
      if (null == macCwHist) {
        macCwHist = new DiagramDataHist(new String[] {category, "Mac",
            "contention window (max)"}, "node", "mac addr",
            "contention window (max)", "slots",
            DiagramDataHist.MODE_MAX);
        addData(macCwHist, DataManager.LEVEL_BASIC);
      }
      return macCwHist;
    }

    public DiagramData getMacIgnoredAcks() {
      if (null == macIgnoredAcks) {
        macIgnoredAcks = new DiagramData(new String[] { category, "Mac",
            "ignored acks vs time"}, "time", "s", "node", "id",
            AbstractDiagramData.MARK);
        addData(macIgnoredAcks, DataManager.LEVEL_ALL);
      }

      return macIgnoredAcks;
    }
    
    public DiagramDataHist getMacIgnoredAcksHist() {
      if (null == macIgnoredAcksHist) {
        macIgnoredAcksHist = new DiagramDataHist(new String[] {
            category, "Mac", "ignored acks (hist)"}, "node", "id", "occurance",
            "n/a");
        addData(macIgnoredAcksHist, DataManager.LEVEL_ADDITIONAL);
      }

      return macIgnoredAcksHist;
    }

    public DiagramData getSendImmediate() {
      if (null == sendImmediate) {
        sendImmediate = new DiagramData(
            new String[] {category, "Mac", "send immediate"},
            "time", "s", "packet", "id", AbstractDiagramData.MARK);
        addData(sendImmediate, DataManager.LEVEL_ALL);
      }
      return sendImmediate;
    }

    public DiagramDataHist getSendImmediateHist() {
      if (null == sendImmediateHist) {
        sendImmediateHist = new DiagramDataHist(
            new String[] {category, "Mac", "send immediate (hist)"},
            "node", "mac addr", "packets", "amount", DiagramDataHist.MODE_CUML);
        addData(sendImmediateHist, DataManager.LEVEL_BASIC);
      }

      return sendImmediateHist;
    }

    public DiagramData getSendImmediateDelay() {
      if (null == sendImmediateDelay) {
        sendImmediateDelay = new DiagramData(
            new String[] {category, "Mac", "send immediate delay"},
            "time", "s", "delay", "us", AbstractDiagramData.MARK);
        addData(sendImmediateDelay, DataManager.LEVEL_ALL);

        DiagramData sendImmediateDelayRated = new DiagramData(
            new AveragedTimeLine("send immediate delay (avg)", sampleLen, AveragedTimeLine.MODE_A, 1.),
            new String[] {category, "Mac", "send immediate delay (avg)"},
            "time", "s", "delay (avg)", "us");
        sendImmediateDelay.addChain(sendImmediateDelayRated);
        addData(sendImmediateDelayRated, DataManager.LEVEL_ADDITIONAL);
      }
      return sendImmediateDelay;
    }

    public DiagramData getPromiscReceive() {
      if (null == promiscReceive) {
        promiscReceive = new DiagramData(
            new String[] {category, "Mac", "promisc received"},
            "time", "s", "packet", "id", AbstractDiagramData.MARK);
        addData(promiscReceive, DataManager.LEVEL_ALL);
      }
      return promiscReceive;
    }

    public DiagramData getPromiscReceiveTime() {
      if (null == promiscReceiveTime) {
        promiscReceiveTime = new DiagramData(
            new String[] {category, "Mac", "promisc received vs time"},
            "time", "s", "node", "id", AbstractDiagramData.MARK);
        addData(promiscReceiveTime, DataManager.LEVEL_ALL);
      }

      return promiscReceiveTime;
    }

    public DiagramDataHist getPromiscReceiveTimeHist() {
      if (null == promiscReceiveTimeHist) {
        promiscReceiveTimeHist = new DiagramDataHist(
            new String[] {category, "Mac", "promisc received (hist)"},
            "node", "mac addr", "packets", "amount");
        addData(promiscReceiveTimeHist, DataManager.LEVEL_ALL);
      }

      return promiscReceiveTimeHist;
    }

    public DiagramData getAckCancelation() {
      if (null == ackCancelation) {
        ackCancelation = new DiagramData(
            new String[] {category, "Mac", "ack cancelation"},
            "time", "s", "node", "id", AbstractDiagramData.MARK);
        addData(ackCancelation, DataManager.LEVEL_ALL);
      }
      return ackCancelation;
    }

    public DiagramDataHist getAckCancelationHist() {
      if (null == ackCancelationHist) {
        ackCancelationHist = new DiagramDataHist(
            new String[] {category, "Mac", "ack cancelation (hist)"},
            "node", "mac addr", "packets", "amount");
        addData(ackCancelationHist, DataManager.LEVEL_BASIC);
      }
      return ackCancelationHist;
    }

    public DiagramDataHist getMacQueueLengthHist() {
      if (null == macQueueLengthHist) {
        macQueueLengthHist = new DiagramDataHist(new String[] {category, "Mac",
            "queue length (max)"}, "node", "mac id", "packets in queue (max)", "no.",
            DiagramDataHist.MODE_MAX);
        addData(macQueueLengthHist, DataManager.LEVEL_BASIC);
      }
      return macQueueLengthHist;
    }

    public MacStats getStats() {
      if (null == stats) {
        stats = new MacStats();

        String[] path = new String[] { category, "Mac", "Stats" };
        DataContribution contrib = new PropertiesData(stats, path);
        addData(contrib, DataManager.LEVEL_IMPORTANT);
      }
      return stats;
    }

    public DiagramDataHist getSendHist() {
      if (null == sendHist) {
        sendHist = new DiagramDataHist(
            new String[] {category, "Mac", "send (hist)"},
            "node", "mac addr", "packets", "amount", DiagramDataHist.MODE_CUML);
        addData(sendHist, DataManager.LEVEL_BASIC);
      }

      return sendHist;
    }

    public DiagramDataHist getRecvHist() {
      if (null == recvHist) {
        recvHist = new DiagramDataHist(
            new String[] {category, "Mac", "recv (hist)"},
            "node", "mac addr", "packets", "amount", DiagramDataHist.MODE_CUML);
        addData(recvHist, DataManager.LEVEL_BASIC);
      }
      return recvHist;
    }

  }


  private static final String ID = "StatsMacHandler";

  private double sampleLen;

  private NodeData[] nodeData;

  private GlobalData globalData;

  public StatsMacHandler(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 mac handlers.
   */
  public void registerHandlers() {
    super.registerHandlers();

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

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

    // discarded packets on mac layer
    Event.addHandler(AbstractMac.DiscardEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.DiscardEvent ev = (AbstractMac.DiscardEvent) event;
        getNodeData(ev.nodeId).getMacDiscard().addNextTimePoint(ev.time, 1.0);
      }
    });

    // retry on mac layer
    Event.addHandler(AbstractMac.RetryEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.RetryEvent ev = (AbstractMac.RetryEvent) event;
        if (ev.shortRetry) {
          getNodeData(ev.nodeId).getShortRetry().addNextTimePoint(ev.time, ev.retryCount);
          globalData.getShortRetry().addNextTimePoint(ev.time, (double) ev.nodeId);
          globalData.getShortRetryHist().addNextValue((double) ev.nodeId);
        } else {
          getNodeData(ev.nodeId).getLongRetry().addNextTimePoint(ev.time, ev.retryCount);
          globalData.getLongRetry().addNextTimePoint(ev.time, (double) ev.nodeId);
          globalData.getLongRetryHist().addNextValue((double) ev.nodeId);
        }
      }
    });

    Event.addHandler(AbstractMac.DuplicateEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.DuplicateEvent ev = (AbstractMac.DuplicateEvent) event;

        // TODO use getAdapter
        if (ev.data instanceof MacMessage.HasSeq)
          getNodeData(ev.nodeId).getMacDuplicate().addNextTimePoint(ev.time,
              ((MacMessage.HasSeq) ev.data).getSeq());
      }
    });

    Event.addHandler(AbstractMac.SendEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.SendEvent ev = (AbstractMac.SendEvent) event;

        // TODO use getAdapter
        if (ev.data instanceof MacMessage.HasSeq)
          getNodeData(ev.nodeId).getMacSend().addNextTimePoint(ev.time,
              ((MacMessage.HasSeq) ev.data).getSeq());
        
        globalData.getSendHist().addNextValue(ev.nodeId, 1.);

        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;
        
        if (Main.ASSERT)
          Util.assertion(!(ev.data instanceof MacDcfMessage.Ack));

        getNodeData(ev.nodeId).getForward(flowId.intValue()).addNextTimePoint(
            ev.time, packetId.intValue());
      }
    });

    Event.addHandler(AbstractMac.ReceiveEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.ReceiveEvent ev = (AbstractMac.ReceiveEvent) event;
        NodeData nodeData2 = getNodeData(ev.nodeId);

        int msgId = -1;
        if (null != ev.anno) {
          msgId = (Integer) ev.anno.get(MessageAnno.ANNO_MSG_ID);
          nodeData2.getMacReceive().addNextTimePoint(ev.time, msgId);
        }

        if (ev.data instanceof MacMessage.HasSrc) {
          long srcId = ((MacMessage.HasSrc)ev.data).getSrc().getId();
          nodeData2.getMacReceiveSrc().addNextTimePoint(ev.time, srcId);
          nodeData2.getMacReceiveHist().addNextValue(srcId);
          if (-1 != msgId)
            nodeData2.getMacReceiveSrcPid().addNextPoint(msgId, srcId);
        }

        globalData.getRecvHist().addNextValue(ev.nodeId, 1.);
      }
    });

    // TODO how to deal with foreign?
    Event.addHandler(AbstractMac.ReceiveForeignEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.ReceiveEvent ev = (AbstractMac.ReceiveEvent) event;
        NodeData nodeData2 = getNodeData(ev.nodeId);

        int msgId = -1;
        if (null != ev.anno) {
          msgId = (Integer) ev.anno.get(MessageAnno.ANNO_MSG_ID);
          nodeData2.getMacReceive().addNextTimePoint(ev.time, msgId);
        }
        
        if (ev.data instanceof MacMessage.HasSrc) {
          long srcId = ((MacMessage.HasSrc)ev.data).getSrc().getId();
          nodeData2.getMacReceiveSrc().addNextTimePoint(ev.time, srcId);
          nodeData2.getMacReceiveHist().addNextValue(srcId);
          if (-1 != msgId)
            nodeData2.getMacReceiveSrcPid().addNextPoint(msgId, srcId);
        }
      }
    });
    // TODO how to deal with foreign?

    Event.addHandler(AbstractMac.CwChangedEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.CwChangedEvent ev = (AbstractMac.CwChangedEvent) event;
        getNodeData(ev.nodeId).getMacCw().addNextTimePoint(ev.time, ev.cw);
        globalData.getMacCwHist().addNextValue(ev.nodeId, ev.cw);
      }
    });

    Event.addHandler(AbstractMac.BackoffEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.BackoffEvent ev = (AbstractMac.BackoffEvent) event;
        double bo_ms = ev.waitedBo / (double) Constants.MILLI_SECOND;
        getNodeData(ev.nodeId).getMacBackoff().addNextTimePoint(ev.time,
            bo_ms);
        globalData.getMacBackoffHist().addNextValue(ev.nodeId, bo_ms);
      }
    });

    Event.addHandler(AbstractMac.TXOPTimeEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.TXOPTimeEvent ev = (AbstractMac.TXOPTimeEvent) event;
        double bo_ms = ev.duration / (double) Constants.MILLI_SECOND;
        getNodeData(ev.nodeId).getMacTXOP().addNextTimePoint(ev.time,
            bo_ms);
//        globalData.getMacBackoffHist().addNextValue(ev.nodeId, bo_ms);
      }
    });

    Event.addHandler(AbstractMac.SendImmediateEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.SendImmediateEvent ev = (AbstractMac.SendImmediateEvent) event;
        // TODO flow id
        Integer packetId = null;
        if (null != ev.anno) 
          packetId = (Integer) ev.anno.get(MessageAnno.ANNO_RTG_PACKETID);

        if (null != packetId) {
          getNodeData(ev.nodeId).getSendImmediate().addNextTimePoint(ev.time,
              packetId.intValue());
          globalData.getSendImmediate().addNextTimePoint(ev.time,
              packetId.intValue());
        }
        globalData.getSendImmediateHist().addNextValue(ev.nodeId);

        // available delay
        getNodeData(ev.nodeId).getSendImmediateDelay().addNextTimePoint(ev.time,
            ev.delay / (double) Constants.MICRO_SECOND);
        globalData.getSendImmediateDelay().addNextTimePoint(ev.time,
            ev.delay / (double) Constants.MICRO_SECOND);
      }
    });

    Event.addHandler(AbstractMac.PromiscReceiveEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.PromiscReceiveEvent ev =
          (AbstractMac.PromiscReceiveEvent) event;
//        globalData.getPromiscReceive().addNextTimePoint(ev.time, ev.nodeId);

        // TODO flow id
        Integer packetId = null;
        if (null != ev.anno)
          packetId = (Integer) ev.anno.get(MessageAnno.ANNO_RTG_PACKETID);

        // TODO packet id
        if (null != packetId) {
          getNodeData(ev.nodeId).getPromiscReceive().addNextTimePoint(ev.time,
              packetId.intValue());
          globalData.getPromiscReceive().addNextTimePoint(ev.time,
              packetId.intValue());
        }
        globalData.getPromiscReceiveTime().addNextTimePoint(ev.time, ev.nodeId);
        globalData.getPromiscReceiveTimeHist().addNextValue(ev.nodeId);
      }
    });

    Event.addHandler(AbstractMac.SendEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.SendEvent ev = (AbstractMac.SendEvent) event;
        
        if (ev.data instanceof MacDumbMessage) {
          MacDumbMessage msg = (MacDumbMessage) ev.data;
          if (MacAddress.ANY.equals(msg.getDst())) {
            globalData.getStats().incBroadcastSend();
            getNodeData(ev.nodeId).getStats().incBroadcastSend();
          } else {
            globalData.getStats().incUnicastSend();
            getNodeData(ev.nodeId).getStats().incUnicastSend();
          }
        } else {
          MacDcfMessage msg = (MacDcfMessage) ev.data;
          switch (msg.getType()) {
          case MacDcfMessage.TYPE_DATA:
            MacMessage.HasDst dstMsg = (MacMessage.HasDst)msg.getAdapter(MacMessage.HasDst.class);
            if (null != dstMsg
                && MacAddress.ANY.equals(dstMsg.getDst())) {
              globalData.getStats().incBroadcastSend();
              getNodeData(ev.nodeId).getStats().incBroadcastSend();
            } else {
              globalData.getStats().incUnicastSend();
              getNodeData(ev.nodeId).getStats().incUnicastSend();
            }
            break;
          case MacDcfMessage.TYPE_ACK:
            globalData.getStats().incAckSend();
            getNodeData(ev.nodeId).getStats().incAckSend();
            break;
          case Mac802_11Message.TYPE_RTS:
            globalData.getStats().incRtsSend();
            getNodeData(ev.nodeId).getStats().incRtsSend();
            break;
          case Mac802_11Message.TYPE_CTS:
            globalData.getStats().incCtsSend();
            getNodeData(ev.nodeId).getStats().incCtsSend();
            break;
          default:
            log.error("unknown message type " + msg.getType());
            break;
          }
        }
      }
    });
    Event.addHandler(AbstractMac.ReceiveEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.ReceiveEvent ev = (AbstractMac.ReceiveEvent) event;

        if (ev.data instanceof MacDumbMessage) {
          MacDumbMessage msg = (MacDumbMessage) ev.data;
          if (MacAddress.ANY.equals(msg.getDst())) {
            globalData.getStats().incBroadcastRecv();
            getNodeData(ev.nodeId).getStats().incBroadcastRecv();
          }
          else {
            globalData.getStats().incUnicastRecv();
            getNodeData(ev.nodeId).getStats().incUnicastRecv();
          }
        } else {
          MacDcfMessage msg = (MacDcfMessage) ev.data;
          switch (msg.getType()) {
          case MacDcfMessage.TYPE_DATA:
            if (MacAddress.ANY.equals(((MacDcfMessage.Data)msg).getDst())) {
              globalData.getStats().incBroadcastRecv();
              getNodeData(ev.nodeId).getStats().incBroadcastRecv();
            }
            else {
              globalData.getStats().incUnicastRecv();
              getNodeData(ev.nodeId).getStats().incUnicastRecv();
            }
            break;
          case MacDcfMessage.TYPE_ACK:
            globalData.getStats().incAckRecv();
            getNodeData(ev.nodeId).getStats().incAckRecv();
            break;
          case Mac802_11Message.TYPE_RTS:
            globalData.getStats().incRtsRecv();
            getNodeData(ev.nodeId).getStats().incRtsRecv();
            break;
          case Mac802_11Message.TYPE_CTS:
            globalData.getStats().incCtsRecv();
            getNodeData(ev.nodeId).getStats().incCtsRecv();
            break;
          default:
            log.error("unknown message type " + msg.getType());
            break;
          }
        }
      }
    });

    Event.addHandler(AbstractMac.DiscardEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.DiscardEvent ev = (AbstractMac.DiscardEvent) event;
        globalData.getMacDiscard().addNextTimePoint(ev.time, ev.nodeId);
      }
    });

    Event.addHandler(AbstractMac.ForwarderEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.ForwarderEvent ev = (AbstractMac.ForwarderEvent) event;
        globalData.getMacForwarder().addNextTimePoint(ev.time, ev.localAddr.getId());
        globalData.getMacForwarderHist().addNextValue(ev.localAddr.getId());
      }
    });

    Event.addHandler(AbstractMac.DuplicateEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.DuplicateEvent ev = (AbstractMac.DuplicateEvent) event;

        // TODO use getAdapter
        if (ev.data instanceof MacMessage.HasSeq)
          globalData.getMacDuplicate().addNextTimePoint(ev.time, ((MacMessage.HasSeq) ev.data).getSeq());

        globalData.getMacDuplicateHist().addNextValue(ev.localAddr.getId());
      }
    });

    Event.addHandler(AbstractMac.MacIgnoredAcks.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.MacIgnoredAcks ev = (AbstractMac.MacIgnoredAcks) event;
        globalData.getMacIgnoredAcks().addNextTimePoint(ev.time, ev.nodeId);
        globalData.getMacIgnoredAcksHist().addNextValue(ev.nodeId);
      }
    });

    Event.addHandler(AbstractMac.SendAckCancelationEvent.class, new Event.Handler() {
      public void handle(Event event) {
        AbstractMac.SendAckCancelationEvent ev =
          (AbstractMac.SendAckCancelationEvent) event;
        globalData.getAckCancelation().addNextTimePoint(ev.time, ev.nodeId);
        globalData.getAckCancelationHist().addNextValue(ev.nodeId);
      }
    });
  }
}
